From 092436b6a3e0eab86a9108b5c9b83f092395b8f7 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Thu, 22 Dec 2022 14:15:12 -0500 Subject: [PATCH 01/18] Finished the first ddraft of the dashboard sidebar --- frontend/components/basic/Toggle.tsx | 28 +++++ frontend/components/basic/buttons/Button.tsx | 2 +- .../dashboard/GenerateSecretMenu.tsx | 93 ++++++++++++++ frontend/components/dashboard/SideBar.tsx | 84 +++++++++++++ frontend/pages/dashboard/[id].js | 115 ++---------------- 5 files changed, 216 insertions(+), 106 deletions(-) create mode 100644 frontend/components/basic/Toggle.tsx create mode 100644 frontend/components/dashboard/GenerateSecretMenu.tsx create mode 100644 frontend/components/dashboard/SideBar.tsx diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx new file mode 100644 index 000000000..c2cf3cb07 --- /dev/null +++ b/frontend/components/basic/Toggle.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { Switch } from "@headlessui/react"; + +/** + * This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values) + * @param obj + * @param {boolean} obj.enabled - whether the toggle is turned on or off + * @param {function} obj.setEnabled - change the state of the toggle + * @returns + */ +export default function Toggle ({ enabled, setEnabled }: { enabled: boolean; setEnabled: (value: boolean) => void; }): JSX.Element { + return ( + <Switch + checked={enabled} + onChange={setEnabled} + className={`${ + enabled ? 'bg-primary' : 'bg-bunker-400' + } relative inline-flex h-5 w-9 items-center rounded-full`} + > + <span className="sr-only">Enable notifications</span> + <span + className={`${ + enabled ? 'translate-x-[1.26rem]' : 'translate-x-0.5' + } inline-block h-3.5 w-3.5 transform rounded-full bg-bunker-800 transition`} + /> + </Switch> + ) +} diff --git a/frontend/components/basic/buttons/Button.tsx b/frontend/components/basic/buttons/Button.tsx index 562a82a36..f8bd1941d 100644 --- a/frontend/components/basic/buttons/Button.tsx +++ b/frontend/components/basic/buttons/Button.tsx @@ -80,7 +80,7 @@ export default function Button(props: ButtonProps): JSX.Element { ); const textStyle = classNames( - "relative duration-200", + "relative duration-200 text-center w-full", // Show the loading sign if the loading indicator is on props.loading ? "opacity-0" : "opacity-100", diff --git a/frontend/components/dashboard/GenerateSecretMenu.tsx b/frontend/components/dashboard/GenerateSecretMenu.tsx new file mode 100644 index 000000000..1d71749e5 --- /dev/null +++ b/frontend/components/dashboard/GenerateSecretMenu.tsx @@ -0,0 +1,93 @@ +import { Fragment,useState } from 'react'; +import { faShuffle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Menu, Transition } from '@headlessui/react'; + + +/** + * This is the menu that is used to (re)generate secrets (currently we only have ranom hex, in future we will have more options) + * @returns the popup-menu for randomly generating secrets + */ +const GenerateSecretMenu = () => { + const [randomStringLength, setRandomStringLength] = useState(32); + + return <Menu as="div" className="relative inline-block text-left"> + <div> + <Menu.Button className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"> + <div className='p-1 rounded-md hover:bg-white/5'> + <FontAwesomeIcon icon={faShuffle} className='text-bunker-300'/> + </div> + </Menu.Button> + </div> + <Transition + as={Fragment} + enter="transition ease-out duration-100" + enterFrom="transform opacity-0 scale-95" + enterTo="transform opacity-100 scale-100" + leave="transition ease-in duration-75" + leaveFrom="transform opacity-100 scale-100" + leaveTo="transform opacity-0 scale-95" + > + <Menu.Items className="absolute z-50 drop-shadow-xl right-0 mt-0.5 w-[20rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none px-1 py-1"> + <div + onClick={() => { + if (randomStringLength > 32) { + setRandomStringLength(32); + } else if (randomStringLength < 2) { + setRandomStringLength(2); + } else { + // modifyValue( + // [...Array(randomStringLength)] + // .map(() => Math.floor(Math.random() * 16).toString(16)) + // .join(''), + // keyPair.pos + // ); + } + }} + className="relative flex flex-row justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" + > + <FontAwesomeIcon + className="text-lg pl-1.5 pr-3" + icon={faShuffle} + /> + <div className="text-sm justify-between flex flex-row w-full"> + <p>Generate Random Hex</p> + <p>digits</p> + </div> + </div> + <div className="flex flex-row absolute bottom-[0.4rem] right-[3.3rem] w-16 bg-bunker-800 border border-chicago-700 rounded-md text-bunker-200 "> + <div + className="m-0.5 px-1 cursor-pointer rounded-md hover:bg-chicago-700" + onClick={() => { + if (randomStringLength > 1) { + setRandomStringLength(randomStringLength - 1); + } + }} + > + - + </div> + <input + onChange={(e) => + setRandomStringLength(parseInt(e.target.value)) + } + value={randomStringLength} + className="text-center z-20 peer text-sm bg-transparent w-full outline-none" + spellCheck="false" + /> + <div + className="m-0.5 px-1 pb-0.5 cursor-pointer rounded-md hover:bg-chicago-700" + onClick={() => { + if (randomStringLength < 32) { + setRandomStringLength(randomStringLength + 1); + } + }} + > + + + </div> + </div> + </Menu.Items> + </Transition> + </Menu> +} + +export default GenerateSecretMenu; diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx new file mode 100644 index 000000000..11f756ccb --- /dev/null +++ b/frontend/components/dashboard/SideBar.tsx @@ -0,0 +1,84 @@ +import { useState } from 'react'; +import { faX } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import Button from '../basic/buttons/Button'; +import Toggle from '../basic/Toggle'; +import DashboardInputField from './DashboardInputField'; +import GenerateSecretMenu from './GenerateSecretMenu'; + +/** + * @returns the sidebar with 'secret's settings' + */ +const SideBar = () => { + const [overrideEnabled, setOverrideEnabled] = useState(false) + + return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'> + <div className='h-min'> + <div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center"> + <p className="font-semibold text-lg text-bunker-200">Secret</p> + <FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/> + </div> + <div className='mt-4 px-4 pointer-events-none'> + <p className='text-sm text-bunker-300'>Key</p> + <DashboardInputField + onChangeHandler={() => {}} + type="varName" + position={1} + value={"KeyKeyKey"} + duplicates={[]} + blurred={false} + /> + </div> + <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> + <p className='text-sm text-bunker-300'>Value</p> + <DashboardInputField + onChangeHandler={() => {}} + type="value" + position={1} + value={"ValueValueValue"} + duplicates={[]} + blurred={true} + /> + <div className='absolute right-[1.7rem] top-[1.65rem] z-50'> + <GenerateSecretMenu /> + </div> + </div> + <div className='mt-4 px-4'> + <div className='flex flex-row items-center justify-between my-2 pl-1 pr-2'> + <p className='text-sm text-bunker-300'>Override value with a personal value</p> + <Toggle enabled={overrideEnabled} setEnabled={setOverrideEnabled} /> + </div> + <div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> + <DashboardInputField + onChangeHandler={() => {}} + type="value" + position={1} + value={"ValueValueValue"} + duplicates={[]} + blurred={true} + /> + <div className='absolute right-3 top-[0.3rem] z-50'> + <GenerateSecretMenu /> + </div> + </div> + <div className={`mt-6`}> + <p className='text-sm text-bunker-300'>Comments & notes</p> + <div className='h-32 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 rounded-md text-sm text-bunker-300'> + Leave your comment here... + </div> + </div> + </div> + </div> + <div className='mt-full px-4 mb-[4.7rem]'> + <Button + onButtonPressed={() => console.log('Saved')} + text="Save Changes" + color="primary" + size="md" + /> + </div> + </div> +}; + +export default SideBar; diff --git a/frontend/pages/dashboard/[id].js b/frontend/pages/dashboard/[id].js index 36f5eb3f2..db1f4a746 100644 --- a/frontend/pages/dashboard/[id].js +++ b/frontend/pages/dashboard/[id].js @@ -1,4 +1,4 @@ -import React, { Fragment, useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import { useRouter } from 'next/router'; @@ -14,14 +14,10 @@ import { faEyeSlash, faFolderOpen, faMagnifyingGlass, - faPeopleGroup, - faPerson, faPlus, - faShuffle, faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Menu, Transition } from '@headlessui/react'; import Button from '~/components/basic/buttons/Button'; import ListBox from '~/components/basic/Listbox'; @@ -29,14 +25,13 @@ import BottonRightPopup from '~/components/basic/popups/BottomRightPopup'; import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider'; import DashboardInputField from '~/components/dashboard/DashboardInputField'; import DropZone from '~/components/dashboard/DropZone'; +import SideBar from '~/components/dashboard/Sidebar'; import NavHeader from '~/components/navigation/NavHeader'; import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject'; import pushKeys from '~/components/utilities/secrets/pushKeys'; -import pushKeysIntegration from '~/components/utilities/secrets/pushKeysIntegration'; import guidGenerator from '~/utilities/randomId'; import { envMapping } from '../../public/data/frequentConstants'; -import getWorkspaceIntegrations from '../api/integrations/getWorkspaceIntegrations'; import getUser from '../api/user/getUser'; import checkUserAction from '../api/userActions/checkUserAction'; import registerUserAction from '../api/userActions/registerUserAction'; @@ -63,7 +58,6 @@ const KeyPair = ({ isBlurred, duplicates }) => { - const [randomStringLength, setRandomStringLength] = useState(32); return ( <div className="px-1 flex flex-col items-center ml-1"> @@ -90,103 +84,12 @@ const KeyPair = ({ /> </div> </div> - <Menu as="div" className="relative inline-block text-left"> - <div> - <Menu.Button className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"> - <div className="cursor-pointer w-9 h-9 bg-white/10 rounded-md flex flex-row justify-center items-center opacity-50 hover:opacity-100 duration-200"> - <FontAwesomeIcon - className="text-gray-300 px-2.5 text-lg mt-0.5" - icon={faEllipsis} - /> - </div> - </Menu.Button> - </div> - <Transition - as={Fragment} - enter="transition ease-out duration-100" - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leave="transition ease-in duration-75" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - > - <Menu.Items className="absolute z-50 drop-shadow-xl right-0 mt-0.5 w-[20rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none px-1 py-1"> - <div - onClick={() => - modifyVisibility( - keyPair.type == 'personal' ? 'shared' : 'personal', - keyPair.pos - ) - } - className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" - > - <FontAwesomeIcon - className="text-lg pl-1.5 pr-3" - icon={keyPair.type == 'personal' ? faPeopleGroup : faPerson} - /> - <div className="text-sm"> - {keyPair.type == 'personal' ? 'Make Shared' : 'Make Personal'} - </div> - </div> - <div - onClick={() => { - if (randomStringLength > 32) { - setRandomStringLength(32); - } else if (randomStringLength < 2) { - setRandomStringLength(2); - } else { - modifyValue( - [...Array(randomStringLength)] - .map(() => Math.floor(Math.random() * 16).toString(16)) - .join(''), - keyPair.pos - ); - } - }} - className="relative flex flex-row justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" - > - <FontAwesomeIcon - className="text-lg pl-1.5 pr-3" - icon={keyPair.value == '' ? faPlus : faShuffle} - /> - <div className="text-sm justify-between flex flex-row w-full"> - <p>Generate Random Hex</p> - <p>digits</p> - </div> - </div> - <div className="flex flex-row absolute bottom-[0.4rem] right-[3.3rem] w-16 bg-bunker-800 border border-chicago-700 rounded-md text-bunker-200 "> - <div - className="m-0.5 px-1 cursor-pointer rounded-md hover:bg-chicago-700" - onClick={() => { - if (randomStringLength > 1) { - setRandomStringLength(randomStringLength - 1); - } - }} - > - - - </div> - <input - onChange={(e) => - setRandomStringLength(parseInt(e.target.value)) - } - value={randomStringLength} - className="text-center z-20 peer text-sm bg-transparent w-full outline-none" - spellCheck="false" - /> - <div - className="m-0.5 px-1 pb-0.5 cursor-pointer rounded-md hover:bg-chicago-700" - onClick={() => { - if (randomStringLength < 32) { - setRandomStringLength(randomStringLength + 1); - } - }} - > - + - </div> - </div> - </Menu.Items> - </Transition> - </Menu> + <div className="cursor-pointer w-9 h-9 bg-white/10 rounded-md flex flex-row justify-center items-center opacity-50 hover:opacity-100 duration-200"> + <FontAwesomeIcon + className="text-gray-300 px-2.5 text-lg mt-0.5" + icon={faEllipsis} + /> + </div> <div className="w-2"></div> <div className="opacity-50 hover:opacity-100 duration-200"> <Button @@ -227,6 +130,7 @@ export default function Dashboard() { const [sortMethod, setSortMethod] = useState('alphabetical'); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false); + const [sidebarOpen, toggleSidebar] = useState(false); const { createNotification } = useNotificationContext(); @@ -491,6 +395,7 @@ export default function Dashboard() { /> </Head> <div className="flex flex-row"> + {!sidebarOpen && <SideBar />} <div className="w-full max-h-96 pb-2"> <NavHeader pageName="Secrets" isProjectRelated={true} /> {checkDocsPopUpVisible && ( From 87a9a587b94771ba0b703385b1174b3484203be9 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Thu, 22 Dec 2022 21:42:24 -0500 Subject: [PATCH 02/18] Added state management to the dashboard sidebar --- frontend/components/basic/buttons/Button.tsx | 5 +- .../dashboard/GenerateSecretMenu.tsx | 2 +- frontend/components/dashboard/SideBar.tsx | 70 ++++++++++++++----- frontend/pages/dashboard/[id].js | 54 ++++++++------ 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/frontend/components/basic/buttons/Button.tsx b/frontend/components/basic/buttons/Button.tsx index f8bd1941d..6501c5244 100644 --- a/frontend/components/basic/buttons/Button.tsx +++ b/frontend/components/basic/buttons/Button.tsx @@ -72,9 +72,10 @@ export default function Button(props: ButtonProps): JSX.Element { // Setting the text color for the text and icon props.color == "mineshaft" && "text-gray-400", - props.color != "mineshaft" && props.color != "red" && "text-black", + props.color != "mineshaft" && props.color != "red" && props.color != "none" && "text-black", props.color == "red" && "text-gray-200", - activityStatus && props.color != "red" ? "group-hover:text-black" : "", + props.color == "none" && "text-gray-200 text-xl", + activityStatus && props.color != "red" && props.color != "none" ? "group-hover:text-black" : "", props.size == "icon" && "flex items-center justify-center" ); diff --git a/frontend/components/dashboard/GenerateSecretMenu.tsx b/frontend/components/dashboard/GenerateSecretMenu.tsx index 1d71749e5..0d1001733 100644 --- a/frontend/components/dashboard/GenerateSecretMenu.tsx +++ b/frontend/components/dashboard/GenerateSecretMenu.tsx @@ -14,7 +14,7 @@ const GenerateSecretMenu = () => { return <Menu as="div" className="relative inline-block text-left"> <div> <Menu.Button className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"> - <div className='p-1 rounded-md hover:bg-white/5'> + <div className='py-1 px-2 rounded-md bg-bunker-800 hover:bg-bunker-500'> <FontAwesomeIcon icon={faShuffle} className='text-bunker-300'/> </div> </Menu.Button> diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index 11f756ccb..5e77e7d75 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -3,29 +3,54 @@ import { faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Button from '../basic/buttons/Button'; +import ListBox from '../basic/Listbox'; import Toggle from '../basic/Toggle'; import DashboardInputField from './DashboardInputField'; import GenerateSecretMenu from './GenerateSecretMenu'; + +interface SecretProps { + key: string; + value: string; + pos: number; + visibility: string; +} + +interface SideBarProps { + toggleSidebar: (value: number) => void; + data: SecretProps[]; + modifyKey: (value: string) => void; + modifyValue: (value: string) => void; + modifyVisibility: (value: string) => void; +} + /** + * @param {object} obj + * @param {function} obj.toggleSidebar - function that opens or closes the sidebar + * @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.modifyVisibility - function that modifies the secret visibility * @returns the sidebar with 'secret's settings' */ -const SideBar = () => { - const [overrideEnabled, setOverrideEnabled] = useState(false) +const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility }: SideBarProps) => { + const [overrideEnabled, setOverrideEnabled] = useState(false); return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'> <div className='h-min'> <div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center"> <p className="font-semibold text-lg text-bunker-200">Secret</p> - <FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/> + <div className='p-1' onClick={() => toggleSidebar(-1)}> + <FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/> + </div> </div> - <div className='mt-4 px-4 pointer-events-none'> + <div className='mt-4 px-4'> <p className='text-sm text-bunker-300'>Key</p> <DashboardInputField - onChangeHandler={() => {}} + onChangeHandler={modifyKey} type="varName" - position={1} - value={"KeyKeyKey"} + position={data[0].pos} + value={data[0].key} duplicates={[]} blurred={false} /> @@ -33,14 +58,14 @@ const SideBar = () => { <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> <p className='text-sm text-bunker-300'>Value</p> <DashboardInputField - onChangeHandler={() => {}} + onChangeHandler={modifyValue} type="value" - position={1} - value={"ValueValueValue"} + position={data[0].pos} + value={data[0].value} duplicates={[]} blurred={true} /> - <div className='absolute right-[1.7rem] top-[1.65rem] z-50'> + <div className='absolute bg-bunker-800 right-[1rem] top-[1.65rem] z-50'> <GenerateSecretMenu /> </div> </div> @@ -51,9 +76,9 @@ const SideBar = () => { </div> <div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> <DashboardInputField - onChangeHandler={() => {}} + onChangeHandler={modifyValue} type="value" - position={1} + position={data[0].pos} value={"ValueValueValue"} duplicates={[]} blurred={true} @@ -62,11 +87,20 @@ const SideBar = () => { <GenerateSecretMenu /> </div> </div> - <div className={`mt-6`}> - <p className='text-sm text-bunker-300'>Comments & notes</p> - <div className='h-32 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 rounded-md text-sm text-bunker-300'> - Leave your comment here... - </div> + </div> + <div className={`relative mt-4 px-4 opacity-80 duration-200`}> + <p className='text-sm text-bunker-200'>Group</p> + <ListBox + selected={"Database Secrets"} + onChange={() => {}} + data={["Group1"]} + isFull={true} + /> + </div> + <div className={`mt-4 px-4`}> + <p className='text-sm text-bunker-300'>Comments & notes</p> + <div className='h-32 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 rounded-md text-sm text-bunker-300'> + Leave your comment here... </div> </div> </div> diff --git a/frontend/pages/dashboard/[id].js b/frontend/pages/dashboard/[id].js index db1f4a746..ef4db42bd 100644 --- a/frontend/pages/dashboard/[id].js +++ b/frontend/pages/dashboard/[id].js @@ -56,14 +56,16 @@ const KeyPair = ({ modifyValue, modifyVisibility, isBlurred, - duplicates + duplicates, + toggleSidebar, + sidebarSecretNumber }) => { return ( - <div className="px-1 flex flex-col items-center ml-1"> - <div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-14 my-1 items-start px-2"> + <div className={`mx-1 flex flex-col items-center ml-1 ${keyPair.pos == sidebarSecretNumber && "bg-mineshaft-500 duration-200"} rounded-md`}> + <div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-14 my-1 items-start px-1"> <div className="min-w-xl w-96"> - <div className="flex items-center md:px-1 rounded-lg mt-4 md:mt-0 max-h-16"> + <div className="flex pr-1 items-center rounded-lg mt-4 md:mt-0 max-h-16"> <DashboardInputField onChangeHandler={modifyKey} type="varName" @@ -84,17 +86,17 @@ const KeyPair = ({ /> </div> </div> - <div className="cursor-pointer w-9 h-9 bg-white/10 rounded-md flex flex-row justify-center items-center opacity-50 hover:opacity-100 duration-200"> + <div onClick={() => toggleSidebar(keyPair.pos)} className="cursor-pointer w-9 h-9 bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> <FontAwesomeIcon className="text-gray-300 px-2.5 text-lg mt-0.5" icon={faEllipsis} /> </div> <div className="w-2"></div> - <div className="opacity-50 hover:opacity-100 duration-200"> + <div className="bg-[#9B3535] hover:bg-red rounded-md duration-200"> <Button onButtonPressed={() => deleteRow(keyPair.id)} - color="red" + color="none" size="icon-sm" icon={faX} /> @@ -130,7 +132,7 @@ export default function Dashboard() { const [sortMethod, setSortMethod] = useState('alphabetical'); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false); - const [sidebarOpen, toggleSidebar] = useState(false); + const [sidebarSecretNumber, toggleSidebar] = useState(-1); const { createNotification } = useNotificationContext(); @@ -335,17 +337,17 @@ export default function Dashboard() { const sortValuesHandler = (dataToSort) => { const sortedData = (dataToSort != 1 ? dataToSort : data) - .sort((a, b) => - sortMethod == 'alphabetical' - ? a.key.localeCompare(b.key) - : b.key.localeCompare(a.key) - ) - .map((item, index) => { - return { - ...item, - pos: index - }; - }); + .sort((a, b) => + sortMethod == 'alphabetical' + ? a.key.localeCompare(b.key) + : b.key.localeCompare(a.key) + ) + .map((item, index) => { + return { + ...item, + pos: index + }; + }); setData(sortedData); }; @@ -395,7 +397,13 @@ export default function Dashboard() { /> </Head> <div className="flex flex-row"> - {!sidebarOpen && <SideBar />} + {sidebarSecretNumber != -1 && <SideBar + toggleSidebar={toggleSidebar} + data={data.filter(row => row.pos == sidebarSecretNumber)} + modifyKey={listenChangeKey} + modifyValue={listenChangeValue} + modifyVisibility={listenChangeVisibility} + />} <div className="w-full max-h-96 pb-2"> <NavHeader pageName="Secrets" isProjectRelated={true} /> {checkDocsPopUpVisible && ( @@ -557,7 +565,7 @@ export default function Dashboard() { </div> </div> </div> - <div id="data1" className=""> + <div id="data1" className="px-1"> {data .filter( (keyPair) => @@ -582,6 +590,8 @@ export default function Dashboard() { index !== data?.map((item) => item.key).indexOf(item) )} + toggleSidebar={toggleSidebar} + sidebarSecretNumber={sidebarSecretNumber} /> ))} </div> @@ -631,6 +641,8 @@ export default function Dashboard() { index !== data?.map((item) => item.key).indexOf(item) )} + toggleSidebar={toggleSidebar} + sidebarSecretNumber={sidebarSecretNumber} /> ))} </div> From 205bf70861e7f4311ef761caba076ca0f6108a14 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Fri, 23 Dec 2022 23:00:26 -0500 Subject: [PATCH 03/18] Added overrides for secrets --- frontend/components/basic/Toggle.tsx | 31 +++- .../dashboard/DashboardInputField.tsx | 12 +- frontend/components/dashboard/SideBar.tsx | 90 ++++++++-- frontend/pages/dashboard/[id].js | 167 +++++++----------- 4 files changed, 183 insertions(+), 117 deletions(-) diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx index c2cf3cb07..6a32d588a 100644 --- a/frontend/components/basic/Toggle.tsx +++ b/frontend/components/basic/Toggle.tsx @@ -1,6 +1,25 @@ import React from "react"; import { Switch } from "@headlessui/react"; + +interface OverrideProps { + id: string; + keyName: string; + value: string; + pos: number; +} + +interface ToggleProps { + enabled: boolean; + setEnabled: (value: boolean) => void; + addOverride: (value: OverrideProps) => void; + keyName: string; + value: string; + pos: number; + id: string; + deleteOverride: (id: string) => void; +} + /** * This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values) * @param obj @@ -8,11 +27,19 @@ import { Switch } from "@headlessui/react"; * @param {function} obj.setEnabled - change the state of the toggle * @returns */ -export default function Toggle ({ enabled, setEnabled }: { enabled: boolean; setEnabled: (value: boolean) => void; }): JSX.Element { +export default function Toggle ({ enabled, setEnabled, addOverride, keyName, value, pos, id, deleteOverride }: ToggleProps): JSX.Element { + console.log(755, pos, enabled) return ( <Switch checked={enabled} - onChange={setEnabled} + onChange={() => { + if (enabled == false) { + addOverride({ id, keyName, value, pos }); + } else { + deleteOverride(id); + } + setEnabled(!enabled); + }} className={`${ enabled ? 'bg-primary' : 'bg-bunker-400' } relative inline-flex h-5 w-9 items-center rounded-full`} diff --git a/frontend/components/dashboard/DashboardInputField.tsx b/frontend/components/dashboard/DashboardInputField.tsx index cb75dcf8c..3a5cd80ee 100644 --- a/frontend/components/dashboard/DashboardInputField.tsx +++ b/frontend/components/dashboard/DashboardInputField.tsx @@ -13,6 +13,7 @@ interface DashboardInputFieldProps { type: 'varName' | 'value'; blurred: boolean; duplicates: string[]; + override?: boolean; } /** @@ -33,7 +34,8 @@ const DashboardInputField = ({ type, value, blurred, - duplicates + duplicates, + override }: DashboardInputFieldProps) => { const ref = useRef<HTMLDivElement | null>(null); const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => { @@ -85,6 +87,7 @@ const DashboardInputField = ({ <div className={`group relative whitespace-pre flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`} > + {override == true && <div className='bg-yellow-300 absolute top-[0.1rem] right-[0.1rem] z-10 w-min text-xxs px-1 text-black opacity-80 rounded-sm'>Override enabled</div>} <input value={value} onChange={(e) => onChangeHandler(e.target.value, position)} @@ -99,10 +102,13 @@ const DashboardInputField = ({ <div ref={ref} className={`${ - blurred + blurred && !override ? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400' : '' - } absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} + } ${ + override ? 'text-yellow-300' : 'text-gray-400' + } + absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} > {value.split(REGEX).map((word, id) => { if (word.match(REGEX) !== null) { diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index 5e77e7d75..38f575c6b 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { faX } from '@fortawesome/free-solid-svg-icons'; +import { faBackward, faDotCircle, faRotateLeft, faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Button from '../basic/buttons/Button'; @@ -13,7 +13,14 @@ interface SecretProps { key: string; value: string; pos: number; - visibility: string; + id: string; +} + +interface OverrideProps { + id: string; + keyName: string; + value: string; + pos: number; } interface SideBarProps { @@ -22,6 +29,8 @@ interface SideBarProps { modifyKey: (value: string) => void; modifyValue: (value: string) => void; modifyVisibility: (value: string) => void; + addOverride: (value: OverrideProps) => void; + deleteOverride: (id: string) => void; } /** @@ -33,7 +42,7 @@ interface SideBarProps { * @param {function} obj.modifyVisibility - function that modifies the secret visibility * @returns the sidebar with 'secret's settings' */ -const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility }: SideBarProps) => { +const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility, addOverride, deleteOverride }: SideBarProps) => { const [overrideEnabled, setOverrideEnabled] = useState(false); return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'> @@ -44,7 +53,7 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility <FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/> </div> </div> - <div className='mt-4 px-4'> + <div className='mt-4 px-4 pointer-events-none'> <p className='text-sm text-bunker-300'>Key</p> <DashboardInputField onChangeHandler={modifyKey} @@ -61,25 +70,34 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility onChangeHandler={modifyValue} type="value" position={data[0].pos} - value={data[0].value} + value={data[0].value + "xxx" + data[0].pos} duplicates={[]} - blurred={true} + blurred={true} /> - <div className='absolute bg-bunker-800 right-[1rem] top-[1.65rem] z-50'> + <div className='absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50'> <GenerateSecretMenu /> </div> </div> <div className='mt-4 px-4'> <div className='flex flex-row items-center justify-between my-2 pl-1 pr-2'> <p className='text-sm text-bunker-300'>Override value with a personal value</p> - <Toggle enabled={overrideEnabled} setEnabled={setOverrideEnabled} /> + <Toggle + enabled={overrideEnabled} + setEnabled={setOverrideEnabled} + addOverride={addOverride} + keyName={data[0].key} + value={data[0].value} + pos={data[0].pos} + id={data[0].id} + deleteOverride={deleteOverride} + /> </div> <div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> <DashboardInputField onChangeHandler={modifyValue} type="value" position={data[0].pos} - value={"ValueValueValue"} + value={"ValueValueValue" + data[0].pos} duplicates={[]} blurred={true} /> @@ -97,9 +115,57 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility isFull={true} /> </div> - <div className={`mt-4 px-4`}> - <p className='text-sm text-bunker-300'>Comments & notes</p> - <div className='h-32 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 rounded-md text-sm text-bunker-300'> + <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'> + <p className=''>Version History</p> + <div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none'> + <div className='h-48 overflow-y-scroll overflow-x-none'> + <div className='flex flex-row'> + <div className='pr-1 flex flex-col items-center'> + <div className='p-1'><FontAwesomeIcon icon={faDotCircle} /></div> + <div className='w-0 h-full border-l mt-1'></div> + </div> + <div className='flex flex-col w-full max-w-[calc(100%-2.3rem)]'> + <div className='pr-2 pt-1'>Current</div> + <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Key:</span>{data[0].key}</p></div> + <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Value:</span>{data[0].value}</p></div> + <div className='pb-1'><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Visibility:</span>{'shared'}</p></div> + </div> + </div> + <div className='flex flex-row'> + <div className='pr-1 flex flex-col items-center'> + <div className='cursor-pointer p-1 hover:bg-bunker-500 rounded-md'><FontAwesomeIcon icon={faRotateLeft} /></div> + <div className='w-0 h-full border-l'></div> + </div> + <div className='flex flex-col max-w-[calc(100%-2.3rem)]'> + <div className='pr-2 pt-1'>12/22/2022 12:36 EST</div> + <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Key:</span> KeyKeyKey</div> + <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Value:</span> ValueValueValue</div> + <div className='pb-1'><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Visibility:</span>{'shared'}</p></div> + </div> + </div> + <div className='flex flex-row'> + <div className='pr-1 flex flex-col items-center'> + <div className='cursor-pointer p-1 hover:bg-bunker-500 rounded-md'><FontAwesomeIcon icon={faRotateLeft} /></div> + <div className='w-0 h-full border-l'></div> + </div> + <div className='flex flex-col max-w-[calc(100%-2.3rem)]'> + <div className='pr-2 pt-1'>12/21/2022 09:11 EST</div> + <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Key:</span> KeyKey</div> + <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Value:</span> ValueValue</div> + <div className='pb-1'><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Visibility:</span>{'shared'}</p></div> + </div> + </div> + </div> + </div> + </div> + <div className={`relative mt-4 px-4 pt-4`}> + <div className='flex flex-row justify-between'> + <p className='text-sm text-bunker-300'>Comments & notes</p> + <div className="bg-yellow rounded-md h-min"> + <p className="relative text-black text-xs px-1.5 h-min">Coming soon!</p> + </div> + </div> + <div className='h-32 opacity-50 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 rounded-md text-sm text-bunker-300'> Leave your comment here... </div> </div> diff --git a/frontend/pages/dashboard/[id].js b/frontend/pages/dashboard/[id].js index ef4db42bd..11519578e 100644 --- a/frontend/pages/dashboard/[id].js +++ b/frontend/pages/dashboard/[id].js @@ -6,7 +6,6 @@ import { faArrowDownAZ, faArrowDownZA, faCheck, - faCircleInfo, faCopy, faDownload, faEllipsis, @@ -83,6 +82,7 @@ const KeyPair = ({ position={keyPair.pos} value={keyPair.value} blurred={isBlurred} + override={keyPair.value == "user1234" && true} /> </div> </div> @@ -176,7 +176,7 @@ export default function Dashboard() { prevSort == 'alphabetical' ? '-alphabetical' : 'alphabetical' ); - sortValuesHandler(dataToReorder); + sortValuesHandler(dataToReorder, ""); }; useEffect(() => { @@ -238,11 +238,43 @@ export default function Dashboard() { ]); }; + /** + * 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 }) => { + setIsNew(false); + const tempdata = [ + ...data, + { + id: id, + pos: pos, + key: keyName, + value: value, + type: 'personal' + } + ]; + sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical"); + }; + const deleteRow = (id) => { setButtonReady(true); setData(data.filter((row) => row.id !== id)); }; + /** + * This function deleted the override of a certain secrer + * @param {string} id - id of a secret to be deleted + */ + const deleteOverride = (id) => { + setButtonReady(true); + setData(data.filter((row) => !(row.id == id && row.type == 'personal'))); + }; + const modifyValue = (value, pos) => { setData((oldData) => { oldData[pos].value = value; @@ -335,10 +367,11 @@ export default function Dashboard() { setBlurred(!blurred); }; - const sortValuesHandler = (dataToSort) => { + const sortValuesHandler = (dataToSort, specificSortMethod) => { + const howToSort = specificSortMethod != "" ? specificSortMethod : sortMethod const sortedData = (dataToSort != 1 ? dataToSort : data) .sort((a, b) => - sortMethod == 'alphabetical' + howToSort == 'alphabetical' ? a.key.localeCompare(b.key) : b.key.localeCompare(a.key) ) @@ -397,12 +430,14 @@ export default function Dashboard() { /> </Head> <div className="flex flex-row"> - {sidebarSecretNumber != -1 && <SideBar + {sidebarSecretNumber != -1 && <SideBar toggleSidebar={toggleSidebar} data={data.filter(row => row.pos == sidebarSecretNumber)} modifyKey={listenChangeKey} modifyValue={listenChangeValue} modifyVisibility={listenChangeVisibility} + addOverride={addOverride} + deleteOverride={deleteOverride} />} <div className="w-full max-h-96 pb-2"> <NavHeader pageName="Secrets" isProjectRelated={true} /> @@ -550,101 +585,33 @@ export default function Dashboard() { <div className={`bg-white/5 mt-1 mb-1 rounded-md pb-2 max-w-5xl overflow-visible`} > - <div className="rounded-t-md sticky top-0 z-20 bg-bunker flex flex-row pl-4 pr-6 pt-4 pb-2 items-center justify-between text-gray-300 font-bold"> - {/* <FontAwesomeIcon icon={faAngleDown} /> */} - <div className="flex flex-row items-center"> - <p className="pl-2 text-lg">Personal</p> - <div className="group font-normal group relative inline-block text-gray-300 underline hover:text-primary duration-200"> - <FontAwesomeIcon - className="ml-3 mt-1 text-lg" - icon={faCircleInfo} - /> - <span className="absolute hidden group-hover:flex group-hover:animate-popdown duration-300 w-44 -left-16 -top-7 translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-1/2 after:bottom-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-gray-700"> - Personal keys are only visible to you - </span> - </div> - </div> - </div> - <div id="data1" className="px-1"> - {data - .filter( - (keyPair) => - keyPair.key - .toLowerCase() - .includes(searchKeys.toLowerCase()) && - keyPair.type == 'personal' - ) - ?.map((keyPair) => ( - <KeyPair - key={keyPair.id} - keyPair={keyPair} - deleteRow={deleteCertainRow} - modifyValue={listenChangeValue} - modifyKey={listenChangeKey} - modifyVisibility={listenChangeVisibility} - isBlurred={blurred} - duplicates={data - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - data?.map((item) => item.key).indexOf(item) - )} - toggleSidebar={toggleSidebar} - sidebarSecretNumber={sidebarSecretNumber} - /> - ))} - </div> - </div> - <div - className={`bg-white/5 mt-1 mb-2 rounded-md p-1 pb-2 max-w-5xl ${ - data?.length > 8 ? 'h-3/4' : 'h-min' - }`} - > - <div className="sticky top-0 z-40 bg-bunker flex flex-row pl-4 pr-5 pt-4 pb-2 items-center justify-between text-gray-300 font-bold"> - {/* <FontAwesomeIcon icon={faAngleDown} /> */} - <div className="flex flex-row items-center"> - <p className="pl-2 text-lg">Shared</p> - <div className="group font-normal group relative inline-block text-gray-300 underline hover:text-primary duration-200"> - <FontAwesomeIcon - className="ml-3 text-lg mt-1" - icon={faCircleInfo} - /> - <span className="absolute hidden group-hover:flex group-hover:animate-popdown duration-300 w-44 -left-16 -top-7 translate-y-full px-2 py-2 bg-gray-700 rounded-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-1/2 after:bottom-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-gray-700"> - Shared keys are visible to your whole team - </span> - </div> - </div> - </div> - <div id="data2" className="data2"> - {data - .filter( - (keyPair) => - keyPair.key - .toLowerCase() - .includes(searchKeys.toLowerCase()) && - keyPair.type == 'shared' - ) - ?.map((keyPair) => ( - <KeyPair - key={keyPair.id} - keyPair={keyPair} - deleteRow={deleteCertainRow} - modifyValue={listenChangeValue} - modifyKey={listenChangeKey} - modifyVisibility={listenChangeVisibility} - isBlurred={blurred} - duplicates={data - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - data?.map((item) => item.key).indexOf(item) - )} - toggleSidebar={toggleSidebar} - sidebarSecretNumber={sidebarSecretNumber} - /> - ))} + <div id="data1" className="px-1 pt-2"> + {data?.filter(row => !(data + ?.map((item) => item.key) + .filter( + (item, index) => + index !== + data?.map((item) => item.key).indexOf(item) + ).includes(row.key) && row.type == 'shared')).map((keyPair) => ( + <KeyPair + key={keyPair.id} + keyPair={keyPair} + deleteRow={deleteCertainRow} + modifyValue={listenChangeValue} + modifyKey={listenChangeKey} + modifyVisibility={listenChangeVisibility} + isBlurred={blurred} + duplicates={data + ?.map((item) => item.key) + .filter( + (item, index) => + index !== + data?.map((item) => item.key).indexOf(item) + )} + toggleSidebar={toggleSidebar} + sidebarSecretNumber={sidebarSecretNumber} + /> + ))} </div> </div> <div className="w-full max-w-5xl"> From d89af29070589eec8425998b6e847322b68eff3d Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Sun, 25 Dec 2022 00:33:37 -0500 Subject: [PATCH 04/18] Refactored dashboard to TS - still some bugs and inefficiencies --- frontend/components/basic/Toggle.tsx | 1 - frontend/components/basic/buttons/Button.tsx | 2 +- ...ttomRightPopup.js => BottomRightPopup.tsx} | 22 +- .../dashboard/DashboardInputField.tsx | 4 +- .../dashboard/GenerateSecretMenu.tsx | 14 +- frontend/components/dashboard/SideBar.tsx | 60 +++-- .../utilities/secrets/getSecretsForProject.ts | 4 +- .../components/utilities/secrets/pushKeys.ts | 6 +- .../pages/dashboard/{[id].js => [id].tsx} | 249 ++++++++++-------- 9 files changed, 210 insertions(+), 152 deletions(-) rename frontend/components/basic/popups/{BottomRightPopup.js => BottomRightPopup.tsx} (71%) rename frontend/pages/dashboard/{[id].js => [id].tsx} (76%) diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx index 6a32d588a..7702bb29e 100644 --- a/frontend/components/basic/Toggle.tsx +++ b/frontend/components/basic/Toggle.tsx @@ -28,7 +28,6 @@ interface ToggleProps { * @returns */ export default function Toggle ({ enabled, setEnabled, addOverride, keyName, value, pos, id, deleteOverride }: ToggleProps): JSX.Element { - console.log(755, pos, enabled) return ( <Switch checked={enabled} diff --git a/frontend/components/basic/buttons/Button.tsx b/frontend/components/basic/buttons/Button.tsx index 6501c5244..6c5b5561a 100644 --- a/frontend/components/basic/buttons/Button.tsx +++ b/frontend/components/basic/buttons/Button.tsx @@ -16,7 +16,7 @@ type ButtonProps = { size: string; icon?: IconProp; active?: boolean; - iconDisabled?: string; + iconDisabled?: IconProp; textDisabled?: string; }; diff --git a/frontend/components/basic/popups/BottomRightPopup.js b/frontend/components/basic/popups/BottomRightPopup.tsx similarity index 71% rename from frontend/components/basic/popups/BottomRightPopup.js rename to frontend/components/basic/popups/BottomRightPopup.tsx index 85aaba725..e6ea60f92 100644 --- a/frontend/components/basic/popups/BottomRightPopup.js +++ b/frontend/components/basic/popups/BottomRightPopup.tsx @@ -3,6 +3,16 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +interface PopupProps { + buttonText: string; + buttonLink: string; + titleText: string; + emoji: string; + textLine1: string; + textLine2: string; + setCheckDocsPopUpVisible: (value: boolean) => void; +} + /** * This is the notification that pops up at the bottom right when a user performs a certain action * @param {object} org @@ -23,16 +33,16 @@ export default function BottonRightPopup({ textLine1, textLine2, setCheckDocsPopUpVisible, -}) { +}: PopupProps): JSX.Element { return ( <div - class="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-xl absolute bottom-0 right-0 mr-6 mb-6" + className="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-xl absolute bottom-0 right-0 mr-6 mb-6" role="alert" > <div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6"> <div className="font-bold text-xl mr-2 mt-0.5 flex flex-row"> <div>{titleText}</div> - <div class="ml-2.5">{emoji}</div> + <div className="ml-2.5">{emoji}</div> </div> <button className="mt-1" @@ -44,14 +54,14 @@ export default function BottonRightPopup({ /> </button> </div> - <div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300"> + <div className="block sm:inline px-6 mt-4 mb-0.5 text-gray-300"> {textLine1} </div> - <div class="block sm:inline mb-4 px-6">{textLine2}</div> + <div className="block sm:inline mb-4 px-6">{textLine2}</div> <div className="flex flex-row px-6 w-full"> {/*eslint-disable-next-line react/jsx-no-target-blank */} <a - class="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center" + className="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center" href={buttonLink} target="_blank" rel="noopener" diff --git a/frontend/components/dashboard/DashboardInputField.tsx b/frontend/components/dashboard/DashboardInputField.tsx index 3a5cd80ee..13df0630d 100644 --- a/frontend/components/dashboard/DashboardInputField.tsx +++ b/frontend/components/dashboard/DashboardInputField.tsx @@ -87,7 +87,7 @@ const DashboardInputField = ({ <div className={`group relative whitespace-pre flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`} > - {override == true && <div className='bg-yellow-300 absolute top-[0.1rem] right-[0.1rem] z-10 w-min text-xxs px-1 text-black opacity-80 rounded-sm'>Override enabled</div>} + {override == true && <div className='bg-primary-300 absolute top-[0.1rem] right-[0.1rem] z-10 w-min text-xxs px-1 text-black opacity-80 rounded-md'>Override enabled</div>} <input value={value} onChange={(e) => onChangeHandler(e.target.value, position)} @@ -106,7 +106,7 @@ const DashboardInputField = ({ ? 'text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400' : '' } ${ - override ? 'text-yellow-300' : 'text-gray-400' + override ? 'text-primary-300' : 'text-gray-400' } absolute flex flex-row whitespace-pre font-mono z-0 ph-no-capture max-w-2xl overflow-x-scroll bg-bunker-800 h-9 rounded-md text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-2 focus:ring-primary/50 duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`} > diff --git a/frontend/components/dashboard/GenerateSecretMenu.tsx b/frontend/components/dashboard/GenerateSecretMenu.tsx index 0d1001733..115e19374 100644 --- a/frontend/components/dashboard/GenerateSecretMenu.tsx +++ b/frontend/components/dashboard/GenerateSecretMenu.tsx @@ -8,7 +8,7 @@ import { Menu, Transition } from '@headlessui/react'; * This is the menu that is used to (re)generate secrets (currently we only have ranom hex, in future we will have more options) * @returns the popup-menu for randomly generating secrets */ -const GenerateSecretMenu = () => { +const GenerateSecretMenu = ({ modifyValue, position }: { modifyValue: (value: string, position: number) => void; position: number; }) => { const [randomStringLength, setRandomStringLength] = useState(32); return <Menu as="div" className="relative inline-block text-left"> @@ -36,12 +36,12 @@ const GenerateSecretMenu = () => { } else if (randomStringLength < 2) { setRandomStringLength(2); } else { - // modifyValue( - // [...Array(randomStringLength)] - // .map(() => Math.floor(Math.random() * 16).toString(16)) - // .join(''), - // keyPair.pos - // ); + modifyValue( + [...Array(randomStringLength)] + .map(() => Math.floor(Math.random() * 16).toString(16)) + .join(''), + position + ); } }} className="relative flex flex-row justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full" diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index 38f575c6b..5051820cc 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { faBackward, faDotCircle, faRotateLeft, faX } from '@fortawesome/free-solid-svg-icons'; +import { faDotCircle, faRotateLeft, faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Button from '../basic/buttons/Button'; @@ -13,6 +13,7 @@ interface SecretProps { key: string; value: string; pos: number; + type: string; id: string; } @@ -24,13 +25,14 @@ interface OverrideProps { } interface SideBarProps { - toggleSidebar: (value: number) => void; + toggleSidebar: (value: string) => void; data: SecretProps[]; - modifyKey: (value: string) => void; - modifyValue: (value: string) => void; - modifyVisibility: (value: string) => void; + modifyKey: (value: string, position: number) => void; + modifyValue: (value: string, position: number) => void; addOverride: (value: OverrideProps) => void; deleteOverride: (id: string) => void; + buttonReady: boolean; + savePush: () => void; } /** @@ -39,17 +41,21 @@ 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.modifyVisibility - function that modifies the secret visibility + * @param {function} obj.addOverride - override a certain secret + * @param {function} obj.deleteOverride - delete the personal override for a certain secret + * @param {boolean} obj.buttonReady - is the button for saving chagnes active + * @param {function} obj.savePush - save changes andp ush secrets * @returns the sidebar with 'secret's settings' */ -const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility, addOverride, deleteOverride }: SideBarProps) => { - const [overrideEnabled, setOverrideEnabled] = useState(false); +const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, deleteOverride, buttonReady, savePush }: SideBarProps) => { + const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal")); + console.log("sidebar", data, data.map(secret => secret.type).includes("personal")) return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'> - <div className='h-min'> + <div className='h-min overflow-y-auto'> <div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center"> <p className="font-semibold text-lg text-bunker-200">Secret</p> - <div className='p-1' onClick={() => toggleSidebar(-1)}> + <div className='p-1' onClick={() => toggleSidebar("None")}> <FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/> </div> </div> @@ -64,21 +70,27 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility blurred={false} /> </div> - <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> + {data.filter(secret => secret.type == "shared")[0]?.value + ? <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> <p className='text-sm text-bunker-300'>Value</p> <DashboardInputField onChangeHandler={modifyValue} type="value" - position={data[0].pos} - value={data[0].value + "xxx" + data[0].pos} + position={data.filter(secret => secret.type == "shared")[0]?.pos} + value={data.filter(secret => secret.type == "shared")[0]?.value} duplicates={[]} blurred={true} /> <div className='absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50'> - <GenerateSecretMenu /> + <GenerateSecretMenu modifyValue={modifyValue} position={data.filter(secret => secret.type == "shared")[0]?.pos} /> </div> </div> + : <div className='px-4 text-sm text-bunker-300 pt-4'> + <span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Note:</span> + This secret is personal. It is not shared with any of your teammates. + </div>} <div className='mt-4 px-4'> + {data.filter(secret => secret.type == "shared")[0]?.value && <div className='flex flex-row items-center justify-between my-2 pl-1 pr-2'> <p className='text-sm text-bunker-300'>Override value with a personal value</p> <Toggle @@ -91,22 +103,22 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility id={data[0].id} deleteOverride={deleteOverride} /> - </div> + </div>} <div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> <DashboardInputField onChangeHandler={modifyValue} type="value" - position={data[0].pos} - value={"ValueValueValue" + data[0].pos} + position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos} + value={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].value : data[0].value} duplicates={[]} blurred={true} /> - <div className='absolute right-3 top-[0.3rem] z-50'> - <GenerateSecretMenu /> + <div className='absolute right-[0.57rem] top-[0.3rem] z-50'> + <GenerateSecretMenu modifyValue={modifyValue} position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos} /> </div> </div> </div> - <div className={`relative mt-4 px-4 opacity-80 duration-200`}> + {/* <div className={`relative mt-4 px-4 opacity-80 duration-200`}> <p className='text-sm text-bunker-200'>Group</p> <ListBox selected={"Database Secrets"} @@ -114,7 +126,7 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility data={["Group1"]} isFull={true} /> - </div> + </div> */} <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'> <p className=''>Version History</p> <div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none'> @@ -170,12 +182,14 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility </div> </div> </div> - <div className='mt-full px-4 mb-[4.7rem]'> + <div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}> <Button - onButtonPressed={() => console.log('Saved')} text="Save Changes" + onButtonPressed={savePush} color="primary" size="md" + active={buttonReady} + textDisabled="Saved" /> </div> </div> diff --git a/frontend/components/utilities/secrets/getSecretsForProject.ts b/frontend/components/utilities/secrets/getSecretsForProject.ts index 3b63f9177..311c0c59a 100644 --- a/frontend/components/utilities/secrets/getSecretsForProject.ts +++ b/frontend/components/utilities/secrets/getSecretsForProject.ts @@ -39,7 +39,7 @@ const getSecretsForProject = async ({ const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY'); - const tempFileState: { key: string; value: string; type: string }[] = []; + const tempFileState: { key: string; value: string; type: 'personal' | 'shared'; }[] = []; if (file.key) { // assymmetrically decrypt symmetric key with local private key const key = decryptAssymmetric({ @@ -97,7 +97,7 @@ const getSecretsForProject = async ({ } catch (error) { console.log('Something went wrong during accessing or decripting secrets.'); } - return true; + return []; }; export default getSecretsForProject; diff --git a/frontend/components/utilities/secrets/pushKeys.ts b/frontend/components/utilities/secrets/pushKeys.ts index 0d570f273..7b91c0b31 100644 --- a/frontend/components/utilities/secrets/pushKeys.ts +++ b/frontend/components/utilities/secrets/pushKeys.ts @@ -51,7 +51,7 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st iv: ivKey, tag: tagKey, } = encryptSymmetric({ - plaintext: key, + plaintext: key.slice(1), key: randomBytes, }); @@ -65,13 +65,13 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st key: randomBytes, }); - const visibility = obj[key as keyof typeof obj][1] != null ? obj[key as keyof typeof obj][1] : "personal"; + const visibility = key.charAt(0) == "p" ? "personal" : "shared"; return { ciphertextKey, ivKey, tagKey, - hashKey: crypto.createHash("sha256").update(key).digest("hex"), + hashKey: crypto.createHash("sha256").update(key.slice(1)).digest("hex"), ciphertextValue, ivValue, tagValue, diff --git a/frontend/pages/dashboard/[id].js b/frontend/pages/dashboard/[id].tsx similarity index 76% rename from frontend/pages/dashboard/[id].js rename to frontend/pages/dashboard/[id].tsx index 11519578e..65408aaa5 100644 --- a/frontend/pages/dashboard/[id].js +++ b/frontend/pages/dashboard/[id].tsx @@ -24,7 +24,7 @@ import BottonRightPopup from '~/components/basic/popups/BottomRightPopup'; import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider'; import DashboardInputField from '~/components/dashboard/DashboardInputField'; import DropZone from '~/components/dashboard/DropZone'; -import SideBar from '~/components/dashboard/Sidebar'; +import SideBar from '~/components/dashboard/SideBar'; import NavHeader from '~/components/navigation/NavHeader'; import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject'; import pushKeys from '~/components/utilities/secrets/pushKeys'; @@ -36,6 +36,26 @@ import checkUserAction from '../api/userActions/checkUserAction'; import registerUserAction from '../api/userActions/registerUserAction'; import getWorkspaces from '../api/workspace/getWorkspaces'; + +interface SecretDataProps { + type: 'personal' | 'shared'; + pos: number; + key: string; + value: string; + id: string; +} + +interface KeyPairProps { + keyPair: SecretDataProps; + deleteRow: (id: string) => void; + modifyKey: (value: string, position: number) => void; + modifyValue: (value: string, position: number) => void; + isBlurred: boolean; + duplicates: any[]; + toggleSidebar: (id: string) => void; + sidebarSecretId: string; +} + /** * This component represent a single row for an environemnt variable on the dashboard * @param {object} obj @@ -43,9 +63,10 @@ import getWorkspaces from '../api/workspace/getWorkspaces'; * @param {function} obj.deleteRow - a function to delete a certain keyPair * @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.modifyVisibility - switch between public/private visibility * @param {boolean} obj.isBlurred - if the blurring setting is turned on * @param {string[]} obj.duplicates - list of all the duplicates secret names on the dashboard + * @param {string[]} obj.toggleSidebar - open/close/switch sidebar + * @param {string[]} obj.sidebarSecretId - the id of a secret for the side bar is displayed * @returns */ const KeyPair = ({ @@ -53,16 +74,20 @@ const KeyPair = ({ deleteRow, modifyKey, modifyValue, - modifyVisibility, isBlurred, duplicates, toggleSidebar, - sidebarSecretNumber -}) => { - + sidebarSecretId +}: KeyPairProps) => { return ( - <div className={`mx-1 flex flex-col items-center ml-1 ${keyPair.pos == sidebarSecretNumber && "bg-mineshaft-500 duration-200"} rounded-md`}> + <div className={`mx-1 flex flex-col items-center ml-1 ${keyPair.id == sidebarSecretId && "bg-mineshaft-500 duration-200"} rounded-md`}> <div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-14 my-1 items-start px-1"> + {keyPair.type == "personal" && <div className="group font-normal group absolute top-[1rem] left-[0.2rem] z-40 inline-block text-gray-300 underline hover:text-primary duration-200"> + <div className='w-1 h-1 rounded-full bg-primary z-40'></div> + <span className="absolute z-50 hidden group-hover:flex group-hover:animate-popdown duration-200 w-[10.5rem] -left-[0.4rem] -top-[1.7rem] translate-y-full px-2 py-2 bg-mineshaft-500 rounded-b-md rounded-r-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-0 after:bottom-[100%] after:-translate-x-0 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-mineshaft-500"> + This secret is overriden + </span> + </div>} <div className="min-w-xl w-96"> <div className="flex pr-1 items-center rounded-lg mt-4 md:mt-0 max-h-16"> <DashboardInputField @@ -71,6 +96,7 @@ const KeyPair = ({ position={keyPair.pos} value={keyPair.key} duplicates={duplicates} + blurred={false} /> </div> </div> @@ -82,11 +108,12 @@ const KeyPair = ({ position={keyPair.pos} value={keyPair.value} blurred={isBlurred} - override={keyPair.value == "user1234" && true} + override={keyPair.type == "personal"} + duplicates={[]} /> </div> </div> - <div onClick={() => toggleSidebar(keyPair.pos)} className="cursor-pointer w-9 h-9 bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> + <div onClick={() => toggleSidebar(keyPair.id)} className="cursor-pointer w-9 h-9 bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> <FontAwesomeIcon className="text-gray-300 px-2.5 text-lg mt-0.5" icon={faEllipsis} @@ -106,13 +133,32 @@ const KeyPair = ({ ); }; + +/** + * this function finds the teh duplicates in an array + * @param arr - array of anything (e.g., with secret keys and types (personal/shared)) + * @returns - a list with duplicates + */ +function findDuplicates(arr: any[]) { + const map = new Map(); + return arr.filter((item) => { + if (map.has(item)) { + map.set(item, false); + return true; + } else { + map.set(item, true); + return false; + } + }); +} + /** * This is the main component for the dashboard (aka the screen with all the encironemnt variable & secrets) * @returns */ export default function Dashboard() { - const [data, setData] = useState(); - const [fileState, setFileState] = useState([]); + const [data, setData] = useState<SecretDataProps[] | null>(); + const [fileState, setFileState] = useState<SecretDataProps[]>([]); const [buttonReady, setButtonReady] = useState(false); const router = useRouter(); const [workspaceId, setWorkspaceId] = useState(''); @@ -132,7 +178,7 @@ export default function Dashboard() { const [sortMethod, setSortMethod] = useState('alphabetical'); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false); - const [sidebarSecretNumber, toggleSidebar] = useState(-1); + const [sidebarSecretId, toggleSidebar] = useState("None"); const { createNotification } = useNotificationContext(); @@ -155,7 +201,7 @@ export default function Dashboard() { useEffect(() => { const warningText = 'Do you want to save your results before leaving this page?'; - const handleWindowClose = (e) => { + const handleWindowClose = (e: any) => { if (!buttonReady) return; e.preventDefault(); return (e.returnValue = warningText); @@ -171,18 +217,18 @@ export default function Dashboard() { /** * Reorder rows alphabetically or in the opprosite order */ - const reorderRows = (dataToReorder) => { + const reorderRows = (dataToReorder: SecretDataProps[] | 1) => { setSortMethod((prevSort) => prevSort == 'alphabetical' ? '-alphabetical' : 'alphabetical' ); - sortValuesHandler(dataToReorder, ""); + sortValuesHandler(dataToReorder, undefined); }; useEffect(() => { (async () => { try { - let userWorkspaces = await getWorkspaces(); + const userWorkspaces = await getWorkspaces(); const listWorkspaces = userWorkspaces.map((workspace) => workspace._id); if ( !listWorkspaces.includes(router.asPath.split('/')[2].split('?')[0]) @@ -194,31 +240,31 @@ export default function Dashboard() { router.push(router.asPath.split('?')[0] + '?' + env); } setBlurred(true); - setWorkspaceId(router.query.id); + setWorkspaceId(String(router.query.id)); const dataToSort = await getSecretsForProject({ env, setFileState, setIsKeyAvailable, setData, - workspaceId: router.query.id + workspaceId: String(router.query.id) }); reorderRows(dataToSort); const user = await getUser(); setIsNew( - (Date.parse(new Date()) - Date.parse(user.createdAt)) / 60000 < 3 + (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / 60000 < 3 ? true : false ); - let userAction = await checkUserAction({ + const userAction = await checkUserAction({ action: 'first_time_secrets_pushed' }); setHasUserEverPushed(userAction ? true : false); } catch (error) { console.log('Error', error); - setData([]); + setData(undefined); } })(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -227,10 +273,10 @@ export default function Dashboard() { const addRow = () => { setIsNew(false); setData([ - ...data, + ...data!, { id: guidGenerator(), - pos: data.length, + pos: data!.length, key: '', value: '', type: 'shared' @@ -238,6 +284,13 @@ export default function Dashboard() { ]); }; + interface overrideProps { + id: string; + keyName: string; + value: string; + pos: number; + } + /** * This function add an ovverrided version of a certain secret to the current user * @param {object} obj @@ -246,10 +299,10 @@ export default function Dashboard() { * @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 }) => { + const addOverride = ({ id, keyName, value, pos }: overrideProps) => { setIsNew(false); - const tempdata = [ - ...data, + const tempdata: SecretDataProps[] | 1 = [ + ...data!, { id: id, pos: pos, @@ -261,54 +314,55 @@ export default function Dashboard() { sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical"); }; - const deleteRow = (id) => { + const deleteRow = (id: string) => { setButtonReady(true); - setData(data.filter((row) => row.id !== id)); + setData(data!.filter((row: SecretDataProps) => row.id !== id)); }; /** * This function deleted the override of a certain secrer * @param {string} id - id of a secret to be deleted */ - const deleteOverride = (id) => { + const deleteOverride = (id: string) => { setButtonReady(true); - setData(data.filter((row) => !(row.id == id && row.type == 'personal'))); + const tempData = data!.filter((row: SecretDataProps) => !(row.id == id && row.type == 'personal')) + sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical") }; - const modifyValue = (value, pos) => { + const modifyValue = (value: string, pos: number) => { setData((oldData) => { - oldData[pos].value = value; - return [...oldData]; + oldData![pos].value = value; + return [...oldData!]; }); setButtonReady(true); }; - const modifyKey = (value, pos) => { + const modifyKey = (value: string, pos: number) => { setData((oldData) => { - oldData[pos].key = value; - return [...oldData]; + oldData![pos].key = value; + return [...oldData!]; }); setButtonReady(true); }; - const modifyVisibility = (value, pos) => { + const modifyVisibility = (value: "shared" | "personal", pos: number) => { setData((oldData) => { - oldData[pos].type = value; - return [...oldData]; + oldData![pos].type = value; + return [...oldData!]; }); setButtonReady(true); }; // For speed purposes and better perforamance, we are using useCallback - const listenChangeValue = useCallback((value, pos) => { + const listenChangeValue = useCallback((value: string, pos: number) => { modifyValue(value, pos); }, []); - const listenChangeKey = useCallback((value, pos) => { + const listenChangeKey = useCallback((value: string, pos: number) => { modifyKey(value, pos); }, []); - const listenChangeVisibility = useCallback((value, pos) => { + const listenChangeVisibility = useCallback((value: "shared" | "personal", pos: number) => { modifyVisibility(value, pos); }, []); @@ -317,21 +371,16 @@ export default function Dashboard() { */ const savePush = async () => { // Format the new object with environment variables - let obj = Object.assign( + const obj = Object.assign( {}, - ...data.map((row) => ({ [row.key]: [row.value, row.type] })) + ...data!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.type] })) ); // 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(key.charAt(0))) + .map((key) => !isNaN(Number(key[0].charAt(0)))) .every((v) => v === false); - const duplicatesExist = - data - ?.map((item) => item.key) - .filter( - (item, index) => index !== data?.map((item) => item.key).indexOf(item) - ).length > 0; + const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => [item.key, item.type])).length > 0; if (nameErrors) { return createNotification({ @@ -347,9 +396,11 @@ export default function Dashboard() { }); } + console.log('pushing', obj) + // Once "Save changed is clicked", disable that button setButtonReady(false); - pushKeys({ obj, workspaceId: router.query.id, env }); + pushKeys({ obj, workspaceId: String(router.query.id), env }); // If this user has never saved environment variables before, show them a prompt to read docs if (!hasUserEverPushed) { @@ -358,8 +409,8 @@ export default function Dashboard() { } }; - const addData = (newData) => { - setData(data.concat(newData)); + const addData = (newData: SecretDataProps[]) => { + setData(data!.concat(newData)); setButtonReady(true); }; @@ -367,38 +418,39 @@ export default function Dashboard() { setBlurred(!blurred); }; - const sortValuesHandler = (dataToSort, specificSortMethod) => { - const howToSort = specificSortMethod != "" ? specificSortMethod : sortMethod - const sortedData = (dataToSort != 1 ? dataToSort : data) + const sortValuesHandler = (dataToSort: SecretDataProps[] | 1, specificSortMethod?: 'alphabetical' | '-alphabetical') => { + const howToSort = specificSortMethod == undefined ? sortMethod : specificSortMethod; + const sortedData = (dataToSort != 1 ? dataToSort : data)! .sort((a, b) => howToSort == 'alphabetical' ? a.key.localeCompare(b.key) : b.key.localeCompare(a.key) ) - .map((item, index) => { + .map((item: SecretDataProps, index: number) => { return { ...item, pos: index }; }); + console.log('override', sortedData) setData(sortedData); }; // This function downloads the secrets as a .env file const download = () => { - const file = data - .map((item) => [item.key, item.value].join('=')) + const file = data! + .map((item: SecretDataProps) => [item.key, item.value].join('=')) .join('\n'); const blob = new Blob([file]); const fileDownloadUrl = URL.createObjectURL(blob); - let alink = document.createElement('a'); + const alink = document.createElement('a'); alink.href = fileDownloadUrl; alink.download = envMapping[env] + '.env'; alink.click(); }; - const deleteCertainRow = (id) => { + const deleteCertainRow = (id: string) => { deleteRow(id); }; @@ -406,15 +458,17 @@ export default function Dashboard() { * This function copies the project id to the clipboard */ function copyToClipboard() { - var copyText = document.getElementById('myInput'); + const copyText = document.getElementById('myInput'); + + if (copyText) { + copyText.select(); + // copyText.setSelectionRange(0, 99999); // For mobile devices - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices - - navigator.clipboard.writeText(copyText.value); - - setProjectIdCopied(true); - setTimeout(() => setProjectIdCopied(false), 2000); + navigator.clipboard.writeText(copyText.value); + + setProjectIdCopied(true); + setTimeout(() => setProjectIdCopied(false), 2000); + } } return data ? ( @@ -430,14 +484,15 @@ export default function Dashboard() { /> </Head> <div className="flex flex-row"> - {sidebarSecretNumber != -1 && <SideBar + {sidebarSecretId != "None" && <SideBar toggleSidebar={toggleSidebar} - data={data.filter(row => row.pos == sidebarSecretNumber)} + data={data.filter((row: SecretDataProps) => row.id == sidebarSecretId)} modifyKey={listenChangeKey} modifyValue={listenChangeValue} - modifyVisibility={listenChangeVisibility} addOverride={addOverride} deleteOverride={deleteOverride} + buttonReady={buttonReady} + savePush={savePush} />} <div className="w-full max-h-96 pb-2"> <NavHeader pageName="Secrets" isProjectRelated={true} /> @@ -461,7 +516,6 @@ export default function Dashboard() { data={['Development', 'Staging', 'Production', 'Testing']} // ref={useRef(123)} onChange={setEnv} - className="z-40" /> )} </div> @@ -516,7 +570,6 @@ export default function Dashboard() { data={['Development', 'Staging', 'Production', 'Testing']} // ref={useRef(123)} onChange={setEnv} - className="z-40" /> <div className="h-10 w-full bg-white/5 hover:bg-white/10 ml-2 flex items-center rounded-md flex flex-row items-center"> <FontAwesomeIcon @@ -599,17 +652,10 @@ export default function Dashboard() { deleteRow={deleteCertainRow} modifyValue={listenChangeValue} modifyKey={listenChangeKey} - modifyVisibility={listenChangeVisibility} isBlurred={blurred} - duplicates={data - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - data?.map((item) => item.key).indexOf(item) - )} + duplicates={findDuplicates(data?.map((item) => [item.key, item.type]))} toggleSidebar={toggleSidebar} - sidebarSecretNumber={sidebarSecretNumber} + sidebarSecretId={sidebarSecretId} /> ))} </div> @@ -628,30 +674,19 @@ export default function Dashboard() { </div> ) : ( <div className="flex flex-col items-center justify-center h-full text-xl text-gray-400 max-w-5xl mt-28"> - {fileState.message != "There's nothing to pull" && - fileState.message != undefined && ( - <FontAwesomeIcon - className="text-7xl mb-8" - icon={faFolderOpen} - /> - )} - {(fileState.message == "There's nothing to pull" || - fileState.message == undefined) && - isKeyAvailable && ( - <DropZone - setData={setData} - setErrorDragAndDrop={setErrorDragAndDrop} - createNewFile={addRow} - errorDragAndDrop={errorDragAndDrop} - setButtonReady={setButtonReady} - numCurrentRows={data.length} - /> - )} - {fileState.message == - 'Failed membership validation for workspace' && ( - <p>You are not authorized to view this project.</p> + {isKeyAvailable && ( + <DropZone + setData={setData} + setErrorDragAndDrop={setErrorDragAndDrop} + createNewFile={addRow} + errorDragAndDrop={errorDragAndDrop} + setButtonReady={setButtonReady} + numCurrentRows={data.length} + keysExist={false} + /> )} - {fileState.message == 'Access needed to pull the latest file' || + { + // fileState.message == 'Access needed to pull the latest file' || (!isKeyAvailable && ( <> <FontAwesomeIcon From 0cb26a9495826cc2e312e3cca1b93537c0c53dac Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Sun, 25 Dec 2022 10:11:13 -0500 Subject: [PATCH 05/18] Added memoization and did performance optimizations --- .../dashboard/DashboardInputField.tsx | 23 ++-- frontend/components/dashboard/KeyPair.tsx | 102 ++++++++++++++++++ frontend/pages/dashboard/[id].tsx | 95 +--------------- 3 files changed, 118 insertions(+), 102 deletions(-) create mode 100644 frontend/components/dashboard/KeyPair.tsx diff --git a/frontend/components/dashboard/DashboardInputField.tsx b/frontend/components/dashboard/DashboardInputField.tsx index 13df0630d..9774a1ec7 100644 --- a/frontend/components/dashboard/DashboardInputField.tsx +++ b/frontend/components/dashboard/DashboardInputField.tsx @@ -11,20 +11,21 @@ interface DashboardInputFieldProps { onChangeHandler: (value: string, position: number) => void; value: string; type: 'varName' | 'value'; - blurred: boolean; - duplicates: string[]; + blurred?: boolean; + isDuplicate?: boolean; override?: boolean; } /** * This component renders the input fields on the dashboard * @param {object} obj - the order number of a keyPair - * @param {number} obj.pos - the order number of a keyPair + * @param {number} obj.position - the order number of a keyPair * @param {function} obj.onChangeHandler - what happens when the input is modified * @param {string} obj.type - whether the input field is for a Key Name or for a Key Value * @param {string} obj.value - value of the InputField * @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard - * @param {string[]} obj.duplicates - list of all the duplicated key names on the dashboard + * @param {boolean} obj.isDuplicate - if the key name is duplicated + * @param {boolean} obj.override - whether a secret/row should be displalyed as overriden * @returns */ @@ -34,7 +35,7 @@ const DashboardInputField = ({ type, value, blurred, - duplicates, + isDuplicate, override }: DashboardInputFieldProps) => { const ref = useRef<HTMLDivElement | null>(null); @@ -44,11 +45,11 @@ const DashboardInputField = ({ ref.current.scrollTop = e.currentTarget.scrollTop; ref.current.scrollLeft = e.currentTarget.scrollLeft; }; + console.log('rerender', value) if (type === 'varName') { const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != ''; - const hasDuplicates = duplicates?.includes(value); - const error = startsWithNumber || hasDuplicates; + const error = startsWithNumber || isDuplicate; return ( <div className="flex-col w-full"> @@ -74,7 +75,7 @@ const DashboardInputField = ({ Should not start with a number </p> )} - {hasDuplicates && !startsWithNumber && ( + {isDuplicate && !startsWithNumber && ( <p className="text-red text-xs mt-0.5 mx-1 mb-2 max-w-xs"> Secret names should be unique </p> @@ -159,4 +160,8 @@ const DashboardInputField = ({ return <>Something Wrong</>; }; -export default React.memo(DashboardInputField); +function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInputFieldProps) { + return prev.value === next.value && prev.type === next.type && prev.position === next.position && prev.blurred === next.blurred && prev.override === next.override && prev.duplicate === next.duplicate; +} + +export default React.memo(DashboardInputField, inputPropsAreEqual); diff --git a/frontend/components/dashboard/KeyPair.tsx b/frontend/components/dashboard/KeyPair.tsx new file mode 100644 index 000000000..d917f3cee --- /dev/null +++ b/frontend/components/dashboard/KeyPair.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { faEllipsis, faShuffle, faX } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import Button from '../basic/buttons/Button'; +import DashboardInputField from './DashboardInputField'; + +interface SecretDataProps { + type: 'personal' | 'shared'; + pos: number; + key: string; + value: string; + id: string; +} + +interface KeyPairProps { + keyPair: SecretDataProps; + deleteRow: (id: string) => void; + modifyKey: (value: string, position: number) => void; + modifyValue: (value: string, position: number) => void; + isBlurred: boolean; + isDuplicate: boolean; + toggleSidebar: (id: string) => void; + sidebarSecretId: string; +} + +/** + * This component represent a single row for an environemnt variable on the dashboard + * @param {object} obj + * @param {String[]} obj.keyPair - data related to the environment variable (id, pos, key, value, public/private) + * @param {function} obj.deleteRow - a function to delete a certain keyPair + * @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 {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 + * @param {string} obj.sidebarSecretId - the id of a secret for the side bar is displayed + * @returns + */ +const KeyPair = ({ + keyPair, + deleteRow, + modifyKey, + modifyValue, + isBlurred, + isDuplicate, + toggleSidebar, + sidebarSecretId +}: KeyPairProps) => { + return ( + <div className={`mx-1 flex flex-col items-center ml-1 ${keyPair.id == sidebarSecretId && "bg-mineshaft-500 duration-200"} rounded-md`}> + <div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-14 my-1 items-start px-1"> + {keyPair.type == "personal" && <div className="group font-normal group absolute top-[1rem] left-[0.2rem] z-40 inline-block text-gray-300 underline hover:text-primary duration-200"> + <div className='w-1 h-1 rounded-full bg-primary z-40'></div> + <span className="absolute z-50 hidden group-hover:flex group-hover:animate-popdown duration-200 w-[10.5rem] -left-[0.4rem] -top-[1.7rem] translate-y-full px-2 py-2 bg-mineshaft-500 rounded-b-md rounded-r-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-0 after:bottom-[100%] after:-translate-x-0 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-mineshaft-500"> + This secret is overriden + </span> + </div>} + <div className="min-w-xl w-96"> + <div className="flex pr-1 items-center rounded-lg mt-4 md:mt-0 max-h-16"> + <DashboardInputField + onChangeHandler={modifyKey} + type="varName" + position={keyPair.pos} + value={keyPair.key} + isDuplicate={isDuplicate} + /> + </div> + </div> + <div className="w-full min-w-5xl"> + <div className="flex min-w-7xl items-center pl-1 pr-1.5 rounded-lg mt-4 md:mt-0 max-h-10 "> + <DashboardInputField + onChangeHandler={modifyValue} + type="value" + position={keyPair.pos} + value={keyPair.value} + blurred={isBlurred} + override={keyPair.type == "personal"} + /> + </div> + </div> + <div onClick={() => toggleSidebar(keyPair.id)} className="cursor-pointer w-9 h-9 bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> + <FontAwesomeIcon + className="text-gray-300 px-2.5 text-lg mt-0.5" + icon={faEllipsis} + /> + </div> + <div className="w-2"></div> + <div className="bg-[#9B3535] hover:bg-red rounded-md duration-200"> + <Button + onButtonPressed={() => deleteRow(keyPair.id)} + color="none" + size="icon-sm" + icon={faX} + /> + </div> + </div> + </div> + ); +}; + +export default React.memo(KeyPair); \ No newline at end of file diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 65408aaa5..276d7c5ad 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -8,13 +8,11 @@ import { faCheck, faCopy, faDownload, - faEllipsis, faEye, faEyeSlash, faFolderOpen, faMagnifyingGlass, faPlus, - faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -22,8 +20,8 @@ import Button from '~/components/basic/buttons/Button'; import ListBox from '~/components/basic/Listbox'; import BottonRightPopup from '~/components/basic/popups/BottomRightPopup'; import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider'; -import DashboardInputField from '~/components/dashboard/DashboardInputField'; import DropZone from '~/components/dashboard/DropZone'; +import KeyPair from '~/components/dashboard/KeyPair'; import SideBar from '~/components/dashboard/SideBar'; import NavHeader from '~/components/navigation/NavHeader'; import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject'; @@ -45,95 +43,6 @@ interface SecretDataProps { id: string; } -interface KeyPairProps { - keyPair: SecretDataProps; - deleteRow: (id: string) => void; - modifyKey: (value: string, position: number) => void; - modifyValue: (value: string, position: number) => void; - isBlurred: boolean; - duplicates: any[]; - toggleSidebar: (id: string) => void; - sidebarSecretId: string; -} - -/** - * This component represent a single row for an environemnt variable on the dashboard - * @param {object} obj - * @param {String[]} obj.keyPair - data related to the environment variable (id, pos, key, value, public/private) - * @param {function} obj.deleteRow - a function to delete a certain keyPair - * @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 {boolean} obj.isBlurred - if the blurring setting is turned on - * @param {string[]} obj.duplicates - list of all the duplicates secret names on the dashboard - * @param {string[]} obj.toggleSidebar - open/close/switch sidebar - * @param {string[]} obj.sidebarSecretId - the id of a secret for the side bar is displayed - * @returns - */ -const KeyPair = ({ - keyPair, - deleteRow, - modifyKey, - modifyValue, - isBlurred, - duplicates, - toggleSidebar, - sidebarSecretId -}: KeyPairProps) => { - return ( - <div className={`mx-1 flex flex-col items-center ml-1 ${keyPair.id == sidebarSecretId && "bg-mineshaft-500 duration-200"} rounded-md`}> - <div className="relative flex flex-row justify-between w-full max-w-5xl mr-auto max-h-14 my-1 items-start px-1"> - {keyPair.type == "personal" && <div className="group font-normal group absolute top-[1rem] left-[0.2rem] z-40 inline-block text-gray-300 underline hover:text-primary duration-200"> - <div className='w-1 h-1 rounded-full bg-primary z-40'></div> - <span className="absolute z-50 hidden group-hover:flex group-hover:animate-popdown duration-200 w-[10.5rem] -left-[0.4rem] -top-[1.7rem] translate-y-full px-2 py-2 bg-mineshaft-500 rounded-b-md rounded-r-md text-center text-gray-100 text-sm after:content-[''] after:absolute after:left-0 after:bottom-[100%] after:-translate-x-0 after:border-8 after:border-x-transparent after:border-t-transparent after:border-b-mineshaft-500"> - This secret is overriden - </span> - </div>} - <div className="min-w-xl w-96"> - <div className="flex pr-1 items-center rounded-lg mt-4 md:mt-0 max-h-16"> - <DashboardInputField - onChangeHandler={modifyKey} - type="varName" - position={keyPair.pos} - value={keyPair.key} - duplicates={duplicates} - blurred={false} - /> - </div> - </div> - <div className="w-full min-w-5xl"> - <div className="flex min-w-7xl items-center pl-1 pr-1.5 rounded-lg mt-4 md:mt-0 max-h-10 "> - <DashboardInputField - onChangeHandler={modifyValue} - type="value" - position={keyPair.pos} - value={keyPair.value} - blurred={isBlurred} - override={keyPair.type == "personal"} - duplicates={[]} - /> - </div> - </div> - <div onClick={() => toggleSidebar(keyPair.id)} className="cursor-pointer w-9 h-9 bg-mineshaft-700 hover:bg-chicago-700 rounded-md flex flex-row justify-center items-center duration-200"> - <FontAwesomeIcon - className="text-gray-300 px-2.5 text-lg mt-0.5" - icon={faEllipsis} - /> - </div> - <div className="w-2"></div> - <div className="bg-[#9B3535] hover:bg-red rounded-md duration-200"> - <Button - onButtonPressed={() => deleteRow(keyPair.id)} - color="none" - size="icon-sm" - icon={faX} - /> - </div> - </div> - </div> - ); -}; - - /** * this function finds the teh duplicates in an array * @param arr - array of anything (e.g., with secret keys and types (personal/shared)) @@ -653,7 +562,7 @@ export default function Dashboard() { modifyValue={listenChangeValue} modifyKey={listenChangeKey} isBlurred={blurred} - duplicates={findDuplicates(data?.map((item) => [item.key, item.type]))} + isDuplicate={findDuplicates(data?.map((item) => [item.key, item.type]))?.includes(keyPair.value)} toggleSidebar={toggleSidebar} sidebarSecretId={sidebarSecretId} /> From f575ae84e0d1cf3fdb6a33dc04e542b3b08f8451 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Sun, 25 Dec 2022 11:47:13 -0500 Subject: [PATCH 06/18] removed console.log --- frontend/components/dashboard/DashboardInputField.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/components/dashboard/DashboardInputField.tsx b/frontend/components/dashboard/DashboardInputField.tsx index 9774a1ec7..03396f11b 100644 --- a/frontend/components/dashboard/DashboardInputField.tsx +++ b/frontend/components/dashboard/DashboardInputField.tsx @@ -45,7 +45,6 @@ const DashboardInputField = ({ ref.current.scrollTop = e.currentTarget.scrollTop; ref.current.scrollLeft = e.currentTarget.scrollLeft; }; - console.log('rerender', value) if (type === 'varName') { const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != ''; From 20050bcba161974400813d4f7804a755c85f99b0 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Sun, 25 Dec 2022 21:37:53 -0500 Subject: [PATCH 07/18] solved the bug with duplicate management --- frontend/components/basic/Toggle.tsx | 29 ++++++++++++++++++++++- frontend/components/dashboard/SideBar.tsx | 25 +++++++++++++++---- frontend/pages/dashboard/[id].tsx | 27 +++++++++++++-------- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/frontend/components/basic/Toggle.tsx b/frontend/components/basic/Toggle.tsx index 7702bb29e..fa69e4419 100644 --- a/frontend/components/basic/Toggle.tsx +++ b/frontend/components/basic/Toggle.tsx @@ -18,6 +18,8 @@ interface ToggleProps { pos: number; id: string; deleteOverride: (id: string) => void; + sharedToHide: string[]; + setSharedToHide: (values: string[]) => void; } /** @@ -25,16 +27,41 @@ interface ToggleProps { * @param obj * @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 + * @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, deleteOverride }: ToggleProps): JSX.Element { +export default function Toggle ({ + enabled, + setEnabled, + addOverride, + keyName, + value, + pos, + id, + deleteOverride, + sharedToHide, + setSharedToHide +}: ToggleProps): JSX.Element { return ( <Switch checked={enabled} onChange={() => { if (enabled == false) { addOverride({ id, keyName, value, pos }); + setSharedToHide([ + ...sharedToHide!, + id + ]) } else { + setSharedToHide(sharedToHide!.filter(tempId => tempId != id)) deleteOverride(id); } setEnabled(!enabled); diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index 5051820cc..f8d6e9eba 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -33,6 +33,8 @@ interface SideBarProps { deleteOverride: (id: string) => void; buttonReady: boolean; savePush: () => void; + sharedToHide: string[]; + setSharedToHide: (values: string[]) => void; } /** @@ -45,9 +47,22 @@ interface SideBarProps { * @param {function} obj.deleteOverride - delete the personal override for a certain secret * @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 * @returns the sidebar with 'secret's settings' */ -const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, deleteOverride, buttonReady, savePush }: SideBarProps) => { +const SideBar = ({ + toggleSidebar, + data, + modifyKey, + modifyValue, + addOverride, + deleteOverride, + buttonReady, + savePush, + sharedToHide, + setSharedToHide +}: SideBarProps) => { const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal")); console.log("sidebar", data, data.map(secret => secret.type).includes("personal")) @@ -66,7 +81,7 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, del type="varName" position={data[0].pos} value={data[0].key} - duplicates={[]} + isDuplicate={false} blurred={false} /> </div> @@ -78,7 +93,7 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, del type="value" position={data.filter(secret => secret.type == "shared")[0]?.pos} value={data.filter(secret => secret.type == "shared")[0]?.value} - duplicates={[]} + isDuplicate={false} blurred={true} /> <div className='absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50'> @@ -102,6 +117,8 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, del pos={data[0].pos} id={data[0].id} deleteOverride={deleteOverride} + sharedToHide={sharedToHide} + setSharedToHide={setSharedToHide} /> </div>} <div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> @@ -110,7 +127,7 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, del type="value" position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos} value={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].value : data[0].value} - duplicates={[]} + isDuplicate={false} blurred={true} /> <div className='absolute right-[0.57rem] top-[0.3rem] z-50'> diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 276d7c5ad..3852e44f6 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -88,6 +88,7 @@ export default function Dashboard() { const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false); const [sidebarSecretId, toggleSidebar] = useState("None"); + const [sharedToHide, setSharedToHide] = useState<string[]>([]); const { createNotification } = useNotificationContext(); @@ -160,6 +161,16 @@ export default function Dashboard() { }); 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) + ) + const user = await getUser(); setIsNew( (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / 60000 < 3 @@ -289,7 +300,7 @@ export default function Dashboard() { const nameErrors = !Object.keys(obj) .map((key) => !isNaN(Number(key[0].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 + item.type)).length > 0; if (nameErrors) { return createNotification({ @@ -402,6 +413,8 @@ export default function Dashboard() { deleteOverride={deleteOverride} buttonReady={buttonReady} savePush={savePush} + sharedToHide={sharedToHide} + setSharedToHide={setSharedToHide} />} <div className="w-full max-h-96 pb-2"> <NavHeader pageName="Secrets" isProjectRelated={true} /> @@ -548,21 +561,15 @@ export default function Dashboard() { className={`bg-white/5 mt-1 mb-1 rounded-md pb-2 max-w-5xl overflow-visible`} > <div id="data1" className="px-1 pt-2"> - {data?.filter(row => !(data - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - data?.map((item) => item.key).indexOf(item) - ).includes(row.key) && row.type == 'shared')).map((keyPair) => ( - <KeyPair + {data?.filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( + <KeyPair key={keyPair.id} keyPair={keyPair} deleteRow={deleteCertainRow} modifyValue={listenChangeValue} modifyKey={listenChangeKey} isBlurred={blurred} - isDuplicate={findDuplicates(data?.map((item) => [item.key, item.type]))?.includes(keyPair.value)} + isDuplicate={findDuplicates(data?.map((item) => item.key + item.type))?.includes(keyPair.key + keyPair.type)} toggleSidebar={toggleSidebar} sidebarSecretId={sidebarSecretId} /> From a4285df0ffe7365f62794b9164ad4b5e8b9f89e7 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Mon, 26 Dec 2022 09:39:32 -0500 Subject: [PATCH 08/18] Added welcome email to new users --- backend/src/controllers/signupController.ts | 16 +++ frontend/pages/signup.tsx | 109 ++++++++++++-------- 2 files changed, 80 insertions(+), 45 deletions(-) diff --git a/backend/src/controllers/signupController.ts b/backend/src/controllers/signupController.ts index 90fad4744..42bef4973 100644 --- a/backend/src/controllers/signupController.ts +++ b/backend/src/controllers/signupController.ts @@ -10,6 +10,7 @@ import { } from '../helpers/signup'; import { issueTokens, createToken } from '../helpers/auth'; import { INVITED, ACCEPTED } from '../variables'; +import axios from 'axios'; /** * Signup step 1: Initialize account for user under email [email] and send a verification code @@ -179,6 +180,21 @@ export const completeAccountSignup = async (req: Request, res: Response) => { token = tokens.token; refreshToken = tokens.refreshToken; + + // sending a welcome email to new users + if (process.env.LOOPS_API_KEY) { + await axios.post("https://app.loops.so/api/v1/events/send", { + "email": email, + "eventName": "Sign Up", + "firstName": firstName, + "lastName": lastName + }, { + headers: { + "Accept": "application/json", + "Authorization": "Bearer " + process.env.LOOPS_API_KEY + }, + }); + } } catch (err) { Sentry.setUser(null); Sentry.captureException(err); diff --git a/frontend/pages/signup.tsx b/frontend/pages/signup.tsx index 51e2e1c05..a98e5cf29 100644 --- a/frontend/pages/signup.tsx +++ b/frontend/pages/signup.tsx @@ -260,46 +260,49 @@ export default function SignUp() { // Step 1 of the sign up process (enter the email or choose google authentication) const step1 = ( - <div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 mb-48 md:mb-16 rounded-xl drop-shadow-xl"> - <p className="text-4xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> - {'Let\''}s get started - </p> - <div className="flex flex-col items-center justify-center w-full md:pb-2 max-h-24 max-w-md mx-auto pt-2"> + <div> + <div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 rounded-xl drop-shadow-xl"> + <p className="text-4xl font-semibold flex justify-center text-primary"> + {'Let\''}s get started + </p> + <div className="flex items-center justify-center w-5/6 md:w-full m-auto md:p-2 rounded-lg max-h-24 mt-4"> + <InputField + label="Email" + onChangeHandler={setEmail} + type="email" + value={email} + placeholder="" + isRequired + error={emailError} + errorText={emailErrorMessage} + autoComplete="username" + /> + </div> + {/* <div className='flex flex-row justify-left mt-4 max-w-md mx-auto'> + <Checkbox className="mr-4"/> + <p className='text-sm'>I do not want to receive emails about Infisical and its products.</p> + </div> */} + <div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left"> + <p className="text-gray-400 mt-2 md:mx-0.5"> + By creating an account, you agree to our Terms and have read and + acknowledged the Privacy Policy. + </p> + <div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg"> + <Button text="Get Started" type="submit" onButtonPressed={emailCheck} size="lg" /> + </div> + </div> + </div> + <div className="flex flex-col items-center justify-center w-full md:pb-2 max-w-md mx-auto pt-2 mb-48 md:mb-16 mt-2"> <Link href="/login"> <button type="button" className="w-max pb-3 hover:opacity-90 duration-200"> - <u className="font-normal text-md text-sky-500"> + <u className="font-normal text-sm text-primary-500"> Have an account? Log in </u> </button> </Link> </div> - <div className="flex items-center justify-center w-5/6 md:w-full m-auto md:p-2 rounded-lg max-h-24 mt-4"> - <InputField - label="Email" - onChangeHandler={setEmail} - type="email" - value={email} - placeholder="" - isRequired - error={emailError} - errorText={emailErrorMessage} - autoComplete="username" - /> - </div> - {/* <div className='flex flex-row justify-left mt-4 max-w-md mx-auto'> - <Checkbox className="mr-4"/> - <p className='text-sm'>I do not want to receive emails about Infisical and its products.</p> - </div> */} - <div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left"> - <p className="text-gray-400 mt-2 md:mx-0.5"> - By creating an account, you agree to our Terms and have read and - acknowledged the Privacy Policy. - </p> - <div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg"> - <Button text="Get Started" type="submit" onButtonPressed={emailCheck} size="lg" /> - </div> - </div> </div> + ); // Step 2 of the signup process (enter the email verification code) @@ -340,11 +343,11 @@ export default function SignUp() { <Button text="Verify" onButtonPressed={incrementStep} size="lg" /> </div> <div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2"> - <div className="flex flex-row items-baseline gap-1"> + <div className="flex flex-row items-baseline gap-1 text-sm"> <span className="text-gray-400"> Not seeing an email? </span> - <u className={`font-normal text-sm ${isResendingVerificationEmail ? 'text-gray-400' : 'text-sky-500 hover:opacity-90 duration-200'}`}> + <u className={`font-normal ${isResendingVerificationEmail ? 'text-gray-400' : 'text-primary-500 hover:opacity-90 duration-200'}`}> <button disabled={isLoading} onClick={resendVerificationEmail}> {isResendingVerificationEmail ? 'Resending...' : 'Resend'} </button> @@ -512,17 +515,33 @@ export default function SignUp() { It contains your Secret Key which we cannot access or recover for you if you lose it. </div> - <div - className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer" - onClick={() => { - if (localStorage.getItem("projectData.id")) { - router.push("/dashboard/" + localStorage.getItem("projectData.id")); - } else { - router.push("/noprojects") - } - }} - > - Later + <div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> + <Button + text="Download PDF" + onButtonPressed={async () => { + await issueBackupKey({ + email, + password, + personalName: firstName + ' ' + lastName, + setBackupKeyError, + setBackupKeyIssued + }); + router.push('/dashboard/'); + }} + size="lg" + /> + {/* <div + className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer" + onClick={() => { + if (localStorage.getItem("projectData.id")) { + router.push("/dashboard/" + localStorage.getItem("projectData.id")); + } else { + router.push("/noprojects") + } + }} + > + Later + </div> */} </div> </div> ); From c556072b5d33000fff68bb08d2aeb9481d6dca09 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Mon, 26 Dec 2022 10:59:47 -0500 Subject: [PATCH 09/18] Fixed the duplicate error --- frontend/components/dashboard/DashboardInputField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/dashboard/DashboardInputField.tsx b/frontend/components/dashboard/DashboardInputField.tsx index 03396f11b..21a026d2d 100644 --- a/frontend/components/dashboard/DashboardInputField.tsx +++ b/frontend/components/dashboard/DashboardInputField.tsx @@ -160,7 +160,7 @@ const DashboardInputField = ({ }; function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInputFieldProps) { - return prev.value === next.value && prev.type === next.type && prev.position === next.position && prev.blurred === next.blurred && prev.override === next.override && prev.duplicate === next.duplicate; + return prev.value === next.value && prev.type === next.type && prev.position === next.position && prev.blurred === next.blurred && prev.override === next.override && prev.isDuplicate === next.isDuplicate; } export default React.memo(DashboardInputField, inputPropsAreEqual); From 676f34092824dd46cd91b27ff00272f097ebe60f Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Mon, 26 Dec 2022 11:10:32 -0500 Subject: [PATCH 10/18] Fixed the TS error --- frontend/pages/dashboard/[id].tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 3852e44f6..e4b692691 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -378,11 +378,11 @@ export default function Dashboard() { * This function copies the project id to the clipboard */ function copyToClipboard() { - const copyText = document.getElementById('myInput'); + const copyText = document.getElementById('myInput') as HTMLInputElement; if (copyText) { copyText.select(); - // copyText.setSelectionRange(0, 99999); // For mobile devices + copyText.setSelectionRange(0, 99999); // For mobile devices navigator.clipboard.writeText(copyText.value); From 665c6b1a6d7ae9fef274f5f7e5390d932a6057cd Mon Sep 17 00:00:00 2001 From: akhilmhdh <akhilmhdh@gmail.com> Date: Tue, 27 Dec 2022 00:41:06 +0530 Subject: [PATCH 11/18] chore(frontend): removed unneccessary react import in project --- frontend/components/basic/InputField.tsx | 4 ++-- frontend/components/basic/table/ServiceTokenTable.js | 2 +- frontend/components/basic/table/UserTable.js | 2 +- frontend/components/dashboard/DashboardInputField.tsx | 4 ++-- frontend/components/integrations/Integration.tsx | 2 +- frontend/components/navigation/NavBarDashboard.tsx | 2 +- frontend/components/navigation/NavHeader.tsx | 2 +- frontend/pages/dashboard.js | 2 +- frontend/pages/dashboard/[id].js | 2 +- frontend/pages/heroku.js | 2 +- frontend/pages/home/[id].tsx | 2 +- frontend/pages/index.js | 2 +- frontend/pages/integrations/[id].js | 2 +- frontend/pages/login.tsx | 2 +- frontend/pages/netlify.js | 2 +- frontend/pages/password-reset.tsx | 2 +- frontend/pages/settings/billing/[id].js | 2 +- frontend/pages/settings/org/[id].js | 2 +- frontend/pages/settings/personal/[id].js | 2 +- frontend/pages/settings/project/[id].js | 2 +- frontend/pages/signup.tsx | 2 +- frontend/pages/signupinvite.js | 2 +- frontend/pages/users/[id].js | 2 +- frontend/pages/vercel.js | 2 +- frontend/pages/verify-email.tsx | 2 +- 25 files changed, 27 insertions(+), 27 deletions(-) diff --git a/frontend/components/basic/InputField.tsx b/frontend/components/basic/InputField.tsx index 08afa975d..241410959 100644 --- a/frontend/components/basic/InputField.tsx +++ b/frontend/components/basic/InputField.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { memo,useState } from 'react'; import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -140,4 +140,4 @@ const InputField = ( } }; -export default React.memo(InputField); +export default memo(InputField); diff --git a/frontend/components/basic/table/ServiceTokenTable.js b/frontend/components/basic/table/ServiceTokenTable.js index fda94e477..6e5cc65ea 100644 --- a/frontend/components/basic/table/ServiceTokenTable.js +++ b/frontend/components/basic/table/ServiceTokenTable.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; import { faX } from '@fortawesome/free-solid-svg-icons'; diff --git a/frontend/components/basic/table/UserTable.js b/frontend/components/basic/table/UserTable.js index 99ec0ee75..449d23cc6 100644 --- a/frontend/components/basic/table/UserTable.js +++ b/frontend/components/basic/table/UserTable.js @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/router'; import { faX } from '@fortawesome/free-solid-svg-icons'; diff --git a/frontend/components/dashboard/DashboardInputField.tsx b/frontend/components/dashboard/DashboardInputField.tsx index cb75dcf8c..3ede5b051 100644 --- a/frontend/components/dashboard/DashboardInputField.tsx +++ b/frontend/components/dashboard/DashboardInputField.tsx @@ -1,4 +1,4 @@ -import React, { SyntheticEvent, useRef } from 'react'; +import { memo, SyntheticEvent, useRef } from 'react'; import { faCircle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -153,4 +153,4 @@ const DashboardInputField = ({ return <>Something Wrong</>; }; -export default React.memo(DashboardInputField); +export default memo(DashboardInputField); diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 3bba41534..ae9a1a613 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { faArrowRight, diff --git a/frontend/components/navigation/NavBarDashboard.tsx b/frontend/components/navigation/NavBarDashboard.tsx index 1834d8117..23f7c0677 100644 --- a/frontend/components/navigation/NavBarDashboard.tsx +++ b/frontend/components/navigation/NavBarDashboard.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react/jsx-key */ -import React, { Fragment, useEffect, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons'; diff --git a/frontend/components/navigation/NavHeader.tsx b/frontend/components/navigation/NavHeader.tsx index 3d64c2eda..6e9fabe39 100644 --- a/frontend/components/navigation/NavHeader.tsx +++ b/frontend/components/navigation/NavHeader.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { faAngleRight, diff --git a/frontend/pages/dashboard.js b/frontend/pages/dashboard.js index 5954a9db2..e183347aa 100644 --- a/frontend/pages/dashboard.js +++ b/frontend/pages/dashboard.js @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; diff --git a/frontend/pages/dashboard/[id].js b/frontend/pages/dashboard/[id].js index 36f5eb3f2..8438c6625 100644 --- a/frontend/pages/dashboard/[id].js +++ b/frontend/pages/dashboard/[id].js @@ -1,4 +1,4 @@ -import React, { Fragment, useCallback, useEffect, useState } from 'react'; +import { Fragment, useCallback, useEffect, useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import { useRouter } from 'next/router'; diff --git a/frontend/pages/heroku.js b/frontend/pages/heroku.js index 088c96500..3302c0dac 100644 --- a/frontend/pages/heroku.js +++ b/frontend/pages/heroku.js @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; const queryString = require("query-string"); diff --git a/frontend/pages/home/[id].tsx b/frontend/pages/home/[id].tsx index d433600d3..6df85adb1 100644 --- a/frontend/pages/home/[id].tsx +++ b/frontend/pages/home/[id].tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; diff --git a/frontend/pages/index.js b/frontend/pages/index.js index 5fc3aab22..f1412e16c 100644 --- a/frontend/pages/index.js +++ b/frontend/pages/index.js @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { useRouter } from "next/router"; export default function Home() { diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index 32a8e515e..ae203563a 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import Head from "next/head"; import Image from "next/image"; import { useRouter } from "next/router"; diff --git a/frontend/pages/login.tsx b/frontend/pages/login.tsx index 036902de0..83a6920e2 100644 --- a/frontend/pages/login.tsx +++ b/frontend/pages/login.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import Link from 'next/link'; diff --git a/frontend/pages/netlify.js b/frontend/pages/netlify.js index 6907d6db4..fc58042d5 100644 --- a/frontend/pages/netlify.js +++ b/frontend/pages/netlify.js @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; const queryString = require("query-string"); diff --git a/frontend/pages/password-reset.tsx b/frontend/pages/password-reset.tsx index a09b85ab0..45d17d194 100644 --- a/frontend/pages/password-reset.tsx +++ b/frontend/pages/password-reset.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { faCheck, faX } from '@fortawesome/free-solid-svg-icons'; diff --git a/frontend/pages/settings/billing/[id].js b/frontend/pages/settings/billing/[id].js index 00d1db23f..485dfab8d 100644 --- a/frontend/pages/settings/billing/[id].js +++ b/frontend/pages/settings/billing/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import Head from "next/head"; import Plan from "~/components/billing/Plan"; diff --git a/frontend/pages/settings/org/[id].js b/frontend/pages/settings/org/[id].js index 80f6efc5d..c54e8e193 100644 --- a/frontend/pages/settings/org/[id].js +++ b/frontend/pages/settings/org/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; import { diff --git a/frontend/pages/settings/personal/[id].js b/frontend/pages/settings/personal/[id].js index aa497bbfd..60dcba1be 100644 --- a/frontend/pages/settings/personal/[id].js +++ b/frontend/pages/settings/personal/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import Head from "next/head"; import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; diff --git a/frontend/pages/settings/project/[id].js b/frontend/pages/settings/project/[id].js index ec4864919..64ea96093 100644 --- a/frontend/pages/settings/project/[id].js +++ b/frontend/pages/settings/project/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; diff --git a/frontend/pages/signup.tsx b/frontend/pages/signup.tsx index a98e5cf29..e1a50406c 100644 --- a/frontend/pages/signup.tsx +++ b/frontend/pages/signup.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import ReactCodeInput from 'react-code-input'; import Head from 'next/head'; import Image from 'next/image'; diff --git a/frontend/pages/signupinvite.js b/frontend/pages/signupinvite.js index a41010aea..7686161a2 100644 --- a/frontend/pages/signupinvite.js +++ b/frontend/pages/signupinvite.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import Link from 'next/link'; diff --git a/frontend/pages/users/[id].js b/frontend/pages/users/[id].js index fe96ded3a..c1739b0a9 100644 --- a/frontend/pages/users/[id].js +++ b/frontend/pages/users/[id].js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import { useRouter } from 'next/router'; diff --git a/frontend/pages/vercel.js b/frontend/pages/vercel.js index adfffe77e..7b15769b1 100644 --- a/frontend/pages/vercel.js +++ b/frontend/pages/vercel.js @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import Head from "next/head"; import { useRouter } from "next/router"; const queryString = require("query-string"); diff --git a/frontend/pages/verify-email.tsx b/frontend/pages/verify-email.tsx index 7dfc1427e..ae2c9fff1 100644 --- a/frontend/pages/verify-email.tsx +++ b/frontend/pages/verify-email.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import Head from 'next/head'; import Image from 'next/image'; import Link from 'next/link'; From 5294fe9302cb4fdd26f0dcb485d000f7a37c0f4f Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Mon, 26 Dec 2022 14:58:07 -0500 Subject: [PATCH 12/18] Style updates --- frontend/components/dashboard/SideBar.tsx | 49 +--------------- frontend/ee/components/SecretVersionList.tsx | 44 ++++++++++++++ frontend/pages/dashboard/[id].tsx | 61 ++++++++++---------- 3 files changed, 77 insertions(+), 77 deletions(-) create mode 100644 frontend/ee/components/SecretVersionList.tsx diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index f8d6e9eba..32568acd0 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -1,9 +1,9 @@ import { useState } from 'react'; -import { faDotCircle, faRotateLeft, faX } from '@fortawesome/free-solid-svg-icons'; +import { faX } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import SecretVersionList from 'ee/components/SecretVersionList'; import Button from '../basic/buttons/Button'; -import ListBox from '../basic/Listbox'; import Toggle from '../basic/Toggle'; import DashboardInputField from './DashboardInputField'; import GenerateSecretMenu from './GenerateSecretMenu'; @@ -64,7 +64,6 @@ const SideBar = ({ setSharedToHide }: SideBarProps) => { const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal")); - console.log("sidebar", data, data.map(secret => secret.type).includes("personal")) return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'> <div className='h-min overflow-y-auto'> @@ -144,49 +143,7 @@ const SideBar = ({ isFull={true} /> </div> */} - <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'> - <p className=''>Version History</p> - <div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none'> - <div className='h-48 overflow-y-scroll overflow-x-none'> - <div className='flex flex-row'> - <div className='pr-1 flex flex-col items-center'> - <div className='p-1'><FontAwesomeIcon icon={faDotCircle} /></div> - <div className='w-0 h-full border-l mt-1'></div> - </div> - <div className='flex flex-col w-full max-w-[calc(100%-2.3rem)]'> - <div className='pr-2 pt-1'>Current</div> - <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Key:</span>{data[0].key}</p></div> - <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Value:</span>{data[0].value}</p></div> - <div className='pb-1'><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Visibility:</span>{'shared'}</p></div> - </div> - </div> - <div className='flex flex-row'> - <div className='pr-1 flex flex-col items-center'> - <div className='cursor-pointer p-1 hover:bg-bunker-500 rounded-md'><FontAwesomeIcon icon={faRotateLeft} /></div> - <div className='w-0 h-full border-l'></div> - </div> - <div className='flex flex-col max-w-[calc(100%-2.3rem)]'> - <div className='pr-2 pt-1'>12/22/2022 12:36 EST</div> - <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Key:</span> KeyKeyKey</div> - <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Value:</span> ValueValueValue</div> - <div className='pb-1'><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Visibility:</span>{'shared'}</p></div> - </div> - </div> - <div className='flex flex-row'> - <div className='pr-1 flex flex-col items-center'> - <div className='cursor-pointer p-1 hover:bg-bunker-500 rounded-md'><FontAwesomeIcon icon={faRotateLeft} /></div> - <div className='w-0 h-full border-l'></div> - </div> - <div className='flex flex-col max-w-[calc(100%-2.3rem)]'> - <div className='pr-2 pt-1'>12/21/2022 09:11 EST</div> - <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Key:</span> KeyKey</div> - <div className='w-full pr-2'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Value:</span> ValueValue</div> - <div className='pb-1'><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-300/20 mr-1.5'>Visibility:</span>{'shared'}</p></div> - </div> - </div> - </div> - </div> - </div> + <SecretVersionList /> <div className={`relative mt-4 px-4 pt-4`}> <div className='flex flex-row justify-between'> <p className='text-sm text-bunker-300'>Comments & notes</p> diff --git a/frontend/ee/components/SecretVersionList.tsx b/frontend/ee/components/SecretVersionList.tsx new file mode 100644 index 000000000..a2147c2af --- /dev/null +++ b/frontend/ee/components/SecretVersionList.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; +import { faCircle, faDotCircle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface SecretVersionListProps {} + +const versionData = [{ + value: "Value1", + date: "Date1", + user: "vlad@infisical.com" +}, { + value: "Value2", + date: "Date2", + user: "tony@infisical.com" +}] + +/** + * @returns a list of the versions for a specific secret + */ +const SecretVersionList = () => { + return <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'> + <p className=''>Version History</p> + <div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none'> + <div className='h-48 overflow-y-scroll overflow-x-none'> + {versionData.map((version, index) => + <div key={index} className='flex flex-row'> + <div className='pr-1 flex flex-col items-center'> + <div className='p-1'><FontAwesomeIcon icon={index == 0 ? faDotCircle : faCircle} /></div> + <div className='w-0 h-full border-l mt-1'></div> + </div> + <div className='flex flex-col w-full max-w-[calc(100%-2.3rem)]'> + <div className='pr-2 pt-1'>{version.date}</div> + <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5'>Value:</span>{version.value}</p></div> + <div className=''><p className='break-words'><span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5'>Updated by:</span>{version.user}</p></div> + </div> + </div> + )} + </div> + </div> +</div> +}; + +export default SecretVersionList; diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index e4b692691..be02c24de 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -553,40 +553,39 @@ export default function Dashboard() { </div> </div> {data?.length !== 0 ? ( - <div - id="dataall" - className="flex flex-col max-h-40 grow max-h-[calc(100vh-240px)] w-full overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar" - > - <div - className={`bg-white/5 mt-1 mb-1 rounded-md pb-2 max-w-5xl overflow-visible`} - > - <div id="data1" className="px-1 pt-2"> - {data?.filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( - <KeyPair - key={keyPair.id} - keyPair={keyPair} - deleteRow={deleteCertainRow} - modifyValue={listenChangeValue} - modifyKey={listenChangeKey} - isBlurred={blurred} - isDuplicate={findDuplicates(data?.map((item) => item.key + item.type))?.includes(keyPair.key + keyPair.type)} - toggleSidebar={toggleSidebar} - sidebarSecretId={sidebarSecretId} + <div className="flex flex-col w-full mt-1 mb-2"> + <div className='bg-mineshaft-800 rounded-md px-2 py-2 max-w-5xl'> + <div + className={`mt-1 max-h-[calc(100vh-280px)] overflow-hidden overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar`} + > + <div className="px-1 pt-2"> + {data?.filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => ( + <KeyPair + key={keyPair.id} + keyPair={keyPair} + deleteRow={deleteCertainRow} + modifyValue={listenChangeValue} + modifyKey={listenChangeKey} + isBlurred={blurred} + isDuplicate={findDuplicates(data?.map((item) => item.key + item.type))?.includes(keyPair.key + keyPair.type)} + toggleSidebar={toggleSidebar} + sidebarSecretId={sidebarSecretId} + /> + ))} + </div> + <div className="w-full max-w-5xl px-2 pt-2"> + <DropZone + setData={addData} + setErrorDragAndDrop={setErrorDragAndDrop} + createNewFile={addRow} + errorDragAndDrop={errorDragAndDrop} + setButtonReady={setButtonReady} + keysExist={true} + numCurrentRows={data.length} /> - ))} + </div> </div> </div> - <div className="w-full max-w-5xl"> - <DropZone - setData={addData} - setErrorDragAndDrop={setErrorDragAndDrop} - createNewFile={addRow} - errorDragAndDrop={errorDragAndDrop} - setButtonReady={setButtonReady} - keysExist={true} - numCurrentRows={data.length} - /> - </div> </div> ) : ( <div className="flex flex-col items-center justify-center h-full text-xl text-gray-400 max-w-5xl mt-28"> From 454d1d304a37c54e1051d852d7a3d1a5c5c88788 Mon Sep 17 00:00:00 2001 From: mv-turtle <78047717+mv-turtle@users.noreply.github.com> Date: Mon, 26 Dec 2022 15:10:27 -0500 Subject: [PATCH 13/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3851b3c53..292f5fe94 100644 --- a/README.md +++ b/README.md @@ -321,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of <!-- prettier-ignore-start --> <!-- markdownlint-disable --> -<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a> +<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a> From 9e28ba9b84deede37a47523c2703b483271f7b98 Mon Sep 17 00:00:00 2001 From: Maidul Islam <maidul98@gmail.com> Date: Mon, 26 Dec 2022 15:49:48 -0500 Subject: [PATCH 14/18] Add secret override flag to CLI --- cli/packages/cmd/run.go | 12 ++++++++++ cli/packages/models/cli.go | 1 + cli/packages/util/secrets.go | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 7518fe98d..9de8190ad 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -60,6 +60,13 @@ var runCmd = &cobra.Command{ return } + secretOverriding, err := cmd.Flags().GetBool("secret-overriding") + if err != nil { + log.Errorln("Unable to parse the secret-overriding flag") + log.Debugln(err) + return + } + shouldExpandSecrets, err := cmd.Flags().GetBool("expand") if err != nil { log.Errorln("Unable to parse the substitute flag") @@ -84,6 +91,10 @@ var runCmd = &cobra.Command{ secrets = util.SubstituteSecrets(secrets) } + if secretOverriding { + secrets = util.OverrideWithPersonalSecrets(secrets) + } + if cmd.Flags().Changed("command") { command := cmd.Flag("command").Value.String() err = executeMultipleCommandWithEnvs(command, secrets) @@ -108,6 +119,7 @@ func init() { runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") + runCmd.Flags().Bool("secret-overriding", true, "If two secrets are found with the same name, the one that is of type personal will be prioritized") runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")") } diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index 0d1f96a05..8ba1c4627 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -17,6 +17,7 @@ type ConfigFile struct { type SingleEnvironmentVariable struct { Key string `json:"key"` Value string `json:"value"` + Type string `json:"type"` } type WorkspaceConfigFile struct { diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index c127111b1..f88aed96b 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -14,6 +14,9 @@ import ( "golang.org/x/crypto/nacl/box" ) +const PERSONAL_SECRET_TYPE_NAME = "personal" +const SHARED_SECRET_TYPE_NAME = "shared" + func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, workspace models.WorkspaceConfigFile, userCreds models.UserCredentials) (listOfSecrets []models.SingleEnvironmentVariable, err error) { var pullSecretsRequestResponse models.PullSecretsResponse response, err := httpClient. @@ -78,6 +81,7 @@ func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, env := models.SingleEnvironmentVariable{ Key: string(plainTextKey), Value: string(plainTextValue), + Type: string(secret.Type), } listOfEnv = append(listOfEnv, env) @@ -187,6 +191,7 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, env := models.SingleEnvironmentVariable{ Key: string(plainTextKey), Value: string(plainTextValue), + Type: string(secret.Type), } listOfEnv = append(listOfEnv, env) @@ -335,9 +340,48 @@ func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.Sing expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{ Key: secret.Key, Value: expandedVariable, + Type: secret.Type, }) } return expandedSecrets } + +// if two secrets with the same name are found, the one that has type `personal` will be in the returned list +func OverrideWithPersonalSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable { + personalSecret := make(map[string]models.SingleEnvironmentVariable) + sharedSecret := make(map[string]models.SingleEnvironmentVariable) + secretsToReturn := []models.SingleEnvironmentVariable{} + + for _, secret := range secrets { + if secret.Type == PERSONAL_SECRET_TYPE_NAME { + personalSecret[secret.Key] = models.SingleEnvironmentVariable{ + Key: secret.Key, + Value: secret.Value, + Type: secret.Type, + } + } + + if secret.Type == SHARED_SECRET_TYPE_NAME { + sharedSecret[secret.Key] = models.SingleEnvironmentVariable{ + Key: secret.Key, + Value: secret.Value, + Type: secret.Type, + } + } + } + + for _, secret := range secrets { + personalValue, personalExists := personalSecret[secret.Key] + sharedValue, sharedExists := sharedSecret[secret.Key] + + if personalExists && sharedExists || personalExists && !sharedExists { + secretsToReturn = append(secretsToReturn, personalValue) + } else { + secretsToReturn = append(secretsToReturn, sharedValue) + } + } + + return secretsToReturn +} From 44da5da3008a74b7c6f8c7081c988c78cd3cf1dd Mon Sep 17 00:00:00 2001 From: Maidul Islam <maidul98@gmail.com> Date: Mon, 26 Dec 2022 15:50:09 -0500 Subject: [PATCH 15/18] update CLI version --- cli/packages/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index 6a0bdb80c..3aa9c5658 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{ Short: "Infisical CLI is used to inject environment variables into any process", Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`, CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, - Version: "0.1.14", + Version: "0.1.15", } // Execute adds all child commands to the root command and sets flags appropriately. From 79ddada53727b601382690750f0553cd8246ebc3 Mon Sep 17 00:00:00 2001 From: Maidul Islam <maidul98@gmail.com> Date: Mon, 26 Dec 2022 16:00:49 -0500 Subject: [PATCH 16/18] Add secret override to docs --- docs/cli/commands/run.mdx | 1 + docs/cli/usage.mdx | 8 +------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/cli/commands/run.mdx b/docs/cli/commands/run.mdx index 2c65ef53e..4afd7586d 100644 --- a/docs/cli/commands/run.mdx +++ b/docs/cli/commands/run.mdx @@ -34,3 +34,4 @@ Inject environment variables from the platform into an application process. | `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | None | | `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | | `--command` | Pass secrets into chained commands (e.g., `"first-command && second-command; more-commands..."`) | None | +| `--secret-overriding`| Prioritizes personal secrets with the same name over shared secrets | `true` | diff --git a/docs/cli/usage.mdx b/docs/cli/usage.mdx index aac5c1b74..fe08b60b3 100644 --- a/docs/cli/usage.mdx +++ b/docs/cli/usage.mdx @@ -38,13 +38,7 @@ infisical init infisical run -- [your application start command] ``` -Options you can specify: - -| Option | Description | Default value | -| ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | -| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` | -| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` | -| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | +View all available options for `run` command [here](./commands/run) ## Examples: From dcbf8525bd3648510da7f7fb65735d9fbd3c2060 Mon Sep 17 00:00:00 2001 From: Maidul Islam <maidul98@gmail.com> Date: Mon, 26 Dec 2022 16:01:03 -0500 Subject: [PATCH 17/18] update secret override desc --- cli/packages/cmd/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index 9de8190ad..6e0133c8e 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -119,7 +119,7 @@ func init() { runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") - runCmd.Flags().Bool("secret-overriding", true, "If two secrets are found with the same name, the one that is of type personal will be prioritized") + runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets with the same name over shared secrets") runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")") } From 0a108cbf076ce41cc71f4dffb3a3233bde45a162 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako <matsiiako@gmail.com> Date: Mon, 26 Dec 2022 16:48:27 -0500 Subject: [PATCH 18/18] Removed version history for now --- frontend/components/dashboard/SideBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/components/dashboard/SideBar.tsx b/frontend/components/dashboard/SideBar.tsx index 32568acd0..60a4641fe 100644 --- a/frontend/components/dashboard/SideBar.tsx +++ b/frontend/components/dashboard/SideBar.tsx @@ -143,7 +143,6 @@ const SideBar = ({ isFull={true} /> </div> */} - <SecretVersionList /> <div className={`relative mt-4 px-4 pt-4`}> <div className='flex flex-row justify-between'> <p className='text-sm text-bunker-300'>Comments & notes</p>