From 347b7201de0f82fa5e4cba411c43181c2492931a Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Wed, 4 Jan 2023 17:11:07 -0800 Subject: [PATCH] Finished secret snapshots --- .../ee/controllers/v1/workspaceController.ts | 1 + backend/src/ee/routes/v1/workspace.ts | 4 +- backend/src/helpers/secret.ts | 11 +- frontend/components/basic/Layout.tsx | 10 +- frontend/components/basic/buttons/Button.tsx | 2 +- .../context/Notifications/Notification.tsx | 4 +- .../Notifications/NotificationProvider.tsx | 2 +- frontend/components/dashboard/KeyPair.tsx | 15 +- .../api/secrets/GetProjectSercetShanpshots.ts | 39 +++ .../secrets/GetProjectSercetSnapshotsCount.ts | 31 +++ .../ee/api/secrets/GetSecretSnapshotData.ts | 31 +++ frontend/ee/components/PITRecoverySidebar.tsx | 158 +++++++++++ frontend/pages/dashboard/[id].tsx | 247 ++++++++++++++---- frontend/public/locales/en/activity.json | 3 +- 14 files changed, 485 insertions(+), 73 deletions(-) create mode 100644 frontend/ee/api/secrets/GetProjectSercetShanpshots.ts create mode 100644 frontend/ee/api/secrets/GetProjectSercetSnapshotsCount.ts create mode 100644 frontend/ee/api/secrets/GetSecretSnapshotData.ts create mode 100644 frontend/ee/components/PITRecoverySidebar.tsx diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index 016baff67..88c31b8e1 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -21,6 +21,7 @@ import { secretSnapshots = await SecretSnapshot.find({ workspace: workspaceId }) + .sort({ createdAt: -1 }) .skip(offset) .limit(limit); diff --git a/backend/src/ee/routes/v1/workspace.ts b/backend/src/ee/routes/v1/workspace.ts index 48c054970..4b2e839eb 100644 --- a/backend/src/ee/routes/v1/workspace.ts +++ b/backend/src/ee/routes/v1/workspace.ts @@ -39,7 +39,9 @@ router.get( router.get( '/:workspaceId/logs', - requireAuth, + requireAuth({ + acceptedAuthModes: ['jwt'] + }), requireWorkspaceAuth({ acceptedRoles: [ADMIN, MEMBER] }), diff --git a/backend/src/helpers/secret.ts b/backend/src/helpers/secret.ts index 0d64da3b1..920e8dc1d 100644 --- a/backend/src/helpers/secret.ts +++ b/backend/src/helpers/secret.ts @@ -470,11 +470,12 @@ const v1PushSecrets = async ({ // (EE) add secret versions for new secrets EESecretService.addSecretVersions({ - secretVersions: newSecrets.map((s) => ({ - ...s, - secret: s._id, - isDeleted: false - })) + secretVersions: newSecrets.map((secretDocument) => { + return { + ...secretDocument.toObject(), + secret: secretDocument._id, + isDeleted: false + }}) }); const addAction = await EELogService.createActionSecret({ diff --git a/frontend/components/basic/Layout.tsx b/frontend/components/basic/Layout.tsx index 8bdabc97b..8ac9ec92b 100644 --- a/frontend/components/basic/Layout.tsx +++ b/frontend/components/basic/Layout.tsx @@ -121,7 +121,7 @@ export default function Layout({ children }: LayoutProps) { } }); } - router.push("/dashboard/" + newWorkspaceId + "?Development"); + router.push("/dashboard/" + newWorkspaceId); setIsOpen(false); setNewWorkspaceName(""); } else { @@ -141,8 +141,7 @@ export default function Layout({ children }: LayoutProps) { { href: "/dashboard/" + - workspaceMapping[workspaceSelected as any] + - "?Development", + workspaceMapping[workspaceSelected as any], title: t("nav:menu.secrets"), emoji: , }, @@ -199,7 +198,7 @@ export default function Layout({ children }: LayoutProps) { .map((workspace: { _id: string }) => workspace._id) .includes(intendedWorkspaceId) ) { - router.push("/dashboard/" + userWorkspaces[0]._id + "?Development"); + router.push("/dashboard/" + userWorkspaces[0]._id); } else { setWorkspaceList( userWorkspaces.map((workspace: any) => workspace.name) @@ -242,8 +241,7 @@ export default function Layout({ children }: LayoutProps) { ) { router.push( "/dashboard/" + - workspaceMapping[workspaceSelected as any] + - "?Development" + workspaceMapping[workspaceSelected as any] ); localStorage.setItem( "projectData.id", diff --git a/frontend/components/basic/buttons/Button.tsx b/frontend/components/basic/buttons/Button.tsx index 9197ccf13..939d9b17d 100644 --- a/frontend/components/basic/buttons/Button.tsx +++ b/frontend/components/basic/buttons/Button.tsx @@ -115,7 +115,7 @@ export default function Button(props: ButtonProps): JSX.Element { )} diff --git a/frontend/components/context/Notifications/Notification.tsx b/frontend/components/context/Notifications/Notification.tsx index ad556a6f5..6635c795e 100644 --- a/frontend/components/context/Notifications/Notification.tsx +++ b/frontend/components/context/Notifications/Notification.tsx @@ -36,7 +36,7 @@ const Notification = ({ return (
{notification.type === 'error' && ( @@ -56,7 +56,7 @@ const Notification = ({ onClick={() => clearNotification(notification.text)} > diff --git a/frontend/components/context/Notifications/NotificationProvider.tsx b/frontend/components/context/Notifications/NotificationProvider.tsx index 05f9eee19..aa694a1d0 100644 --- a/frontend/components/context/Notifications/NotificationProvider.tsx +++ b/frontend/components/context/Notifications/NotificationProvider.tsx @@ -38,7 +38,7 @@ const NotificationProvider = ({ children }: NotificationProviderProps) => { const createNotification = ({ text, type = 'success', - timeoutMs = 5000 + timeoutMs = 4000 }: Notification) => { const doesNotifExist = notifications.some((notif) => notif.text === text); diff --git a/frontend/components/dashboard/KeyPair.tsx b/frontend/components/dashboard/KeyPair.tsx index 52495829a..ba6cc6ee7 100644 --- a/frontend/components/dashboard/KeyPair.tsx +++ b/frontend/components/dashboard/KeyPair.tsx @@ -21,6 +21,7 @@ interface KeyPairProps { isDuplicate: boolean; toggleSidebar: (id: string) => void; sidebarSecretId: string; + isSnapshot: boolean; } /** @@ -33,6 +34,7 @@ interface KeyPairProps { * @param {boolean} obj.isDuplicate - list of all the duplicates secret names on the dashboard * @param {function} obj.toggleSidebar - open/close/switch sidebar * @param {string} obj.sidebarSecretId - the id of a secret for the side bar is displayed + * @param {boolean} obj.isSnapshot - whether this keyPair is in a snapshot. If so, it won't have some features like sidebar * @returns */ const KeyPair = ({ @@ -42,10 +44,11 @@ const KeyPair = ({ isBlurred, isDuplicate, toggleSidebar, - sidebarSecretId + sidebarSecretId, + isSnapshot }: KeyPairProps) => { return ( -
+
{keyPair.type == "personal" &&
@@ -65,7 +68,7 @@ const KeyPair = ({
-
+
-
toggleSidebar(keyPair.id)} className="cursor-pointer w-[2.35rem] h-[2.35rem] bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> + {!isSnapshot &&
toggleSidebar(keyPair.id)} className="cursor-pointer w-[2.35rem] h-[2.35rem] bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> -
+
}
); }; -export default React.memo(KeyPair); \ No newline at end of file +export default KeyPair; \ No newline at end of file diff --git a/frontend/ee/api/secrets/GetProjectSercetShanpshots.ts b/frontend/ee/api/secrets/GetProjectSercetShanpshots.ts new file mode 100644 index 000000000..21c2ac801 --- /dev/null +++ b/frontend/ee/api/secrets/GetProjectSercetShanpshots.ts @@ -0,0 +1,39 @@ +import SecurityClient from '~/utilities/SecurityClient'; + + +interface workspaceProps { + workspaceId: string; + offset: number; + limit: number; +} + +/** + * This function fetches the secret snapshots for a certain project + * @param {object} obj + * @param {string} obj.workspaceId - project id for which we are trying to get project secret snapshots + * @param {object} obj.offset - teh starting point of snapshots that we want to pull + * @param {object} obj.limit - how many snapshots will we output + * @returns + */ +const getProjectSecretShanpshots = async ({ workspaceId, offset, limit }: workspaceProps) => { + return SecurityClient.fetchCall( + '/api/v1/workspace/' + workspaceId + '/secret-snapshots?' + + new URLSearchParams({ + offset: String(offset), + limit: String(limit) + }), { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + ).then(async (res) => { + if (res && res.status == 200) { + return (await res.json()).secretSnapshots; + } else { + console.log('Failed to get project secret snapshots'); + } + }); +}; + +export default getProjectSecretShanpshots; diff --git a/frontend/ee/api/secrets/GetProjectSercetSnapshotsCount.ts b/frontend/ee/api/secrets/GetProjectSercetSnapshotsCount.ts new file mode 100644 index 000000000..19389026b --- /dev/null +++ b/frontend/ee/api/secrets/GetProjectSercetSnapshotsCount.ts @@ -0,0 +1,31 @@ +import SecurityClient from '~/utilities/SecurityClient'; + + +interface workspaceProps { + workspaceId: string; +} + +/** + * This function fetches the count of secret snapshots for a certain project + * @param {object} obj + * @param {string} obj.workspaceId - project id for which we are trying to get project secret snapshots + * @returns + */ +const getProjectSercetSnapshotsCount = async ({ workspaceId }: workspaceProps) => { + return SecurityClient.fetchCall( + '/api/v1/workspace/' + workspaceId + '/secret-snapshots/count', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + ).then(async (res) => { + if (res && res.status == 200) { + return (await res.json()).count; + } else { + console.log('Failed to get the count of project secret snapshots'); + } + }); +}; + +export default getProjectSercetSnapshotsCount; diff --git a/frontend/ee/api/secrets/GetSecretSnapshotData.ts b/frontend/ee/api/secrets/GetSecretSnapshotData.ts new file mode 100644 index 000000000..181fa85ce --- /dev/null +++ b/frontend/ee/api/secrets/GetSecretSnapshotData.ts @@ -0,0 +1,31 @@ +import SecurityClient from '~/utilities/SecurityClient'; + + +interface SnapshotProps { + secretSnapshotId: string; +} + +/** + * This function fetches the secrets for a certain secret snapshot + * @param {object} obj + * @param {string} obj.secretSnapshotId - snapshot id for which we are trying to get secrets + * @returns + */ +const getSecretSnapshotData = async ({ secretSnapshotId }: SnapshotProps) => { + return SecurityClient.fetchCall( + '/api/v1/secret-snapshot/' + secretSnapshotId, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + ).then(async (res) => { + if (res && res.status == 200) { + return (await res.json()).secretSnapshot; + } else { + console.log('Failed to get the secrets of a certain snapshot'); + } + }); +}; + +export default getSecretSnapshotData; diff --git a/frontend/ee/components/PITRecoverySidebar.tsx b/frontend/ee/components/PITRecoverySidebar.tsx new file mode 100644 index 000000000..e763d695b --- /dev/null +++ b/frontend/ee/components/PITRecoverySidebar.tsx @@ -0,0 +1,158 @@ +import { useEffect, useState } from "react"; +import Image from "next/image"; +import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; +import { faX } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import getProjectSecretShanpshots from "ee/api/secrets/GetProjectSercetShanpshots"; +import getSecretSnapshotData from "ee/api/secrets/GetSecretSnapshotData"; +import timeSince from "ee/utilities/timeSince"; + +import Button from "~/components/basic/buttons/Button"; +import { decryptAssymmetric, decryptSymmetric } from "~/components/utilities/cryptography/crypto"; +import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; + + +interface SideBarProps { + toggleSidebar: (value: boolean) => void; + setSnapshotData: (value: any) => void; + chosenSnapshot: string; +} + +interface SnaphotProps { + _id: string; + createdAt: string; + secretVersions: string[]; +} + +interface EncrypetedSecretVersionListProps { + _id: string; + createdAt: string; + secretValueCiphertext: string; + secretValueIV: string; + secretValueTag: string; + secretKeyCiphertext: string; + secretKeyIV: string; + secretKeyTag: string; + environment: string; + type: "personal" | "shared"; +} + +/** + * @param {object} obj + * @param {function} obj.toggleSidebar - function that opens or closes the sidebar + * @param {function} obj.setSnapshotData - state manager for snapshot data + * @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected + * + * + * @returns the sidebar with the options for point-in-time recovery (commits) + */ +const PITRecoverySidebar = ({ + toggleSidebar, + setSnapshotData, + chosenSnapshot +}: SideBarProps) => { + const { t } = useTranslation(); + const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + const [secretSnapshotsMetadata, setSecretSnapshotsMetadata] = useState([]); + const [currentOffset, setCurrentOffset] = useState(0); + const currentLimit = 15; + + const loadMoreSnapshots = () => { + setCurrentOffset(currentOffset + currentLimit); + } + + useEffect(() => { + const getLogData = async () => { + setIsLoading(true); + const results = await getProjectSecretShanpshots({ workspaceId: String(router.query.id), limit: currentLimit, offset: currentOffset }) + setSecretSnapshotsMetadata(secretSnapshotsMetadata.concat(results)); + setIsLoading(false); + } + getLogData(); + }, [currentOffset]); + + const exploreSnapshot = async ({ snapshotId }: { snapshotId: string; }) => { + const secretSnapshotData = await getSecretSnapshotData({ secretSnapshotId: snapshotId }); + + const latestKey = await getLatestFileKey({ workspaceId: String(router.query.id) }) + const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY'); + + let decryptedLatestKey: string; + if (latestKey) { + // assymmetrically decrypt symmetric key with local private key + decryptedLatestKey = decryptAssymmetric({ + ciphertext: latestKey.latestKey.encryptedKey, + nonce: latestKey.latestKey.nonce, + publicKey: latestKey.latestKey.sender.publicKey, + privateKey: String(PRIVATE_KEY) + }); + } + + const decryptedSecretVersions = secretSnapshotData.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => { + return { + id: encryptedSecretVersion._id, + pos: pos, + type: encryptedSecretVersion.type, + environment: encryptedSecretVersion.environment, + key: decryptSymmetric({ + ciphertext: encryptedSecretVersion.secretKeyCiphertext, + iv: encryptedSecretVersion.secretKeyIV, + tag: encryptedSecretVersion.secretKeyTag, + key: decryptedLatestKey + }), + value: decryptSymmetric({ + ciphertext: encryptedSecretVersion.secretValueCiphertext, + iv: encryptedSecretVersion.secretValueIV, + tag: encryptedSecretVersion.secretValueTag, + key: decryptedLatestKey + }) + } + }) + + setSnapshotData({ id: secretSnapshotData._id, createdAt: secretSnapshotData.createdAt, secretVersions: decryptedSecretVersions }) + } + + return
+ {isLoading ? ( +
+ infisical loading indicator +
+ ) : ( +
+
+

{t("Point-in-time Recovery")}

+
toggleSidebar(false)}> + +
+
+
+ {secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
+
+
{timeSince(new Date(snapshot.createdAt))}
+
{" - " + snapshot.secretVersions.length + " Secrets"}
+
+
exploreSnapshot({ snapshotId: snapshot._id })} + className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800 pointer-events-none" : "text-bunker-200 hover:text-primary duration-200 cursor-pointer"} text-sm`}> + {id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"} +
+
)} +
+
+
+
+
+
+ )} +
+}; + +export default PITRecoverySidebar; diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 24c0a850c..d6e104f57 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -6,8 +6,9 @@ import { useTranslation } from "next-i18next"; import { faArrowDownAZ, faArrowDownZA, + faArrowLeft, faCheck, - faCopy, + faClockRotateLeft, faDownload, faEye, faEyeSlash, @@ -16,6 +17,8 @@ import { faPlus, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import getProjectSercetSnapshotsCount from 'ee/api/secrets/GetProjectSercetSnapshotsCount'; +import PITRecoverySidebar from 'ee/components/PITRecoverySidebar'; import Button from '~/components/basic/buttons/Button'; import ListBox from '~/components/basic/Listbox'; @@ -30,12 +33,13 @@ import pushKeys from '~/components/utilities/secrets/pushKeys'; import { getTranslatedServerSideProps } from '~/components/utilities/withTranslateProps'; import guidGenerator from '~/utilities/randomId'; -import { envMapping } from '../../public/data/frequentConstants'; +import { envMapping, reverseEnvMapping } from '../../public/data/frequentConstants'; import getUser from '../api/user/getUser'; import checkUserAction from '../api/userActions/checkUserAction'; import registerUserAction from '../api/userActions/registerUserAction'; import getWorkspaces from '../api/workspace/getWorkspaces'; +const queryString = require("query-string"); interface SecretDataProps { type: 'personal' | 'shared'; @@ -46,6 +50,19 @@ interface SecretDataProps { comment: string; } +interface SnapshotProps { + id: string; + createdAt: string; + secretVersions: { + id: string; + pos: number; + type: "personal" | "shared"; + environment: string; + key: string; + value: string; + }[]; +} + /** * this function finds the teh duplicates in an array * @param arr - array of anything (e.g., with secret keys and types (personal/shared)) @@ -76,21 +93,20 @@ export default function Dashboard() { const [workspaceId, setWorkspaceId] = useState(''); const [blurred, setBlurred] = useState(true); const [isKeyAvailable, setIsKeyAvailable] = useState(true); - const [env, setEnv] = useState( - router.asPath.split('?').length == 1 - ? 'Development' - : Object.keys(envMapping).includes(router.asPath.split('?')[1]) - ? router.asPath.split('?')[1] - : 'Development' - ); + const [env, setEnv] = useState('Development'); + const [snapshotEnv, setSnapshotEnv] = useState('Development'); const [isNew, setIsNew] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [searchKeys, setSearchKeys] = useState(''); const [errorDragAndDrop, setErrorDragAndDrop] = useState(false); const [sortMethod, setSortMethod] = useState('alphabetical'); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false); const [sidebarSecretId, toggleSidebar] = useState("None"); + const [PITSidebarOpen, togglePITSidebar] = useState(false); const [sharedToHide, setSharedToHide] = useState([]); + const [snapshotData, setSnapshotData] = useState(); + const [numSnapshots, setNumSnapshots] = useState(); const { t } = useTranslation(); const { createNotification } = useNotificationContext(); @@ -141,17 +157,39 @@ export default function Dashboard() { useEffect(() => { (async () => { try { + console.log(1, 'reloaded') + const tempNumSnapshots = await getProjectSercetSnapshotsCount({ workspaceId: String(router.query.id) }) + setNumSnapshots(tempNumSnapshots); const userWorkspaces = await getWorkspaces(); const listWorkspaces = userWorkspaces.map((workspace) => workspace._id); if ( - !listWorkspaces.includes(router.asPath.split('/')[2].split('?')[0]) + !listWorkspaces.includes(router.asPath.split('/')[2]) ) { router.push('/dashboard/' + listWorkspaces[0]); } - if (env != router.asPath.split('?')[1]) { - router.push(router.asPath.split('?')[0] + '?' + env); - } + const user = await getUser(); + setIsNew( + (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / 60000 < 3 + ? true + : false + ); + + const userAction = await checkUserAction({ + action: 'first_time_secrets_pushed' + }); + setHasUserEverPushed(userAction ? true : false); + } catch (error) { + console.log('Error', error); + setData(undefined); + } + })(); + }, []); + + useEffect(() => { + (async () => { + try { + setIsLoading(true); setBlurred(true); setWorkspaceId(String(router.query.id)); @@ -173,18 +211,7 @@ export default function Dashboard() { dataToSort?.map((item) => item.key).indexOf(item) ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id) ) - - const user = await getUser(); - setIsNew( - (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / 60000 < 3 - ? true - : false - ); - - const userAction = await checkUserAction({ - action: 'first_time_secrets_pushed' - }); - setHasUserEverPushed(userAction ? true : false); + setIsLoading(false); } catch (error) { console.log('Error', error); setData(undefined); @@ -321,12 +348,21 @@ export default function Dashboard() { /** * Save the changes of environment variables and push them to the database */ - const savePush = async () => { - // Format the new object with environment variables - const obj = Object.assign( - {}, - ...data!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment] })) - ); + const savePush = async (dataToPush?: any[], envToPush?: string) => { + let obj; + // dataToPush is mostly used for rollbacks, otherwise we always take the current state data + if ((dataToPush ?? [])?.length > 0) { + obj = Object.assign( + {}, + ...dataToPush!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] })) + ); + } else { + // Format the new object with environment variables + obj = Object.assign( + {}, + ...data!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] })) + ); + } // Checking if any of the secret keys start with a number - if so, don't do anything const nameErrors = !Object.keys(obj) @@ -350,13 +386,17 @@ export default function Dashboard() { // Once "Save changed is clicked", disable that button setButtonReady(false); - pushKeys({ obj, workspaceId: String(router.query.id), env }); + console.log(envToPush ? envToPush : env, env, envToPush) + pushKeys({ obj, workspaceId: String(router.query.id), env: envToPush ? envToPush : env }); // If this user has never saved environment variables before, show them a prompt to read docs if (!hasUserEverPushed) { setCheckDocsPopUpVisible(true); await registerUserAction({ action: 'first_time_secrets_pushed' }); } + + // increasing the number of project commits + setNumSnapshots(numSnapshots ?? 0 + 1); }; const addData = (newData: SecretDataProps[]) => { @@ -427,6 +467,11 @@ export default function Dashboard() { setSharedToHide={setSharedToHide} deleteRow={deleteCertainRow} />} + {PITSidebarOpen && }
{checkDocsPopUpVisible && ( @@ -441,9 +486,22 @@ export default function Dashboard() { /> )}
+ {snapshotData && +
+
}
-

{t("dashboard:title")}

- {data?.length == 0 && ( +
+

{snapshotData ? "Secret Snapshot" : t("dashboard:title")}

+ {snapshotData && {new Date(snapshotData.createdAt).toLocaleString()}} +
+ {!snapshotData && data?.length == 0 && (
- {(data?.length !== 0 || buttonReady) && ( -
+
+
+ {(data?.length !== 0 || buttonReady) && !snapshotData && ( +
)} + {snapshotData &&
+
}
- {data?.length !== 0 && ( + {(!snapshotData || data?.length !== 0) && ( <> - + : }
-
+ {!snapshotData &&
-
+
} + {!snapshotData &&
+
}
-
+ {!snapshotData &&
+
} )}
- {data?.length !== 0 ? ( + {isLoading ? ( +
+ infisical loading indicator +
+ ) : ( + data?.length !== 0 ? (
- {data?.filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( + {!snapshotData && data?.filter(row => row.key.toUpperCase().includes(searchKeys.toUpperCase())) + .filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( item.key + item.type))?.includes(keyPair.key + keyPair.type)} toggleSidebar={toggleSidebar} sidebarSecretId={sidebarSecretId} + isSnapshot={false} + /> + ))} + {snapshotData && snapshotData.secretVersions?.sort((a, b) => a.key.localeCompare(b.key)) + .filter(row => reverseEnvMapping[row.environment] == snapshotEnv) + .filter(row => row.key.toUpperCase().includes(searchKeys.toUpperCase())) + .filter(row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions + ?.map((item) => item.key) + .filter( + (item, index) => + index !== + snapshotData.secretVersions?.map((item) => item.key).indexOf(item) + ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id).includes(row.id) && row.type == 'shared')).map((keyPair) => ( + item.key + item.type))?.includes(keyPair.key + keyPair.type)} + toggleSidebar={toggleSidebar} + sidebarSecretId={sidebarSecretId} + isSnapshot={true} /> ))}
-
+ {!snapshotData &&
-
+
}
) : (
- {isKeyAvailable && ( + {isKeyAvailable && !snapshotData && ( ))}
- )} + ))}
diff --git a/frontend/public/locales/en/activity.json b/frontend/public/locales/en/activity.json index b84d570ae..5dff03d1e 100644 --- a/frontend/public/locales/en/activity.json +++ b/frontend/public/locales/en/activity.json @@ -2,6 +2,7 @@ "event": { "readSecrets": "Secrets Viewed", "updateSecrets": "Secrets Updated", - "addSecrets": "Secrets Added" + "addSecrets": "Secrets Added", + "deleteSecrets": "Secrets Deleted" } }