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 ? (
+
+
+
+ ) : (
+
+
+
{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 ? (
+
+
+
+ ) : (
+ 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}
/>
))}
-
}
) : (
- {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"
}
}