Wired frontend to use the batch stucture

This commit is contained in:
Vladyslav Matsiiako
2023-02-18 00:02:52 -08:00
parent dbcd2b0988
commit b0744fd21d
10 changed files with 418 additions and 297 deletions

View File

@@ -3,8 +3,8 @@ import { Switch } from '@headlessui/react';
interface ToggleProps {
enabled: boolean;
setEnabled: (value: boolean) => void;
addOverride: (value: string | undefined, pos: number) => void;
pos: number;
addOverride: (value: string | undefined, id: string) => void;
id: string;
}
/**
@@ -13,18 +13,18 @@ interface ToggleProps {
* @param {boolean} obj.enabled - whether the toggle is turned on or off
* @param {function} obj.setEnabled - change the state of the toggle
* @param {function} obj.addOverride - a function that adds an override to a certain secret
* @param {number} obj.pos - position of a certain secret
* @param {number} obj.id - id of a certain secret
* @returns
*/
const Toggle = ({ enabled, setEnabled, addOverride, pos }: ToggleProps): JSX.Element => {
const Toggle = ({ enabled, setEnabled, addOverride, id }: ToggleProps): JSX.Element => {
return (
<Switch
checked={enabled}
onChange={() => {
if (enabled === false) {
addOverride('', pos);
addOverride('', id);
} else {
addOverride(undefined, pos);
addOverride(undefined, id);
}
setEnabled(!enabled);
}}

View File

@@ -45,19 +45,19 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
{t('dashboard:sidebar.delete-key-dialog.title')}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
<p className="text-sm text-bunker-300">
{t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')}
</p>
</div>
<div className="mt-6 flex justify-start">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-red-700 hover:bg-red-600 px-4 py-2 text-sm font-medium text-bunker-200 hover:text-white text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
>
Delete

View File

@@ -13,7 +13,7 @@ import { Tag } from 'public/data/frequentInterfaces';
* @param {function} obj.modifyTags - modify tags for a certain secret
* @param {Tag[]} obj.position - currently selected tags for a certain secret
*/
const AddTagsMenu = ({ allTags, currentTags, modifyTags, position }: { allTags: Tag[]; currentTags: Tag[]; modifyTags: (value: Tag[], position: number) => void; position: number; }) => {
const AddTagsMenu = ({ allTags, currentTags, modifyTags, id }: { allTags: Tag[]; currentTags: Tag[]; modifyTags: (value: Tag[], id: string) => void; id: string; }) => {
const router = useRouter();
return (
<Menu as="div" className="ml-2 relative inline-block text-left">
@@ -41,7 +41,7 @@ const AddTagsMenu = ({ allTags, currentTags, modifyTags, position }: { allTags:
<button
type="button"
className={`${currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? "opacity-30 cursor-default" : "hover:bg-mineshaft-700"} w-full text-left bg-mineshaft-800 px-2 py-0.5 text-bunker-200 rounded-sm flex items-center`}
onClick={() => {if (!currentTags?.map(currentTag => currentTag.name).includes(tag.name)) {modifyTags(currentTags.concat([tag]), position)}}}
onClick={() => {if (!currentTags?.map(currentTag => currentTag.name).includes(tag.name)) {modifyTags(currentTags.concat([tag]), id)}}}
>
{currentTags?.map(currentTag => currentTag.name).includes(tag.name) ? <FontAwesomeIcon icon={faCheckSquare} className="text-xs mr-2 text-primary"/> : <FontAwesomeIcon icon={faSquare} className="text-xs mr-2"/>} {tag.name}
</button>

View File

@@ -6,11 +6,11 @@ import { useTranslation } from 'next-i18next';
const CommentField = ({
comment,
modifyComment,
position
id
}: {
comment: string;
modifyComment: (value: string, posistion: number) => void;
position: number;
modifyComment: (value: string, id: string) => void;
id: string;
}) => {
const { t } = useTranslation();
@@ -20,7 +20,7 @@ const CommentField = ({
<textarea
className="placeholder:text-bunker-400 dark:[color-scheme:dark] h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
value={comment}
onChange={(e) => modifyComment(e.target.value, position)}
onChange={(e) => modifyComment(e.target.value, id)}
placeholder="Leave any comments here..."
/>
</div>

View File

@@ -9,15 +9,15 @@ import { PopoverObject } from '../v2/Popover/Popover';
const REGEX = /([$]{.*?})/g;
interface DashboardInputFieldProps {
position: number;
onChangeHandler: (value: string, position: number) => void;
id: string;
onChangeHandler: (value: string, id: string) => void;
value: string | undefined;
type: 'varName' | 'value' | 'comment';
blurred?: boolean;
isDuplicate?: boolean;
isCapitalized?: boolean;
overrideEnabled?: boolean;
modifyValueOverride?: (value: string | undefined, position: number) => void;
modifyValueOverride?: (value: string | undefined, id: string) => void;
isSideBarOpen?: boolean;
}
@@ -37,7 +37,7 @@ interface DashboardInputFieldProps {
*/
const DashboardInputField = ({
position,
id,
onChangeHandler,
type,
value,
@@ -72,7 +72,7 @@ const DashboardInputField = ({
}`}
>
<input
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, position)}
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)}
type={type}
value={value}
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${
@@ -105,9 +105,9 @@ const DashboardInputField = ({
<button type="button" onClick={() => {
if (modifyValueOverride) {
if (overrideEnabled === false) {
modifyValueOverride('', position);
modifyValueOverride('', id);
} else {
modifyValueOverride(undefined, position);
modifyValueOverride(undefined, id);
}
}
}}>
@@ -126,7 +126,7 @@ const DashboardInputField = ({
const error = startsWithNumber || isDuplicate;
return (
<PopoverObject text={value || ''} onChangeHandler={onChangeHandler} position={position}>
<PopoverObject text={value || ''} onChangeHandler={onChangeHandler} id={id}>
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
isSideBarOpen && 'bg-mineshaft-700 duration-200'
}`}>
@@ -157,7 +157,7 @@ const DashboardInputField = ({
)}
<input
value={value}
onChange={(e) => onChangeHandler(e.target.value, position)}
onChange={(e) => onChangeHandler(e.target.value, id)}
onScroll={syncScroll}
className={`${
blurred
@@ -175,10 +175,10 @@ const DashboardInputField = ({
} ${overrideEnabled ? 'text-primary-300' : 'text-gray-400'}
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? 'invisible' : 'visible'} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
>
{value?.split(REGEX).map((word, id) => {
{value?.split(REGEX).map((word) => {
if (word.match(REGEX) !== null) {
return (
<span className="ph-no-capture text-yellow" key={`${word}.${id + 1}`}>
<span className="ph-no-capture text-yellow" key={id}>
{word.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)}
@@ -231,7 +231,7 @@ function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInput
return (
prev.value === next.value &&
prev.type === next.type &&
prev.position === next.position &&
prev.id === next.id &&
prev.blurred === next.blurred &&
prev.isCapitalized === next.isCapitalized &&
prev.overrideEnabled === next.overrideEnabled &&

View File

@@ -10,10 +10,10 @@ import { Menu, Transition } from '@headlessui/react';
*/
const GenerateSecretMenu = ({
modifyValue,
position
id
}: {
modifyValue: (value: string, position: number) => void;
position: number;
modifyValue: (value: string, id: string) => void;
id: string;
}) => {
const [randomStringLength, setRandomStringLength] = useState(32);
const { t } = useTranslation();
@@ -51,7 +51,7 @@ const GenerateSecretMenu = ({
[...Array(randomStringLength)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join(''),
position
id
);
}
}}

View File

@@ -8,11 +8,11 @@ import { DeleteActionButton } from './DeleteActionButton';
interface KeyPairProps {
keyPair: SecretDataProps;
modifyKey: (value: string, position: number) => void;
modifyValue: (value: string, position: number) => void;
modifyValueOverride: (value: string | undefined, position: number) => void;
modifyComment: (value: string, position: number) => void;
modifyTags: (value: Tag[], position: number) => void;
modifyKey: (value: string, id: string) => void;
modifyValue: (value: string, id: string) => void;
modifyValueOverride: (value: string | undefined, id: string) => void;
modifyComment: (value: string, id: string) => void;
modifyTags: (value: Tag[], id: string) => void;
isBlurred: boolean;
isDuplicate: boolean;
toggleSidebar: (id: string) => void;
@@ -108,7 +108,7 @@ const KeyPair = ({
isCapitalized = {isCapitalized}
onChangeHandler={modifyKey}
type="varName"
position={keyPair.pos}
id={keyPair.id}
value={keyPair.key}
isDuplicate={isDuplicate}
overrideEnabled={keyPair.valueOverride !== undefined}
@@ -124,7 +124,7 @@ const KeyPair = ({
<DashboardInputField
onChangeHandler={keyPair.valueOverride !== undefined ? modifyValueOverride : modifyValue}
type="value"
position={keyPair.pos}
id={keyPair.id}
value={keyPair.valueOverride !== undefined ? keyPair.valueOverride : keyPair.value}
blurred={isBlurred}
overrideEnabled={keyPair.valueOverride !== undefined}
@@ -137,7 +137,7 @@ const KeyPair = ({
<DashboardInputField
onChangeHandler={modifyComment}
type="comment"
position={keyPair.pos}
id={keyPair.id}
value={keyPair.comment}
isDuplicate={isDuplicate}
isSideBarOpen={keyPair.id === sidebarSecretId}
@@ -149,11 +149,11 @@ const KeyPair = ({
{keyPair.tags?.map((tag, index) => (
index < 2 && <div key={keyPair.pos} className={`ml-2 px-1.5 ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.color} rounded-sm text-sm ${tagData.filter(tagDp => tagDp._id === tag._id)[0]?.colorText} flex items-center`}>
<span className='mb-0.5 cursor-default'>{tag.name}</span>
<FontAwesomeIcon icon={faXmark} className="ml-1 cursor-pointer p-1" onClick={() => modifyTags(keyPair.tags.filter(ttag => ttag._id !== tag._id), keyPair.pos)}/>
<FontAwesomeIcon icon={faXmark} className="ml-1 cursor-pointer p-1" onClick={() => modifyTags(keyPair.tags.filter(ttag => ttag._id !== tag._id), keyPair.id)}/>
</div>
))}
<AddTagsMenu allTags={tags} currentTags={keyPair.tags} modifyTags={modifyTags} position={keyPair.pos} />
<AddTagsMenu allTags={tags} currentTags={keyPair.tags} modifyTags={modifyTags} id={keyPair.id} />
</div>
</div>
<div

View File

@@ -33,10 +33,10 @@ export interface DeleteRowFunctionProps {
interface SideBarProps {
toggleSidebar: (value: string) => void;
data: SecretProps[];
modifyKey: (value: string, position: number) => void;
modifyValue: (value: string, position: number) => void;
modifyValueOverride: (value: string | undefined, position: number) => void;
modifyComment: (value: string, position: number) => void;
modifyKey: (value: string, id: string) => void;
modifyValue: (value: string, id: string) => void;
modifyValueOverride: (value: string | undefined, id: string) => void;
modifyComment: (value: string, id: string) => void;
buttonReady: boolean;
savePush: () => void;
sharedToHide: string[];
@@ -110,7 +110,7 @@ const SideBar = ({
<DashboardInputField
onChangeHandler={modifyKey}
type="varName"
position={data[0]?.pos}
id={data[0]?.id}
value={data[0]?.key}
isDuplicate={false}
blurred={false}
@@ -128,14 +128,14 @@ const SideBar = ({
<DashboardInputField
onChangeHandler={modifyValue}
type="value"
position={data[0].pos}
id={data[0].id}
value={data[0]?.value}
isDuplicate={false}
blurred
/>
</div>
<div className="absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50">
<GenerateSecretMenu modifyValue={modifyValue} position={data[0]?.pos} />
<GenerateSecretMenu modifyValue={modifyValue} id={data[0]?.id} />
</div>
</div>
) : (
@@ -154,7 +154,7 @@ const SideBar = ({
enabled={overrideEnabled}
setEnabled={setOverrideEnabled}
addOverride={modifyValueOverride}
pos={data[0]?.pos}
id={data[0]?.id}
/>
</div>
)}
@@ -167,14 +167,14 @@ const SideBar = ({
<DashboardInputField
onChangeHandler={modifyValueOverride}
type="value"
position={data[0]?.pos}
id={data[0]?.id}
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
isDuplicate={false}
blurred
/>
</div>
<div className="absolute right-[0.57rem] top-[0.3rem] z-50">
<GenerateSecretMenu modifyValue={modifyValueOverride} position={data[0]?.pos} />
<GenerateSecretMenu modifyValue={modifyValueOverride} id={data[0]?.id} />
</div>
</div>
</div>
@@ -182,7 +182,7 @@ const SideBar = ({
<CommentField
comment={data[0]?.comment}
modifyComment={modifyComment}
position={data[0]?.pos}
id={data[0]?.id}
/>
</div>
)}

View File

@@ -5,13 +5,13 @@ import * as Popover from '@radix-ui/react-popover';
type Props = {
children: any;
text: string;
onChangeHandler: (value: string, position: number) => void;
position: number;
onChangeHandler: (value: string, id: string) => void;
id: string;
};
export type PopoverProps = Props;
export const PopoverObject = ({children, text, onChangeHandler, position}: Props) => (
export const PopoverObject = ({children, text, onChangeHandler, id}: Props) => (
<Popover.Root>
<Popover.Trigger asChild className='data-[state=open]:outline data-[state=open]:outline-primary data-[state=closed]:hover:outline data-[state=closed]:hover:outline-mineshaft-400'>
{children}
@@ -26,7 +26,7 @@ export const PopoverObject = ({children, text, onChangeHandler, position}: Props
<div className="flex flex-col pt-2 dark">
<p className="text-bunker-200 text-[15px] leading-[0px] font-medium mb-5">Comment</p>
<textarea
onChange={(e) => onChangeHandler(e.target.value, position)}
onChange={(e) => onChangeHandler(e.target.value, id)}
// type={type}
value={text}
className='z-10 dark:[color-scheme:dark] peer h-[20rem] ph-no-capture bg-bunker-600 border border-mineshaft-500 rounded-md py-2.5 caret-bunker-200 text-sm px-2 w-full outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'

View File

@@ -30,6 +30,7 @@ import DropZone from '@app/components/dashboard/DropZone';
import KeyPair from '@app/components/dashboard/KeyPair';
import SideBar from '@app/components/dashboard/SideBar';
import NavHeader from '@app/components/navigation/NavHeader';
import { decryptAssymmetric, decryptSymmetric } from '@app/components/utilities/cryptography/crypto';
import guidGenerator from '@app/components/utilities/randomId';
import encryptSecrets from '@app/components/utilities/secrets/encryptSecrets';
import getSecretsForProject from '@app/components/utilities/secrets/getSecretsForProject';
@@ -48,6 +49,7 @@ import batchSecrets from '../api/files/batchSecrets';
import getUser from '../api/user/getUser';
import checkUserAction from '../api/userActions/checkUserAction';
import registerUserAction from '../api/userActions/registerUserAction';
import getLatestFileKey from '../api/workspace/getLatestFileKey';
import getWorkspaceEnvironments from '../api/workspace/getWorkspaceEnvironments';
import getWorkspaces from '../api/workspace/getWorkspaces';
import getWorkspaceTags from '../api/workspace/getWorkspaceTags';
@@ -95,6 +97,32 @@ interface SnapshotProps {
}[];
}
interface EncryptedSecretProps {
_id: string;
createdAt: string;
environment: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
type: 'personal' | 'shared';
tags: Tag[];
}
interface SecretProps {
key: string;
value: string | undefined;
type: 'personal' | 'shared';
comment: string;
id: string;
tags: Tag[];
}
/**
* this function finds the teh duplicates in an array
* @param arr - array of anything (e.g., with secret keys and types (personal/shared))
@@ -303,50 +331,50 @@ export default function Dashboard() {
);
};
const modifyValue = (value: string, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, value } : e)));
const modifyValue = (value: string, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, value } : e)));
setHasUnsavedChanges(true);
};
const modifyValueOverride = (valueOverride: string | undefined, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, valueOverride } : e)));
const modifyValueOverride = (valueOverride: string | undefined, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, valueOverride } : e)));
setHasUnsavedChanges(true);
};
const modifyKey = (key: string, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, key } : e)));
const modifyKey = (key: string, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, key } : e)));
setHasUnsavedChanges(true);
};
const modifyComment = (comment: string, pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, comment } : e)));
const modifyComment = (comment: string, id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, comment } : e)));
setHasUnsavedChanges(true);
};
const modifyTags = (tags: Tag[], pos: number) => {
setData((oldData) => oldData?.map((e) => (e.pos === pos ? { ...e, tags } : e)));
const modifyTags = (tags: Tag[], id: string) => {
setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, tags } : e)));
setHasUnsavedChanges(true);
};
// For speed purposes and better perforamance, we are using useCallback
const listenChangeValue = useCallback((value: string, pos: number) => {
modifyValue(value, pos);
const listenChangeValue = useCallback((value: string, id: string) => {
modifyValue(value, id);
}, []);
const listenChangeValueOverride = useCallback((value: string | undefined, pos: number) => {
modifyValueOverride(value, pos);
const listenChangeValueOverride = useCallback((value: string | undefined, id: string) => {
modifyValueOverride(value, id);
}, []);
const listenChangeKey = useCallback((value: string, pos: number) => {
modifyKey(value, pos);
const listenChangeKey = useCallback((value: string, id: string) => {
modifyKey(value, id);
}, []);
const listenChangeComment = useCallback((value: string, pos: number) => {
modifyComment(value, pos);
const listenChangeComment = useCallback((value: string, id: string) => {
modifyComment(value, id);
}, []);
const listenChangeTags = useCallback((value: Tag[], pos: number) => {
modifyTags(value, pos);
const listenChangeTags = useCallback((value: Tag[], id: string) => {
modifyTags(value, id);
}, []);
/**
@@ -354,238 +382,331 @@ export default function Dashboard() {
*/
// TODO(akhilmhdh): split and make it small
const savePush = async (dataToPush?: SecretDataProps[]) => {
setSaveLoading(true);
let newData: SecretDataProps[] | null | undefined;
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
if ((dataToPush ?? [])?.length > 0) {
newData = dataToPush;
} else {
newData = data;
}
try {
setSaveLoading(true);
let newData: SecretDataProps[] | null | undefined;
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
if ((dataToPush ?? [])?.length > 0) {
newData = dataToPush;
} else {
newData = data;
}
// Checking if any of the secret keys start with a number - if so, don't do anything
const nameErrors = !newData!
.map((secret) => !Number.isNaN(Number(secret.key.charAt(0))))
.every((v) => v === false);
const duplicatesExist =
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
// Checking if any of the secret keys start with a number - if so, don't do anything
const nameErrors = !newData!
.map((secret) => !Number.isNaN(Number(secret.key.charAt(0))))
.every((v) => v === false);
const duplicatesExist =
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
if (nameErrors) {
setSaveLoading(false);
return createNotification({
text: 'Solve all name errors before saving secrets.',
type: 'error'
});
}
if (nameErrors) {
setSaveLoading(false);
return createNotification({
text: 'Solve all name errors before saving secrets.',
type: 'error'
});
}
if (duplicatesExist) {
setSaveLoading(false);
return createNotification({
text: 'Remove duplicated secret names before saving.',
type: 'error'
});
}
if (duplicatesExist) {
setSaveLoading(false);
return createNotification({
text: 'Remove duplicated secret names before saving.',
type: 'error'
});
}
if (selectedEnv?.isWriteDenied) {
setSaveLoading(false);
return createNotification({
text: 'You are not allowed to edit this environment',
type: 'error'
});
}
if (selectedEnv?.isWriteDenied) {
setSaveLoading(false);
return createNotification({
text: 'You are not allowed to edit this environment',
type: 'error'
});
}
// Once "Save changes" is clicked, disable that button
setHasUnsavedChanges(false);
// Once "Save changes" is clicked, disable that button
setHasUnsavedChanges(false);
const secretsToBeDeleted = initialData!
.filter(
(initDataPoint) =>
!newData!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => secret.id);
console.log('delete', secretsToBeDeleted.length);
const secretsToBeAdded = newData!.filter(
(newDataPoint) =>
!initialData!.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
);
console.log('add', secretsToBeAdded.length);
const secretsToBeUpdated = newData!.filter((newDataPoint) =>
initialData!
.filter(
(initDataPoint) =>
newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !==
initDataPoint.value ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].comment !==
initDataPoint.comment ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !==
initDataPoint?.tags)
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
);
console.log('update', secretsToBeUpdated.length);
const newOverrides = newData!.filter(
(newDataPoint) => newDataPoint.valueOverride !== undefined
);
const initOverrides = initialData!.filter(
(initDataPoint) => initDataPoint.valueOverride !== undefined
);
const overridesToBeDeleted = initOverrides
.filter(
(initDataPoint) =>
!newOverrides!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => String(secret.idOverride));
console.log('override delete', overridesToBeDeleted.length);
const overridesToBeAdded = newOverrides!
.filter(
(newDataPoint) =>
!initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override add', overridesToBeAdded.length);
const overridesToBeUpdated = newOverrides!
.filter((newDataPoint) =>
initOverrides
const secretsToBeDeleted = initialData!
.filter(
(initDataPoint) =>
newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.valueOverride !== initDataPoint.valueOverride ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.comment !== initDataPoint.comment ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !== initDataPoint?.tags)
!newData!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override update', overridesToBeUpdated.length);
.map((secret) => secret.id);
console.log('delete', secretsToBeDeleted.length);
const requests: any = []; // TODO: fix any
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
console.log('DELETE: ', secretsToBeDeleted.concat(overridesToBeDeleted));
// await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) });
secretsToBeDeleted.concat(overridesToBeDeleted).forEach((_id: string) => {
requests.push({
method: 'DELETE',
secret: {
_id
}
});
});
}
if (selectedEnv && secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded),
workspaceId,
env: selectedEnv.slug
});
if (secrets) {
console.log('ADD: ', secrets);
// await addSecrets({ secrets, env: selectedEnv.slug, workspaceId });
secrets.forEach((secret) => {
requests.push({
method: 'POST',
secret: {
type: secret.type,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
tags: secret.tags
}
})
});
}
}
if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated),
workspaceId,
env: selectedEnv.slug
});
if (secrets) {
console.log('UPDATE: ', secrets);
// await updateSecrets({ secrets });
secrets.forEach((secret) => {
requests.push({
method: 'PATCH',
secret: {
_id: secret.id,
type: secret.type,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
tags: secret.tags
}
const secretsToBeAdded = newData!.filter(
(newDataPoint) =>
!initialData!.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
);
console.log('add', secretsToBeAdded.length);
const secretsToBeUpdated = newData!.filter((newDataPoint) =>
initialData!
.filter(
(initDataPoint) =>
newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !==
initDataPoint.value ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].comment !==
initDataPoint.comment ||
JSON.stringify(newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !==
JSON.stringify(initDataPoint?.tags))
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
);
console.log('update', secretsToBeUpdated.length);
const newOverrides = newData!.filter(
(newDataPoint) => newDataPoint.valueOverride !== undefined
);
const initOverrides = initialData!.filter(
(initDataPoint) => initDataPoint.valueOverride !== undefined
);
const overridesToBeDeleted = initOverrides
.filter(
(initDataPoint) =>
!newOverrides!.map((newDataPoint) => newDataPoint.id).includes(initDataPoint.id)
)
.map((secret) => String(secret.idOverride));
console.log('override delete', overridesToBeDeleted.length);
const overridesToBeAdded = newOverrides!
.filter(
(newDataPoint) =>
!initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override add', overridesToBeAdded.length);
const overridesToBeUpdated = newOverrides!
.filter((newDataPoint) =>
initOverrides
.filter(
(initDataPoint) =>
newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) &&
(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.valueOverride !== initDataPoint.valueOverride ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !==
initDataPoint.key ||
newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]
.comment !== initDataPoint.comment ||
JSON.stringify(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !==
JSON.stringify(initDataPoint?.tags))
)
.map((secret) => secret.id)
.includes(newDataPoint.id)
)
.map((override) => ({
pos: override.pos,
key: override.key,
value: String(override.valueOverride),
valueOverride: override.valueOverride,
comment: '',
id: String(override.idOverride),
idOverride: String(override.idOverride),
tags: override.tags
}));
console.log('override update', overridesToBeUpdated.length);
const requests: any = []; // TODO: fix any
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
secretsToBeDeleted.concat(overridesToBeDeleted).forEach((_id: string) => {
requests.push({
method: 'DELETE',
secret: {
_id
}
});
});
}
if (selectedEnv && secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded),
workspaceId,
env: selectedEnv.slug
});
if (secrets) {
secrets.forEach((secret) => {
requests.push({
method: 'POST',
secret: {
type: secret.type,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
tags: secret.tags
}
})
});
}
}
if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated),
workspaceId,
env: selectedEnv.slug
});
if (secrets) {
secrets.forEach((secret) => {
requests.push({
method: 'PATCH',
secret: {
_id: secret.id,
type: secret.type,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
tags: secret.tags
}
});
});
}
}
let newSecrets;
if (selectedEnv && requests.length > 0) {
newSecrets = await batchSecrets({
workspaceId,
environment: selectedEnv.slug,
requests
});
}
let formattedNewDecryptedKeys;
if (newSecrets.createdSecrets) {
const latestKey = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
const tempDecryptedSecrets: SecretProps[] = [];
if (latestKey) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: latestKey.latestKey.encryptedKey,
nonce: latestKey.latestKey.nonce,
publicKey: latestKey.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
// decrypt secret keys, values, and comments
newSecrets.createdSecrets.forEach((secret: EncryptedSecretProps) => {
const plainTextKey = decryptSymmetric({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
});
let plainTextValue;
if (secret.secretValueCiphertext !== undefined) {
plainTextValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
});
} else {
plainTextValue = undefined;
}
let plainTextComment;
if (secret.secretCommentCiphertext) {
plainTextComment = decryptSymmetric({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key
});
} else {
plainTextComment = '';
}
tempDecryptedSecrets.push({
id: secret._id,
key: plainTextKey,
value: plainTextValue,
type: secret.type,
comment: plainTextComment,
tags: secret.tags
});
});
}
const secretKeys = [...new Set(tempDecryptedSecrets.map((secret) => secret.key))];
formattedNewDecryptedKeys = secretKeys.map((key, index) => ({
id: tempDecryptedSecrets.filter((secret) => secret.key === key && secret.type === 'shared')[0]
?.id,
idOverride: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'personal'
)[0]?.id,
pos: (newData?.filter(dp => !dp.id.includes('-'))?.length ?? 0) + index,
key,
value: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'shared'
)[0]?.value,
valueOverride: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'personal'
)[0]?.value,
comment: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'shared'
)[0]?.comment,
tags: tempDecryptedSecrets.filter(
(secret) => secret.key === key && secret.type === 'shared'
)[0]?.tags
}));
setInitialData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id))));
setData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id))))
} else {
setInitialData(structuredClone(newData));
}
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: 'first_time_secrets_pushed' });
}
// increasing the number of project commits
setNumSnapshots((numSnapshots ?? 0) + 1);
setSaveLoading(false);
createNotification({
text: `Successfully saved secrets.`,
type: 'success'
});
} catch (error) {
console.log("Something went wrong while saving secrets: ", error)
createNotification({
text: `Something went wrong while saving secrets.`,
type: 'error'
});
}
}
if (selectedEnv && requests.length > 0) {
console.log('make batch secret request: ');
const result = await batchSecrets({
workspaceId,
environment: selectedEnv.slug,
requests
});
console.log('result of batchSecrets', result);
}
setInitialData(structuredClone(newData));
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
await registerUserAction({ action: 'first_time_secrets_pushed' });
}
// increasing the number of project commits
setNumSnapshots((numSnapshots ?? 0) + 1);
setSaveLoading(false);
return undefined;
};
@@ -885,7 +1006,7 @@ export default function Dashboard() {
</div>
</div>
<div className="w-[calc(10%)] border-r border-mineshaft-600">
<div className="flex items-center max-h-16">
<div className="flex items-center max-h-16 overflow-hidden">
<div className='text-bunker-300 px-2 font-semibold h-10 flex items-center w-3/12'>Comment</div>
</div>
</div>