diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx index c9957cc3c..1a5ddaa82 100644 --- a/frontend/components/basic/Toggle.tsx +++ b/frontend/components/basic/Toggle.tsx @@ -1,27 +1,11 @@ import React from "react"; import { Switch } from "@headlessui/react"; - -interface OverrideProps { - id: string; - keyName: string; - value: string; - pos: number; - comment: string; -} - interface ToggleProps { enabled: boolean; setEnabled: (value: boolean) => void; - addOverride: (value: OverrideProps) => void; - keyName: string; - value: string; + addOverride: (value: string | undefined, pos: number) => void; pos: number; - id: string; - comment: string; - deleteOverride: (id: string) => void; - sharedToHide: string[]; - setSharedToHide: (values: string[]) => void; } /** @@ -30,41 +14,23 @@ interface ToggleProps { * @param {boolean} obj.enabled - whether the toggle is turned on or off * @param {function} obj.setEnabled - change the state of the toggle * @param {function} obj.addOverride - a function that adds an override to a certain secret - * @param {string} obj.keyName - key of a certain secret - * @param {string} obj.value - value of a certain secret * @param {number} obj.pos - position of a certain secret - #TODO: make the secret id persistent? - * @param {string} obj.id - id of a certain secret (NOTE: THIS IS THE ID OF THE MAIN SECRET - NOT OF AN OVERRIDE) - * @param {function} obj.deleteOverride - a function that deleted an override for a certain secret - * @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden. - * @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually * @returns */ export default function Toggle ({ enabled, setEnabled, addOverride, - keyName, - value, - pos, - id, - comment, - deleteOverride, - sharedToHide, - setSharedToHide + pos }: ToggleProps): JSX.Element { return ( { if (enabled == false) { - addOverride({ id, keyName, value, pos, comment }); - setSharedToHide([ - ...sharedToHide!, - id - ]) + addOverride('', pos); } else { - deleteOverride(id); + addOverride(undefined, pos); } setEnabled(!enabled); }} diff --git a/frontend/components/basic/buttons/Button.tsx b/frontend/components/basic/buttons/Button.tsx index 939d9b17d..2405abce7 100644 --- a/frontend/components/basic/buttons/Button.tsx +++ b/frontend/components/basic/buttons/Button.tsx @@ -3,7 +3,6 @@ import Image from "next/image"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon, - FontAwesomeIconProps, } from "@fortawesome/react-fontawesome"; const classNames = require("classnames"); @@ -101,7 +100,7 @@ export default function Button(props: ButtonProps): JSX.Element {
void; - value: string; + value: string | undefined; type: 'varName' | 'value'; blurred?: boolean; isDuplicate?: boolean; @@ -47,7 +47,7 @@ const DashboardInputField = ({ }; if (type === 'varName') { - const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != ''; + const startsWithNumber = !isNaN(Number(value?.charAt(0))) && value != ''; const error = startsWithNumber || isDuplicate; return ( @@ -141,7 +141,7 @@ const DashboardInputField = ({ {blurred && (
- {value.split('').map(() => ( + {value?.split('').map(() => ( void; modifyValue: (value: string, position: number) => void; + modifyValueOverride: (value: string, position: number) => void; isBlurred: boolean; isDuplicate: boolean; toggleSidebar: (id: string) => void; @@ -30,6 +23,7 @@ interface KeyPairProps { * @param {String[]} obj.keyPair - data related to the environment variable (id, pos, key, value, public/private) * @param {function} obj.modifyKey - modify the key of a certain environment variable * @param {function} obj.modifyValue - modify the value of a certain environment variable + * @param {function} obj.modifyValueOverride - modify the value of a certain environment variable if it is overriden * @param {boolean} obj.isBlurred - if the blurring setting is turned on * @param {boolean} obj.isDuplicate - list of all the duplicates secret names on the dashboard * @param {function} obj.toggleSidebar - open/close/switch sidebar @@ -41,6 +35,7 @@ const KeyPair = ({ keyPair, modifyKey, modifyValue, + modifyValueOverride, isBlurred, isDuplicate, toggleSidebar, @@ -50,7 +45,7 @@ const KeyPair = ({ return (
- {keyPair.type == "personal" &&
+ {keyPair.valueOverride &&
This secret is overriden @@ -70,12 +65,12 @@ const KeyPair = ({
diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index 33b936f52..32cdf7341 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -16,18 +16,15 @@ import GenerateSecretMenu from './GenerateSecretMenu'; interface SecretProps { key: string; value: string; + valueOverride: string | undefined; pos: number; - type: string; id: string; comment: string; } interface OverrideProps { id: string; - keyName: string; - value: string; - pos: number; - comment: string; + valueOverride: string; } export interface DeleteRowFunctionProps { ids: string[]; @@ -39,9 +36,8 @@ interface SideBarProps { data: SecretProps[]; modifyKey: (value: string, position: number) => void; modifyValue: (value: string, position: number) => void; + modifyValueOverride: (value: string | undefined, position: number) => void; modifyComment: (value: string, position: number) => void; - addOverride: (value: OverrideProps) => void; - deleteOverride: (id: string) => void; buttonReady: boolean; savePush: () => void; sharedToHide: string[]; @@ -55,12 +51,9 @@ interface SideBarProps { * @param {SecretProps[]} obj.data - data of a certain key valeu pair * @param {function} obj.modifyKey - function that modifies the secret key * @param {function} obj.modifyValue - function that modifies the secret value - * @param {function} obj.addOverride - override a certain secret - * @param {function} obj.deleteOverride - delete the personal override for a certain secret + * @param {function} obj.modifyValueOverride - function that modifies the secret value if it is an override * @param {boolean} obj.buttonReady - is the button for saving chagnes active * @param {function} obj.savePush - save changes andp ush secrets - * @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden. - * @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually * @param {function} obj.deleteRow - a function to delete a certain keyPair * @returns the sidebar with 'secret's settings' */ @@ -69,17 +62,14 @@ const SideBar = ({ data, modifyKey, modifyValue, + modifyValueOverride, modifyComment, - addOverride, - deleteOverride, buttonReady, savePush, - sharedToHide, - setSharedToHide, deleteRow }: SideBarProps) => { const [isLoading, setIsLoading] = useState(false); - const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal")); + const [overrideEnabled, setOverrideEnabled] = useState(data[0].valueOverride != undefined); const { t } = useTranslation(); return
@@ -111,19 +101,19 @@ const SideBar = ({ blurred={false} />
- {data.filter(secret => secret.type == "shared")[0]?.value + {data[0]?.value ?

{t("dashboard:sidebar.value")}

secret.type == "shared")[0]?.pos} - value={data.filter(secret => secret.type == "shared")[0]?.value} + position={data[0].pos} + value={data[0]?.value} isDuplicate={false} blurred={true} />
- secret.type == "shared")[0]?.pos} /> +
:
@@ -131,39 +121,32 @@ const SideBar = ({ {t("dashboard:sidebar.personal-explanation")}
}
- {data.filter(secret => secret.type == "shared")[0]?.value && + {data[0]?.value &&

{t("dashboard:sidebar.override")}

}
secret.type == "personal")[0]?.pos : data[0]?.pos} - value={overrideEnabled ? data.filter(secret => secret.type == "personal")[0]?.value : data[0]?.value} + position={data[0]?.pos} + value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value} isDuplicate={false} blurred={true} />
- secret.type == "personal")[0]?.pos : data[0]?.pos} /> +
- secret.type == "shared")[0]?.comment} modifyComment={modifyComment} position={data.filter(secret => secret.type == "shared")[0]?.pos} /> +
)}
@@ -176,7 +159,7 @@ const SideBar = ({ textDisabled="Saved" /> deleteRow({ ids: overrideEnabled ? data.map(secret => secret.id) : [data.filter(secret => secret.type == "shared")[0]?.id], secretName: data[0]?.key })} + onSubmit={() => deleteRow({ ids: data.map(secret => secret.id), secretName: data[0]?.key })} />
diff --git a/frontend/components/utilities/secrets/checkOverrides.ts b/frontend/components/utilities/secrets/checkOverrides.ts index fae465d88..83a779a85 100644 --- a/frontend/components/utilities/secrets/checkOverrides.ts +++ b/frontend/components/utilities/secrets/checkOverrides.ts @@ -1,11 +1,4 @@ -interface SecretDataProps { - type: 'personal' | 'shared'; - pos: number; - key: string; - value: string; - id: string; - comment: string; -} +import { SecretDataProps } from "public/data/frequentInterfaces"; /** * This function downloads the secrets as a .env file @@ -16,16 +9,16 @@ interface SecretDataProps { const checkOverrides = async ({ data }: { data: SecretDataProps[]; }) => { let secrets : SecretDataProps[] = data!.map((secret) => Object.create(secret)); const overridenSecrets = data!.filter( - (secret) => secret.type === 'personal' + (secret) => (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ? 'shared' : 'personal' ); if (overridenSecrets.length) { overridenSecrets.forEach((secret) => { const index = secrets!.findIndex( - (_secret) => _secret.key === secret.key && _secret.type === 'shared' + (_secret) => _secret.key === secret.key && (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ); secrets![index].value = secret.value; }); - secrets = secrets!.filter((secret) => secret.type === 'shared'); + secrets = secrets!.filter((secret) => (secret.valueOverride == undefined || secret?.value != secret?.valueOverride)); } return secrets; } diff --git a/frontend/components/utilities/secrets/downloadDotEnv.ts b/frontend/components/utilities/secrets/downloadDotEnv.ts index dbb29497b..93ac774ba 100644 --- a/frontend/components/utilities/secrets/downloadDotEnv.ts +++ b/frontend/components/utilities/secrets/downloadDotEnv.ts @@ -1,16 +1,9 @@ +import { SecretDataProps } from "public/data/frequentInterfaces"; + import { envMapping } from "../../../public/data/frequentConstants"; import checkOverrides from './checkOverrides'; -interface SecretDataProps { - type: 'personal' | 'shared'; - pos: number; - key: string; - value: string; - id: string; - comment: string; -} - /** * This function downloads the secrets as a .env file * @param {object} obj diff --git a/frontend/components/utilities/secrets/downloadYaml.ts b/frontend/components/utilities/secrets/downloadYaml.ts index 1c17b1410..26fca5f00 100644 --- a/frontend/components/utilities/secrets/downloadYaml.ts +++ b/frontend/components/utilities/secrets/downloadYaml.ts @@ -1,19 +1,12 @@ // import YAML from 'yaml'; // import { YAMLSeq } from 'yaml/types'; +import { SecretDataProps } from "public/data/frequentInterfaces"; + // import { envMapping } from "../../../public/data/frequentConstants"; // import checkOverrides from './checkOverrides'; -interface SecretDataProps { - type: 'personal' | 'shared'; - pos: number; - key: string; - value: string; - id: string; - comment: string; -} - /** * This function downloads the secrets as a .yml file * @param {object} obj diff --git a/frontend/components/utilities/secrets/encryptSecrets.ts b/frontend/components/utilities/secrets/encryptSecrets.ts index 46c3150ef..a0f56990a 100644 --- a/frontend/components/utilities/secrets/encryptSecrets.ts +++ b/frontend/components/utilities/secrets/encryptSecrets.ts @@ -1,3 +1,5 @@ +import { SecretDataProps } from "public/data/frequentInterfaces"; + import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; const crypto = require("crypto"); @@ -9,15 +11,6 @@ const nacl = require("tweetnacl"); nacl.util = require("tweetnacl-util"); -interface SecretDataProps { - type: 'personal' | 'shared'; - pos: number; - key: string; - value: string; - id: string; - comment: string; -} - interface EncryptedSecretProps { id: string; createdAt: string; @@ -106,7 +99,7 @@ const encryptSecrets = async ({ secretsToEncrypt, workspaceId, env }: { secretsT secretCommentCiphertext, secretCommentIV, secretCommentTag, - type: secret.type, + type: (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ? 'shared' : 'personal', }; return result; diff --git a/frontend/components/utilities/secrets/getSecretsForProject.ts b/frontend/components/utilities/secrets/getSecretsForProject.ts index 81de27be0..d2c4ddf54 100644 --- a/frontend/components/utilities/secrets/getSecretsForProject.ts +++ b/frontend/components/utilities/secrets/getSecretsForProject.ts @@ -117,15 +117,19 @@ const getSecretsForProject = async ({ }); } - const result = tempDecryptedSecrets.map((secret, index) => { + const secretKeys = [...new Set(tempDecryptedSecrets.map(secret => secret.key))]; + + + const result = secretKeys.map((key, index) => { return { - id: secret['id'], + id: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.id, + idOverride: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'personal')[0]?.id, pos: index, - key: secret['key'], - value: secret['value'], - type: secret['type'], - comment: secret['comment'] - }; + key: key, + value: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.value, + valueOverride: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'personal')[0]?.value, + comment: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.comment, + } }); setData(result); diff --git a/frontend/ee/components/PITRecoverySidebar.tsx b/frontend/ee/components/PITRecoverySidebar.tsx index 085c483ed..e8a1041cd 100644 --- a/frontend/ee/components/PITRecoverySidebar.tsx +++ b/frontend/ee/components/PITRecoverySidebar.tsx @@ -13,6 +13,15 @@ import { decryptAssymmetric, decryptSymmetric } from "~/components/utilities/cry import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey"; +export interface SecretDataProps { + pos: number; + key: string; + value: string; + type: string; + id: string; + environment: string; +} + interface SideBarProps { toggleSidebar: (value: boolean) => void; setSnapshotData: (value: any) => void; @@ -43,8 +52,6 @@ interface EncrypetedSecretVersionListProps { * @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 = ({ @@ -111,7 +118,21 @@ const PITRecoverySidebar = ({ } }) - setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: decryptedSecretVersions }) + + const secretKeys = [...new Set(decryptedSecretVersions.map((secret: SecretDataProps) => secret.key))]; + + const result = secretKeys.map((key, index) => { + return { + id: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].id, + pos: index, + key: key, + environment: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].environment, + value: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0]?.value, + valueOverride: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'personal')[0]?.value, + } + }); + + setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: result, comment: '' }) } return
@@ -125,31 +146,35 @@ const PITRecoverySidebar = ({ >
) : ( -
+

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

toggleSidebar(false)}>
-
- {secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
-
-
{timeSince(new Date(snapshot.createdAt))}
-
{" - " + snapshot.secretVersions.length + " Secrets"}
-
+
+ {secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
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"} + key={snapshot._id} + onClick={() => exploreSnapshot({ snapshotId: snapshot._id })} + className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "bg-primary text-black pointer-events-none" : "bg-mineshaft-700 hover:bg-mineshaft-500 duration-200 cursor-pointer"} py-3 px-4 mb-2 rounded-md flex flex-row justify-between items-center`} + > +
+
{timeSince(new Date(snapshot.createdAt))}
+
{" - " + snapshot.secretVersions.length + " Secrets"}
+
+
+ {id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"} +
+
)} +
+
+
-
)} -
-
-
-
)}
diff --git a/frontend/ee/components/SecretVersionList.tsx b/frontend/ee/components/SecretVersionList.tsx index 3eb40005f..d07198dc0 100644 --- a/frontend/ee/components/SecretVersionList.tsx +++ b/frontend/ee/components/SecretVersionList.tsx @@ -52,7 +52,7 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => { }); } - const decryptedSecretVersions = encryptedSecretVersions.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => { + const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => { return { createdAt: encryptedSecretVersion.createdAt, value: decryptSymmetric({ @@ -87,28 +87,33 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
) : (
- {secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt)) - .map((version: DecryptedSecretVersionListProps, index: number) => -
-
-
-
-
-
-
- {(new Date(version.createdAt)).toLocaleDateString('en-US', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - })} + {secretVersions + ? secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + .map((version: DecryptedSecretVersionListProps, index: number) => +
+
+
+
+
+
+
+ {(new Date(version.createdAt)).toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + })} +
+

Value:{version.value}

-

Value:{version.value}

-
- )} + ) + : ( +
No version history yet.
+ ) + }
)}
diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 4ce91002d..07b4133a0 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -45,11 +45,12 @@ import getWorkspaces from '../api/workspace/getWorkspaces'; interface SecretDataProps { - type: 'personal' | 'shared'; pos: number; key: string; value: string; + valueOverride: string | undefined; id: string; + idOverride: string | undefined; comment: string; } @@ -68,10 +69,11 @@ interface SnapshotProps { secretVersions: { id: string; pos: number; - type: "personal" | "shared"; environment: string; key: string; value: string; + valueOverride: string; + comment: string; }[]; } @@ -99,7 +101,7 @@ function findDuplicates(arr: any[]) { */ export default function Dashboard() { const [data, setData] = useState(); - const [initialData, setInitialData] = useState([]); + const [initialData, setInitialData] = useState([]); const [buttonReady, setButtonReady] = useState(false); const router = useRouter(); const [workspaceId, setWorkspaceId] = useState(''); @@ -119,6 +121,7 @@ export default function Dashboard() { const [sharedToHide, setSharedToHide] = useState([]); const [snapshotData, setSnapshotData] = useState(); const [numSnapshots, setNumSnapshots] = useState(); + const [saveLoading, setSaveLoading] = useState(false); const { t } = useTranslation(); const { createNotification } = useNotificationContext(); @@ -213,16 +216,6 @@ export default function Dashboard() { setInitialData(dataToSort); reorderRows(dataToSort); - setSharedToHide( - dataToSort?.filter(row => (dataToSort - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - dataToSort?.map((item) => item.key).indexOf(item) - ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id) - ) - setIsLoading(false); } catch (error) { console.log('Error', error); @@ -238,39 +231,16 @@ export default function Dashboard() { ...data!, { id: guidGenerator(), + idOverride: guidGenerator(), pos: data!.length, key: '', value: '', - type: 'shared', + valueOverride: undefined, comment: '', } ]); }; - /** - * This function add an ovverrided version of a certain secret to the current user - * @param {object} obj - * @param {string} obj.id - if of this secret that is about to be overriden - * @param {string} obj.keyName - key name of this secret - * @param {string} obj.value - value of this secret - * @param {string} obj.pos - position of this secret on the dashboard - */ - const addOverride = ({ id, keyName, value, pos, comment }: overrideProps) => { - setIsNew(false); - const tempdata: SecretDataProps[] | 1 = [ - ...data!, - { - id: id, - pos: pos, - key: keyName, - value: value, - type: 'personal', - comment: comment - } - ]; - sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical"); - }; - const deleteRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => { setButtonReady(true); toggleSidebar("None"); @@ -289,15 +259,15 @@ export default function Dashboard() { setButtonReady(true); // find which shared secret corresponds to the overriden version - const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id; + // const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id; // change the sidebar to this shared secret; and unhide it - toggleSidebar(sharedVersionOfOverride) - setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride)) + // toggleSidebar(sharedVersionOfOverride) + // setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride)) // resort secrets - const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal')) - sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical") + // const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal')) + // sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical") }; const modifyValue = (value: string, pos: number) => { @@ -308,6 +278,14 @@ export default function Dashboard() { setButtonReady(true); }; + const modifyValueOverride = (value: string | undefined, pos: number) => { + setData((oldData) => { + oldData![pos].valueOverride = value; + return [...oldData!]; + }); + setButtonReady(true); + }; + const modifyKey = (value: string, pos: number) => { setData((oldData) => { oldData![pos].key = value; @@ -329,6 +307,10 @@ export default function Dashboard() { modifyValue(value, pos); }, []); + const listenChangeValueOverride = useCallback((value: string | undefined, pos: number) => { + modifyValueOverride(value, pos); + }, []); + const listenChangeKey = useCallback((value: string, pos: number) => { modifyKey(value, pos); }, []); @@ -341,6 +323,7 @@ export default function Dashboard() { * Save the changes of environment variables and push them to the database */ const savePush = async (dataToPush?: SecretDataProps[]) => { + setSaveLoading(true); let newData: SecretDataProps[] | null | undefined; // dataToPush is mostly used for rollbacks, otherwise we always take the current state data if ((dataToPush ?? [])?.length > 0) { @@ -349,16 +332,11 @@ export default function Dashboard() { newData = data; } - const obj = Object.assign( - {}, - ...newData!.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) - .map((key) => !isNaN(Number(key[0].charAt(0)))) + const nameErrors = !newData! + .map((secret) => !isNaN(Number(secret.key.charAt(0)))) .every((v) => v === false); - const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key + item.type)).length > 0; + const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0; if (nameErrors) { return createNotification({ @@ -378,34 +356,64 @@ export default function Dashboard() { setButtonReady(false); const secretsToBeDeleted - = initialData + = initialData! .filter(initDataPoint => !newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)) .map(secret => secret.id); + console.log('delete', secretsToBeDeleted.length) const secretsToBeAdded = newData! - .filter(newDataPoint => !initialData.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id)); + .filter(newDataPoint => !initialData!.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id)); + console.log('add', secretsToBeAdded.length) const secretsToBeUpdated - = newData!.filter(newDataPoint => initialData + = newData!.filter(newDataPoint => initialData! .filter(initDataPoint => newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id) && (newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].value != initDataPoint.value || newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key || newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment)) .map(secret => secret.id).includes(newDataPoint.id)); + console.log('update', secretsToBeUpdated.length) + + const newOverrides = newData!.filter(newDataPoint => newDataPoint.valueOverride != undefined) + const initOverrides = initialData!.filter(initDataPoint => initDataPoint.valueOverride != undefined) + + const overridesToBeDeleted + = initOverrides + .filter(initDataPoint => !newOverrides!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)) + .map(secret => String(secret.idOverride)); + console.log('override delete', overridesToBeDeleted.length) + + const overridesToBeAdded + = newOverrides! + .filter(newDataPoint => !initOverrides.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id)) + .map(override => ({pos: override.pos, key: override.key, value: String(override.valueOverride), valueOverride: override.valueOverride, comment: '', id: String(override.idOverride), idOverride: String(override.idOverride)})); + console.log('override add', overridesToBeAdded.length) + + const overridesToBeUpdated + = newOverrides!.filter(newDataPoint => initOverrides + .filter(initDataPoint => newOverrides!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id) + && (newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].valueOverride != initDataPoint.valueOverride + || newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key + || newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment)) + .map(secret => secret.id).includes(newDataPoint.id)) + .map(override => ({pos: override.pos, key: override.key, value: String(override.valueOverride), valueOverride: override.valueOverride, comment: '', id: String(override.idOverride), idOverride: String(override.idOverride)})); + console.log('override update', overridesToBeUpdated.length) - if (secretsToBeDeleted.length > 0) { - await deleteSecrets({ secretIds: secretsToBeDeleted }); + if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) { + await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) }); } - if (secretsToBeAdded.length > 0) { - const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId, env: envMapping[env] }) + if (secretsToBeAdded.concat(overridesToBeAdded).length > 0) { + const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded), workspaceId, env: envMapping[env] }); secrets && await addSecrets({ secrets, env: envMapping[env], workspaceId }); } - if (secretsToBeUpdated.length > 0) { - const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated, workspaceId, env: envMapping[env] }) + if (secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) { + const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated), workspaceId, env: envMapping[env] }); secrets && await updateSecrets({ secrets }); } + setInitialData(newData); + // If this user has never saved environment variables before, show them a prompt to read docs if (!hasUserEverPushed) { setCheckDocsPopUpVisible(true); @@ -414,6 +422,7 @@ export default function Dashboard() { // increasing the number of project commits setNumSnapshots((numSnapshots ?? 0) + 1); + setSaveLoading(false); }; const addData = (newData: SecretDataProps[]) => { @@ -462,9 +471,8 @@ export default function Dashboard() { data={data.filter((row: SecretDataProps) => row.key == data.filter(row => row.id == sidebarSecretId)[0]?.key)} modifyKey={listenChangeKey} modifyValue={listenChangeValue} + modifyValueOverride={listenChangeValueOverride} modifyComment={listenChangeComment} - addOverride={addOverride} - deleteOverride={deleteOverride} buttonReady={buttonReady} savePush={savePush} sharedToHide={sharedToHide} @@ -533,6 +541,7 @@ export default function Dashboard() { active={buttonReady} iconDisabled={faCheck} textDisabled={String(t("common:saved"))} + loading={saveLoading} />
)} @@ -545,21 +554,11 @@ export default function Dashboard() { .filter(row => reverseEnvMapping[row.environment] == env) .map((sv, position) => { return { - id: sv.id, pos: position, type: sv.type, key: sv.key, value: sv.value, comment: '' + id: sv.id, idOverride: sv.id, pos: position, valueOverride: sv.valueOverride, key: sv.key, value: sv.value, comment: '' } }); setData(rolledBackSecrets); - setSharedToHide( - rolledBackSecrets?.filter(row => (rolledBackSecrets - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - rolledBackSecrets?.map((item) => item.key).indexOf(item) - ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id) - ) - // Perform the rollback globally performSecretRollback({ workspaceId, version: snapshotData.version }) @@ -663,16 +662,17 @@ export default function Dashboard() { >
{!snapshotData && data?.filter(row => row.key?.toUpperCase().includes(searchKeys.toUpperCase())) - .filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( + .filter(row => !sharedToHide.includes(row.id)).map((keyPair) => ( item.key + item.type) - )?.includes(keyPair.key + keyPair.type)} + data?.map((item) => item.key) + )?.includes(keyPair.key)} toggleSidebar={toggleSidebar} sidebarSecretId={sidebarSecretId} isSnapshot={false} @@ -681,22 +681,26 @@ export default function Dashboard() { {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 + .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) => ( + ).includes(row.key)))?.map((item) => item.id).includes(row.id)) + ) + .map((keyPair) => ( item.key + item.type) - )?.includes(keyPair.key + keyPair.type)} + data?.map((item) => item.key) + )?.includes(keyPair.key)} toggleSidebar={toggleSidebar} sidebarSecretId={sidebarSecretId} isSnapshot={true} diff --git a/frontend/public/data/frequentInterfaces.ts b/frontend/public/data/frequentInterfaces.ts new file mode 100644 index 000000000..c2fa797fd --- /dev/null +++ b/frontend/public/data/frequentInterfaces.ts @@ -0,0 +1,8 @@ +export interface SecretDataProps { + pos: number; + key: string; + value: string; + valueOverride: string | undefined; + id: string; + comment: string; +} \ No newline at end of file