Refactored dashboard to TS - still some bugs and inefficiencies

This commit is contained in:
Vladyslav Matsiiako
2022-12-25 00:33:37 -05:00
parent 205bf70861
commit d89af29070
9 changed files with 210 additions and 152 deletions
-1
View File
@@ -28,7 +28,6 @@ interface ToggleProps {
* @returns * @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 }: ToggleProps): JSX.Element {
console.log(755, pos, enabled)
return ( return (
<Switch <Switch
checked={enabled} checked={enabled}
+1 -1
View File
@@ -16,7 +16,7 @@ type ButtonProps = {
size: string; size: string;
icon?: IconProp; icon?: IconProp;
active?: boolean; active?: boolean;
iconDisabled?: string; iconDisabled?: IconProp;
textDisabled?: string; textDisabled?: string;
}; };
@@ -3,6 +3,16 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 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 * This is the notification that pops up at the bottom right when a user performs a certain action
* @param {object} org * @param {object} org
@@ -23,16 +33,16 @@ export default function BottonRightPopup({
textLine1, textLine1,
textLine2, textLine2,
setCheckDocsPopUpVisible, setCheckDocsPopUpVisible,
}) { }: PopupProps): JSX.Element {
return ( return (
<div <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" 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="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 className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
<div>{titleText}</div> <div>{titleText}</div>
<div class="ml-2.5">{emoji}</div> <div className="ml-2.5">{emoji}</div>
</div> </div>
<button <button
className="mt-1" className="mt-1"
@@ -44,14 +54,14 @@ export default function BottonRightPopup({
/> />
</button> </button>
</div> </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} {textLine1}
</div> </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"> <div className="flex flex-row px-6 w-full">
{/*eslint-disable-next-line react/jsx-no-target-blank */} {/*eslint-disable-next-line react/jsx-no-target-blank */}
<a <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} href={buttonLink}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
@@ -87,7 +87,7 @@ const DashboardInputField = ({
<div <div
className={`group relative whitespace-pre flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`} 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 <input
value={value} value={value}
onChange={(e) => onChangeHandler(e.target.value, position)} 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' ? '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`} 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`}
> >
@@ -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) * 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 * @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); const [randomStringLength, setRandomStringLength] = useState(32);
return <Menu as="div" className="relative inline-block text-left"> return <Menu as="div" className="relative inline-block text-left">
@@ -36,12 +36,12 @@ const GenerateSecretMenu = () => {
} else if (randomStringLength < 2) { } else if (randomStringLength < 2) {
setRandomStringLength(2); setRandomStringLength(2);
} else { } else {
// modifyValue( modifyValue(
// [...Array(randomStringLength)] [...Array(randomStringLength)]
// .map(() => Math.floor(Math.random() * 16).toString(16)) .map(() => Math.floor(Math.random() * 16).toString(16))
// .join(''), .join(''),
// keyPair.pos 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" 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"
+37 -23
View File
@@ -1,5 +1,5 @@
import { useState } from 'react'; 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from '../basic/buttons/Button'; import Button from '../basic/buttons/Button';
@@ -13,6 +13,7 @@ interface SecretProps {
key: string; key: string;
value: string; value: string;
pos: number; pos: number;
type: string;
id: string; id: string;
} }
@@ -24,13 +25,14 @@ interface OverrideProps {
} }
interface SideBarProps { interface SideBarProps {
toggleSidebar: (value: number) => void; toggleSidebar: (value: string) => void;
data: SecretProps[]; data: SecretProps[];
modifyKey: (value: string) => void; modifyKey: (value: string, position: number) => void;
modifyValue: (value: string) => void; modifyValue: (value: string, position: number) => void;
modifyVisibility: (value: string) => void;
addOverride: (value: OverrideProps) => void; addOverride: (value: OverrideProps) => void;
deleteOverride: (id: string) => 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 {SecretProps[]} obj.data - data of a certain key valeu pair
* @param {function} obj.modifyKey - function that modifies the secret key * @param {function} obj.modifyKey - function that modifies the secret key
* @param {function} obj.modifyValue - function that modifies the secret value * @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' * @returns the sidebar with 'secret's settings'
*/ */
const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility, addOverride, deleteOverride }: SideBarProps) => { const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, addOverride, deleteOverride, buttonReady, savePush }: SideBarProps) => {
const [overrideEnabled, setOverrideEnabled] = useState(false); 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'> 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"> <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> <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'/> <FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
</div> </div>
</div> </div>
@@ -64,21 +70,27 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility
blurred={false} blurred={false}
/> />
</div> </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> <p className='text-sm text-bunker-300'>Value</p>
<DashboardInputField <DashboardInputField
onChangeHandler={modifyValue} onChangeHandler={modifyValue}
type="value" type="value"
position={data[0].pos} position={data.filter(secret => secret.type == "shared")[0]?.pos}
value={data[0].value + "xxx" + data[0].pos} value={data.filter(secret => secret.type == "shared")[0]?.value}
duplicates={[]} duplicates={[]}
blurred={true} blurred={true}
/> />
<div className='absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50'> <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> </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'> <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'> <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> <p className='text-sm text-bunker-300'>Override value with a personal value</p>
<Toggle <Toggle
@@ -91,22 +103,22 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility
id={data[0].id} id={data[0].id}
deleteOverride={deleteOverride} deleteOverride={deleteOverride}
/> />
</div> </div>}
<div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}> <div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}>
<DashboardInputField <DashboardInputField
onChangeHandler={modifyValue} onChangeHandler={modifyValue}
type="value" type="value"
position={data[0].pos} position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos}
value={"ValueValueValue" + data[0].pos} value={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].value : data[0].value}
duplicates={[]} duplicates={[]}
blurred={true} blurred={true}
/> />
<div className='absolute right-3 top-[0.3rem] z-50'> <div className='absolute right-[0.57rem] top-[0.3rem] z-50'>
<GenerateSecretMenu /> <GenerateSecretMenu modifyValue={modifyValue} position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos} />
</div> </div>
</div> </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> <p className='text-sm text-bunker-200'>Group</p>
<ListBox <ListBox
selected={"Database Secrets"} selected={"Database Secrets"}
@@ -114,7 +126,7 @@ const SideBar = ({ toggleSidebar, data, modifyKey, modifyValue, modifyVisibility
data={["Group1"]} data={["Group1"]}
isFull={true} isFull={true}
/> />
</div> </div> */}
<div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'> <div className='w-full h-52 px-4 mt-4 text-sm text-bunker-300 overflow-x-none'>
<p className=''>Version History</p> <p className=''>Version History</p>
<div className='p-1 rounded-md bg-bunker-800 border border-mineshaft-500 overflow-x-none'> <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>
</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 <Button
onButtonPressed={() => console.log('Saved')}
text="Save Changes" text="Save Changes"
onButtonPressed={savePush}
color="primary" color="primary"
size="md" size="md"
active={buttonReady}
textDisabled="Saved"
/> />
</div> </div>
</div> </div>
@@ -39,7 +39,7 @@ const getSecretsForProject = async ({
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY'); 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) { if (file.key) {
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
@@ -97,7 +97,7 @@ const getSecretsForProject = async ({
} catch (error) { } catch (error) {
console.log('Something went wrong during accessing or decripting secrets.'); console.log('Something went wrong during accessing or decripting secrets.');
} }
return true; return [];
}; };
export default getSecretsForProject; export default getSecretsForProject;
@@ -51,7 +51,7 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
iv: ivKey, iv: ivKey,
tag: tagKey, tag: tagKey,
} = encryptSymmetric({ } = encryptSymmetric({
plaintext: key, plaintext: key.slice(1),
key: randomBytes, key: randomBytes,
}); });
@@ -65,13 +65,13 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
key: randomBytes, 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 { return {
ciphertextKey, ciphertextKey,
ivKey, ivKey,
tagKey, tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"), hashKey: crypto.createHash("sha256").update(key.slice(1)).digest("hex"),
ciphertextValue, ciphertextValue,
ivValue, ivValue,
tagValue, tagValue,
@@ -24,7 +24,7 @@ import BottonRightPopup from '~/components/basic/popups/BottomRightPopup';
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider'; import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
import DashboardInputField from '~/components/dashboard/DashboardInputField'; import DashboardInputField from '~/components/dashboard/DashboardInputField';
import DropZone from '~/components/dashboard/DropZone'; 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 NavHeader from '~/components/navigation/NavHeader';
import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject'; import getSecretsForProject from '~/components/utilities/secrets/getSecretsForProject';
import pushKeys from '~/components/utilities/secrets/pushKeys'; import pushKeys from '~/components/utilities/secrets/pushKeys';
@@ -36,6 +36,26 @@ import checkUserAction from '../api/userActions/checkUserAction';
import registerUserAction from '../api/userActions/registerUserAction'; import registerUserAction from '../api/userActions/registerUserAction';
import getWorkspaces from '../api/workspace/getWorkspaces'; 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 * This component represent a single row for an environemnt variable on the dashboard
* @param {object} obj * @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.deleteRow - a function to delete a certain keyPair
* @param {function} obj.modifyKey - modify the key of a certain environment variable * @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.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 {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.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 * @returns
*/ */
const KeyPair = ({ const KeyPair = ({
@@ -53,16 +74,20 @@ const KeyPair = ({
deleteRow, deleteRow,
modifyKey, modifyKey,
modifyValue, modifyValue,
modifyVisibility,
isBlurred, isBlurred,
duplicates, duplicates,
toggleSidebar, toggleSidebar,
sidebarSecretNumber sidebarSecretId
}) => { }: KeyPairProps) => {
return ( 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"> <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="min-w-xl w-96">
<div className="flex pr-1 items-center 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 <DashboardInputField
@@ -71,6 +96,7 @@ const KeyPair = ({
position={keyPair.pos} position={keyPair.pos}
value={keyPair.key} value={keyPair.key}
duplicates={duplicates} duplicates={duplicates}
blurred={false}
/> />
</div> </div>
</div> </div>
@@ -82,11 +108,12 @@ const KeyPair = ({
position={keyPair.pos} position={keyPair.pos}
value={keyPair.value} value={keyPair.value}
blurred={isBlurred} blurred={isBlurred}
override={keyPair.value == "user1234" && true} override={keyPair.type == "personal"}
duplicates={[]}
/> />
</div> </div>
</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 <FontAwesomeIcon
className="text-gray-300 px-2.5 text-lg mt-0.5" className="text-gray-300 px-2.5 text-lg mt-0.5"
icon={faEllipsis} 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) * This is the main component for the dashboard (aka the screen with all the encironemnt variable & secrets)
* @returns * @returns
*/ */
export default function Dashboard() { export default function Dashboard() {
const [data, setData] = useState(); const [data, setData] = useState<SecretDataProps[] | null>();
const [fileState, setFileState] = useState([]); const [fileState, setFileState] = useState<SecretDataProps[]>([]);
const [buttonReady, setButtonReady] = useState(false); const [buttonReady, setButtonReady] = useState(false);
const router = useRouter(); const router = useRouter();
const [workspaceId, setWorkspaceId] = useState(''); const [workspaceId, setWorkspaceId] = useState('');
@@ -132,7 +178,7 @@ export default function Dashboard() {
const [sortMethod, setSortMethod] = useState('alphabetical'); const [sortMethod, setSortMethod] = useState('alphabetical');
const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false); const [checkDocsPopUpVisible, setCheckDocsPopUpVisible] = useState(false);
const [hasUserEverPushed, setHasUserEverPushed] = useState(false); const [hasUserEverPushed, setHasUserEverPushed] = useState(false);
const [sidebarSecretNumber, toggleSidebar] = useState(-1); const [sidebarSecretId, toggleSidebar] = useState("None");
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
@@ -155,7 +201,7 @@ export default function Dashboard() {
useEffect(() => { useEffect(() => {
const warningText = const warningText =
'Do you want to save your results before leaving this page?'; 'Do you want to save your results before leaving this page?';
const handleWindowClose = (e) => { const handleWindowClose = (e: any) => {
if (!buttonReady) return; if (!buttonReady) return;
e.preventDefault(); e.preventDefault();
return (e.returnValue = warningText); return (e.returnValue = warningText);
@@ -171,18 +217,18 @@ export default function Dashboard() {
/** /**
* Reorder rows alphabetically or in the opprosite order * Reorder rows alphabetically or in the opprosite order
*/ */
const reorderRows = (dataToReorder) => { const reorderRows = (dataToReorder: SecretDataProps[] | 1) => {
setSortMethod((prevSort) => setSortMethod((prevSort) =>
prevSort == 'alphabetical' ? '-alphabetical' : 'alphabetical' prevSort == 'alphabetical' ? '-alphabetical' : 'alphabetical'
); );
sortValuesHandler(dataToReorder, ""); sortValuesHandler(dataToReorder, undefined);
}; };
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
let userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
const listWorkspaces = userWorkspaces.map((workspace) => workspace._id); const listWorkspaces = userWorkspaces.map((workspace) => workspace._id);
if ( if (
!listWorkspaces.includes(router.asPath.split('/')[2].split('?')[0]) !listWorkspaces.includes(router.asPath.split('/')[2].split('?')[0])
@@ -194,31 +240,31 @@ export default function Dashboard() {
router.push(router.asPath.split('?')[0] + '?' + env); router.push(router.asPath.split('?')[0] + '?' + env);
} }
setBlurred(true); setBlurred(true);
setWorkspaceId(router.query.id); setWorkspaceId(String(router.query.id));
const dataToSort = await getSecretsForProject({ const dataToSort = await getSecretsForProject({
env, env,
setFileState, setFileState,
setIsKeyAvailable, setIsKeyAvailable,
setData, setData,
workspaceId: router.query.id workspaceId: String(router.query.id)
}); });
reorderRows(dataToSort); reorderRows(dataToSort);
const user = await getUser(); const user = await getUser();
setIsNew( setIsNew(
(Date.parse(new Date()) - Date.parse(user.createdAt)) / 60000 < 3 (Date.parse(String(new Date())) - Date.parse(user.createdAt)) / 60000 < 3
? true ? true
: false : false
); );
let userAction = await checkUserAction({ const userAction = await checkUserAction({
action: 'first_time_secrets_pushed' action: 'first_time_secrets_pushed'
}); });
setHasUserEverPushed(userAction ? true : false); setHasUserEverPushed(userAction ? true : false);
} catch (error) { } catch (error) {
console.log('Error', error); console.log('Error', error);
setData([]); setData(undefined);
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -227,10 +273,10 @@ export default function Dashboard() {
const addRow = () => { const addRow = () => {
setIsNew(false); setIsNew(false);
setData([ setData([
...data, ...data!,
{ {
id: guidGenerator(), id: guidGenerator(),
pos: data.length, pos: data!.length,
key: '', key: '',
value: '', value: '',
type: 'shared' 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 * This function add an ovverrided version of a certain secret to the current user
* @param {object} obj * @param {object} obj
@@ -246,10 +299,10 @@ export default function Dashboard() {
* @param {string} obj.value - value of this secret * @param {string} obj.value - value of this secret
* @param {string} obj.pos - position of this secret on the dashboard * @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); setIsNew(false);
const tempdata = [ const tempdata: SecretDataProps[] | 1 = [
...data, ...data!,
{ {
id: id, id: id,
pos: pos, pos: pos,
@@ -261,54 +314,55 @@ export default function Dashboard() {
sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical"); sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical");
}; };
const deleteRow = (id) => { const deleteRow = (id: string) => {
setButtonReady(true); 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 * This function deleted the override of a certain secrer
* @param {string} id - id of a secret to be deleted * @param {string} id - id of a secret to be deleted
*/ */
const deleteOverride = (id) => { const deleteOverride = (id: string) => {
setButtonReady(true); 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) => { setData((oldData) => {
oldData[pos].value = value; oldData![pos].value = value;
return [...oldData]; return [...oldData!];
}); });
setButtonReady(true); setButtonReady(true);
}; };
const modifyKey = (value, pos) => { const modifyKey = (value: string, pos: number) => {
setData((oldData) => { setData((oldData) => {
oldData[pos].key = value; oldData![pos].key = value;
return [...oldData]; return [...oldData!];
}); });
setButtonReady(true); setButtonReady(true);
}; };
const modifyVisibility = (value, pos) => { const modifyVisibility = (value: "shared" | "personal", pos: number) => {
setData((oldData) => { setData((oldData) => {
oldData[pos].type = value; oldData![pos].type = value;
return [...oldData]; return [...oldData!];
}); });
setButtonReady(true); setButtonReady(true);
}; };
// For speed purposes and better perforamance, we are using useCallback // 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); modifyValue(value, pos);
}, []); }, []);
const listenChangeKey = useCallback((value, pos) => { const listenChangeKey = useCallback((value: string, pos: number) => {
modifyKey(value, pos); modifyKey(value, pos);
}, []); }, []);
const listenChangeVisibility = useCallback((value, pos) => { const listenChangeVisibility = useCallback((value: "shared" | "personal", pos: number) => {
modifyVisibility(value, pos); modifyVisibility(value, pos);
}, []); }, []);
@@ -317,21 +371,16 @@ export default function Dashboard() {
*/ */
const savePush = async () => { const savePush = async () => {
// Format the new object with environment variables // 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 // Checking if any of the secret keys start with a number - if so, don't do anything
const nameErrors = !Object.keys(obj) const nameErrors = !Object.keys(obj)
.map((key) => !isNaN(key.charAt(0))) .map((key) => !isNaN(Number(key[0].charAt(0))))
.every((v) => v === false); .every((v) => v === false);
const duplicatesExist = const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => [item.key, item.type])).length > 0;
data
?.map((item) => item.key)
.filter(
(item, index) => index !== data?.map((item) => item.key).indexOf(item)
).length > 0;
if (nameErrors) { if (nameErrors) {
return createNotification({ return createNotification({
@@ -347,9 +396,11 @@ export default function Dashboard() {
}); });
} }
console.log('pushing', obj)
// Once "Save changed is clicked", disable that button // Once "Save changed is clicked", disable that button
setButtonReady(false); 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 this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) { if (!hasUserEverPushed) {
@@ -358,8 +409,8 @@ export default function Dashboard() {
} }
}; };
const addData = (newData) => { const addData = (newData: SecretDataProps[]) => {
setData(data.concat(newData)); setData(data!.concat(newData));
setButtonReady(true); setButtonReady(true);
}; };
@@ -367,38 +418,39 @@ export default function Dashboard() {
setBlurred(!blurred); setBlurred(!blurred);
}; };
const sortValuesHandler = (dataToSort, specificSortMethod) => { const sortValuesHandler = (dataToSort: SecretDataProps[] | 1, specificSortMethod?: 'alphabetical' | '-alphabetical') => {
const howToSort = specificSortMethod != "" ? specificSortMethod : sortMethod const howToSort = specificSortMethod == undefined ? sortMethod : specificSortMethod;
const sortedData = (dataToSort != 1 ? dataToSort : data) const sortedData = (dataToSort != 1 ? dataToSort : data)!
.sort((a, b) => .sort((a, b) =>
howToSort == 'alphabetical' howToSort == 'alphabetical'
? a.key.localeCompare(b.key) ? a.key.localeCompare(b.key)
: b.key.localeCompare(a.key) : b.key.localeCompare(a.key)
) )
.map((item, index) => { .map((item: SecretDataProps, index: number) => {
return { return {
...item, ...item,
pos: index pos: index
}; };
}); });
console.log('override', sortedData)
setData(sortedData); setData(sortedData);
}; };
// This function downloads the secrets as a .env file // This function downloads the secrets as a .env file
const download = () => { const download = () => {
const file = data const file = data!
.map((item) => [item.key, item.value].join('=')) .map((item: SecretDataProps) => [item.key, item.value].join('='))
.join('\n'); .join('\n');
const blob = new Blob([file]); const blob = new Blob([file]);
const fileDownloadUrl = URL.createObjectURL(blob); const fileDownloadUrl = URL.createObjectURL(blob);
let alink = document.createElement('a'); const alink = document.createElement('a');
alink.href = fileDownloadUrl; alink.href = fileDownloadUrl;
alink.download = envMapping[env] + '.env'; alink.download = envMapping[env] + '.env';
alink.click(); alink.click();
}; };
const deleteCertainRow = (id) => { const deleteCertainRow = (id: string) => {
deleteRow(id); deleteRow(id);
}; };
@@ -406,15 +458,17 @@ export default function Dashboard() {
* This function copies the project id to the clipboard * This function copies the project id to the clipboard
*/ */
function copyToClipboard() { 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(); navigator.clipboard.writeText(copyText.value);
copyText.setSelectionRange(0, 99999); // For mobile devices
setProjectIdCopied(true);
navigator.clipboard.writeText(copyText.value); setTimeout(() => setProjectIdCopied(false), 2000);
}
setProjectIdCopied(true);
setTimeout(() => setProjectIdCopied(false), 2000);
} }
return data ? ( return data ? (
@@ -430,14 +484,15 @@ export default function Dashboard() {
/> />
</Head> </Head>
<div className="flex flex-row"> <div className="flex flex-row">
{sidebarSecretNumber != -1 && <SideBar {sidebarSecretId != "None" && <SideBar
toggleSidebar={toggleSidebar} toggleSidebar={toggleSidebar}
data={data.filter(row => row.pos == sidebarSecretNumber)} data={data.filter((row: SecretDataProps) => row.id == sidebarSecretId)}
modifyKey={listenChangeKey} modifyKey={listenChangeKey}
modifyValue={listenChangeValue} modifyValue={listenChangeValue}
modifyVisibility={listenChangeVisibility}
addOverride={addOverride} addOverride={addOverride}
deleteOverride={deleteOverride} deleteOverride={deleteOverride}
buttonReady={buttonReady}
savePush={savePush}
/>} />}
<div className="w-full max-h-96 pb-2"> <div className="w-full max-h-96 pb-2">
<NavHeader pageName="Secrets" isProjectRelated={true} /> <NavHeader pageName="Secrets" isProjectRelated={true} />
@@ -461,7 +516,6 @@ export default function Dashboard() {
data={['Development', 'Staging', 'Production', 'Testing']} data={['Development', 'Staging', 'Production', 'Testing']}
// ref={useRef(123)} // ref={useRef(123)}
onChange={setEnv} onChange={setEnv}
className="z-40"
/> />
)} )}
</div> </div>
@@ -516,7 +570,6 @@ export default function Dashboard() {
data={['Development', 'Staging', 'Production', 'Testing']} data={['Development', 'Staging', 'Production', 'Testing']}
// ref={useRef(123)} // ref={useRef(123)}
onChange={setEnv} 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"> <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 <FontAwesomeIcon
@@ -599,17 +652,10 @@ export default function Dashboard() {
deleteRow={deleteCertainRow} deleteRow={deleteCertainRow}
modifyValue={listenChangeValue} modifyValue={listenChangeValue}
modifyKey={listenChangeKey} modifyKey={listenChangeKey}
modifyVisibility={listenChangeVisibility}
isBlurred={blurred} isBlurred={blurred}
duplicates={data duplicates={findDuplicates(data?.map((item) => [item.key, item.type]))}
?.map((item) => item.key)
.filter(
(item, index) =>
index !==
data?.map((item) => item.key).indexOf(item)
)}
toggleSidebar={toggleSidebar} toggleSidebar={toggleSidebar}
sidebarSecretNumber={sidebarSecretNumber} sidebarSecretId={sidebarSecretId}
/> />
))} ))}
</div> </div>
@@ -628,30 +674,19 @@ export default function Dashboard() {
</div> </div>
) : ( ) : (
<div className="flex flex-col items-center justify-center h-full text-xl text-gray-400 max-w-5xl mt-28"> <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" && {isKeyAvailable && (
fileState.message != undefined && ( <DropZone
<FontAwesomeIcon setData={setData}
className="text-7xl mb-8" setErrorDragAndDrop={setErrorDragAndDrop}
icon={faFolderOpen} createNewFile={addRow}
/> errorDragAndDrop={errorDragAndDrop}
)} setButtonReady={setButtonReady}
{(fileState.message == "There's nothing to pull" || numCurrentRows={data.length}
fileState.message == undefined) && keysExist={false}
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>
)} )}
{fileState.message == 'Access needed to pull the latest file' || {
// fileState.message == 'Access needed to pull the latest file' ||
(!isKeyAvailable && ( (!isKeyAvailable && (
<> <>
<FontAwesomeIcon <FontAwesomeIcon