Skip to content

Commit

Permalink
Merge pull request #169 from Infisical/dashboard-sidebar
Browse files Browse the repository at this point in the history
Added dashboard sidebar
  • Loading branch information
vmatsiiako authored Dec 26, 2022
2 parents dcbf852 + 740100a commit 0e78336
Show file tree
Hide file tree
Showing 11 changed files with 724 additions and 378 deletions.
81 changes: 81 additions & 0 deletions frontend/components/basic/Toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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;
sharedToHide: string[];
setSharedToHide: (values: string[]) => void;
}

/**
* 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
* @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,
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);
}}
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>
)
}
9 changes: 5 additions & 4 deletions frontend/components/basic/buttons/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type ButtonProps = {
size: string;
icon?: IconProp;
active?: boolean;
iconDisabled?: string;
iconDisabled?: IconProp;
textDisabled?: string;
type?: ButtonHTMLAttributes<any>['type'];
};
Expand Down Expand Up @@ -73,15 +73,16 @@ 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"
);

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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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"
Expand Down
32 changes: 21 additions & 11 deletions frontend/components/dashboard/DashboardInputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +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
*/

Expand All @@ -33,7 +35,8 @@ const DashboardInputField = ({
type,
value,
blurred,
duplicates
isDuplicate,
override
}: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
Expand All @@ -45,8 +48,7 @@ const DashboardInputField = ({

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">
Expand All @@ -72,7 +74,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>
Expand All @@ -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-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)}
Expand All @@ -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-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`}
>
{value.split(REGEX).map((word, id) => {
if (word.match(REGEX) !== null) {
Expand Down Expand Up @@ -153,4 +159,8 @@ const DashboardInputField = ({
return <>Something Wrong</>;
};

export default 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.isDuplicate === next.isDuplicate;
}

export default memo(DashboardInputField, inputPropsAreEqual);
93 changes: 93 additions & 0 deletions frontend/components/dashboard/GenerateSecretMenu.tsx
Original file line number Diff line number Diff line change
@@ -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 = ({ 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">
<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='py-1 px-2 rounded-md bg-bunker-800 hover:bg-bunker-500'>
<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(''),
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"
>
<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;
Loading

2 comments on commit 0e78336

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for backend

St.
Category Percentage Covered / Total
🟡 Statements 68.75% 44/64
🔴 Branches 0% 0/5
🔴 Functions 50% 1/2
🟡 Lines 69.84% 44/63

Test suite run success

1 tests passing in 1 suite.

Report generated by 🧪jest coverage report action from 0e78336

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for backend

St.
Category Percentage Covered / Total
🟡 Statements 68.75% 44/64
🔴 Branches 0% 0/5
🔴 Functions 50% 1/2
🟡 Lines 69.84% 44/63

Test suite run success

1 tests passing in 1 suite.

Report generated by 🧪jest coverage report action from 0e78336

Please sign in to comment.