mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Refactored the logic for frontend dashboard
This commit is contained in:
@ -1,27 +1,11 @@
|
||||
import React from "react";
|
||||
import { Switch } from "@headlessui/react";
|
||||
|
||||
|
||||
interface OverrideProps {
|
||||
id: string;
|
||||
keyName: string;
|
||||
value: string;
|
||||
pos: number;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
interface ToggleProps {
|
||||
enabled: boolean;
|
||||
setEnabled: (value: boolean) => void;
|
||||
addOverride: (value: OverrideProps) => void;
|
||||
keyName: string;
|
||||
value: string;
|
||||
addOverride: (value: string | undefined, pos: number) => void;
|
||||
pos: number;
|
||||
id: string;
|
||||
comment: string;
|
||||
deleteOverride: (id: string) => void;
|
||||
sharedToHide: string[];
|
||||
setSharedToHide: (values: string[]) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,41 +14,23 @@ interface ToggleProps {
|
||||
* @param {boolean} obj.enabled - whether the toggle is turned on or off
|
||||
* @param {function} obj.setEnabled - change the state of the toggle
|
||||
* @param {function} obj.addOverride - a function that adds an override to a certain secret
|
||||
* @param {string} obj.keyName - key of a certain secret
|
||||
* @param {string} obj.value - value of a certain secret
|
||||
* @param {number} obj.pos - position of a certain secret
|
||||
#TODO: make the secret id persistent?
|
||||
* @param {string} obj.id - id of a certain secret (NOTE: THIS IS THE ID OF THE MAIN SECRET - NOT OF AN OVERRIDE)
|
||||
* @param {function} obj.deleteOverride - a function that deleted an override for a certain secret
|
||||
* @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden.
|
||||
* @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually
|
||||
* @returns
|
||||
*/
|
||||
export default function Toggle ({
|
||||
enabled,
|
||||
setEnabled,
|
||||
addOverride,
|
||||
keyName,
|
||||
value,
|
||||
pos,
|
||||
id,
|
||||
comment,
|
||||
deleteOverride,
|
||||
sharedToHide,
|
||||
setSharedToHide
|
||||
pos
|
||||
}: ToggleProps): JSX.Element {
|
||||
return (
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={() => {
|
||||
if (enabled == false) {
|
||||
addOverride({ id, keyName, value, pos, comment });
|
||||
setSharedToHide([
|
||||
...sharedToHide!,
|
||||
id
|
||||
])
|
||||
addOverride('', pos);
|
||||
} else {
|
||||
deleteOverride(id);
|
||||
addOverride(undefined, pos);
|
||||
}
|
||||
setEnabled(!enabled);
|
||||
}}
|
||||
|
@ -3,7 +3,6 @@ import Image from "next/image";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconProps,
|
||||
} from "@fortawesome/react-fontawesome";
|
||||
|
||||
const classNames = require("classnames");
|
||||
@ -101,7 +100,7 @@ export default function Button(props: ButtonProps): JSX.Element {
|
||||
<div
|
||||
className={`${
|
||||
props.loading == true ? "opacity-100" : "opacity-0"
|
||||
} absolute flex items-center px-2 duration-200`}
|
||||
} absolute flex items-center px-3 bg-primary duration-200 w-full`}
|
||||
>
|
||||
<Image
|
||||
src="/images/loading/loadingblack.gif"
|
||||
|
@ -9,7 +9,7 @@ const REGEX = /([$]{.*?})/g;
|
||||
interface DashboardInputFieldProps {
|
||||
position: number;
|
||||
onChangeHandler: (value: string, position: number) => void;
|
||||
value: string;
|
||||
value: string | undefined;
|
||||
type: 'varName' | 'value';
|
||||
blurred?: boolean;
|
||||
isDuplicate?: boolean;
|
||||
@ -47,7 +47,7 @@ const DashboardInputField = ({
|
||||
};
|
||||
|
||||
if (type === 'varName') {
|
||||
const startsWithNumber = !isNaN(Number(value.charAt(0))) && value != '';
|
||||
const startsWithNumber = !isNaN(Number(value?.charAt(0))) && value != '';
|
||||
const error = startsWithNumber || isDuplicate;
|
||||
|
||||
return (
|
||||
@ -141,7 +141,7 @@ const DashboardInputField = ({
|
||||
{blurred && (
|
||||
<div className="absolute flex flex-row items-center z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible h-9 w-full rounded-md text-gray-400/50 text-clip">
|
||||
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
{value.split('').map(() => (
|
||||
{value?.split('').map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
|
@ -2,21 +2,12 @@ import { Fragment } from 'react';
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Menu, Transition } from '@headlessui/react';
|
||||
import { SecretDataProps } from 'public/data/frequentInterfaces';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import downloadDotEnv from '../utilities/secrets/downloadDotEnv';
|
||||
import downloadYaml from '../utilities/secrets/downloadYaml';
|
||||
|
||||
|
||||
interface SecretDataProps {
|
||||
type: 'personal' | 'shared';
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the menu that is used to download secrets as .env ad .yml files (in future we may have more options)
|
||||
* @param {object} obj
|
||||
|
@ -1,22 +1,15 @@
|
||||
import React from 'react';
|
||||
import { faEllipsis, faShuffle, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faEllipsis } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { SecretDataProps } from 'public/data/frequentInterfaces';
|
||||
|
||||
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;
|
||||
modifyKey: (value: string, position: number) => void;
|
||||
modifyValue: (value: string, position: number) => void;
|
||||
modifyValueOverride: (value: string, position: number) => void;
|
||||
isBlurred: boolean;
|
||||
isDuplicate: boolean;
|
||||
toggleSidebar: (id: string) => void;
|
||||
@ -30,6 +23,7 @@ interface KeyPairProps {
|
||||
* @param {String[]} obj.keyPair - data related to the environment variable (id, pos, key, value, public/private)
|
||||
* @param {function} obj.modifyKey - modify the key of a certain environment variable
|
||||
* @param {function} obj.modifyValue - modify the value of a certain environment variable
|
||||
* @param {function} obj.modifyValueOverride - modify the value of a certain environment variable if it is overriden
|
||||
* @param {boolean} obj.isBlurred - if the blurring setting is turned on
|
||||
* @param {boolean} obj.isDuplicate - list of all the duplicates secret names on the dashboard
|
||||
* @param {function} obj.toggleSidebar - open/close/switch sidebar
|
||||
@ -41,6 +35,7 @@ const KeyPair = ({
|
||||
keyPair,
|
||||
modifyKey,
|
||||
modifyValue,
|
||||
modifyValueOverride,
|
||||
isBlurred,
|
||||
isDuplicate,
|
||||
toggleSidebar,
|
||||
@ -50,7 +45,7 @@ const KeyPair = ({
|
||||
return (
|
||||
<div className={`mx-1 flex flex-col items-center ml-1 ${isSnapshot && "pointer-events-none"} ${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">
|
||||
{keyPair.valueOverride && <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
|
||||
@ -70,12 +65,12 @@ const KeyPair = ({
|
||||
<div className="w-full min-w-xl">
|
||||
<div className={`flex min-w-xl items-center ${!isSnapshot && "pr-1.5"} rounded-lg mt-4 md:mt-0 max-h-10`}>
|
||||
<DashboardInputField
|
||||
onChangeHandler={modifyValue}
|
||||
onChangeHandler={keyPair.valueOverride ? modifyValueOverride : modifyValue}
|
||||
type="value"
|
||||
position={keyPair.pos}
|
||||
value={keyPair.value}
|
||||
value={keyPair.valueOverride ? keyPair.valueOverride : keyPair.value}
|
||||
blurred={isBlurred}
|
||||
override={keyPair.type == "personal"}
|
||||
override={Boolean(keyPair.valueOverride)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,18 +16,15 @@ import GenerateSecretMenu from './GenerateSecretMenu';
|
||||
interface SecretProps {
|
||||
key: string;
|
||||
value: string;
|
||||
valueOverride: string | undefined;
|
||||
pos: number;
|
||||
type: string;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
interface OverrideProps {
|
||||
id: string;
|
||||
keyName: string;
|
||||
value: string;
|
||||
pos: number;
|
||||
comment: string;
|
||||
valueOverride: string;
|
||||
}
|
||||
export interface DeleteRowFunctionProps {
|
||||
ids: string[];
|
||||
@ -39,9 +36,8 @@ interface SideBarProps {
|
||||
data: SecretProps[];
|
||||
modifyKey: (value: string, position: number) => void;
|
||||
modifyValue: (value: string, position: number) => void;
|
||||
modifyValueOverride: (value: string | undefined, position: number) => void;
|
||||
modifyComment: (value: string, position: number) => void;
|
||||
addOverride: (value: OverrideProps) => void;
|
||||
deleteOverride: (id: string) => void;
|
||||
buttonReady: boolean;
|
||||
savePush: () => void;
|
||||
sharedToHide: string[];
|
||||
@ -55,12 +51,9 @@ interface SideBarProps {
|
||||
* @param {SecretProps[]} obj.data - data of a certain key valeu pair
|
||||
* @param {function} obj.modifyKey - function that modifies the secret key
|
||||
* @param {function} obj.modifyValue - function that modifies the secret value
|
||||
* @param {function} obj.addOverride - override a certain secret
|
||||
* @param {function} obj.deleteOverride - delete the personal override for a certain secret
|
||||
* @param {function} obj.modifyValueOverride - function that modifies the secret value if it is an override
|
||||
* @param {boolean} obj.buttonReady - is the button for saving chagnes active
|
||||
* @param {function} obj.savePush - save changes andp ush secrets
|
||||
* @param {string[]} obj.sharedToHide - an array of shared secrets that we want to hide visually because they are overriden.
|
||||
* @param {function} obj.setSharedToHide - a function that updates the array of secrets that we want to hide visually
|
||||
* @param {function} obj.deleteRow - a function to delete a certain keyPair
|
||||
* @returns the sidebar with 'secret's settings'
|
||||
*/
|
||||
@ -69,17 +62,14 @@ const SideBar = ({
|
||||
data,
|
||||
modifyKey,
|
||||
modifyValue,
|
||||
modifyValueOverride,
|
||||
modifyComment,
|
||||
addOverride,
|
||||
deleteOverride,
|
||||
buttonReady,
|
||||
savePush,
|
||||
sharedToHide,
|
||||
setSharedToHide,
|
||||
deleteRow
|
||||
}: SideBarProps) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal"));
|
||||
const [overrideEnabled, setOverrideEnabled] = useState(data[0].valueOverride != undefined);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between'>
|
||||
@ -111,19 +101,19 @@ const SideBar = ({
|
||||
blurred={false}
|
||||
/>
|
||||
</div>
|
||||
{data.filter(secret => secret.type == "shared")[0]?.value
|
||||
{data[0]?.value
|
||||
? <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}>
|
||||
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.value")}</p>
|
||||
<DashboardInputField
|
||||
onChangeHandler={modifyValue}
|
||||
type="value"
|
||||
position={data.filter(secret => secret.type == "shared")[0]?.pos}
|
||||
value={data.filter(secret => secret.type == "shared")[0]?.value}
|
||||
position={data[0].pos}
|
||||
value={data[0]?.value}
|
||||
isDuplicate={false}
|
||||
blurred={true}
|
||||
/>
|
||||
<div className='absolute bg-bunker-800 right-[1.07rem] top-[1.6rem] z-50'>
|
||||
<GenerateSecretMenu modifyValue={modifyValue} position={data.filter(secret => secret.type == "shared")[0]?.pos} />
|
||||
<GenerateSecretMenu modifyValue={modifyValue} position={data[0]?.pos} />
|
||||
</div>
|
||||
</div>
|
||||
: <div className='px-4 text-sm text-bunker-300 pt-4'>
|
||||
@ -131,39 +121,32 @@ const SideBar = ({
|
||||
{t("dashboard:sidebar.personal-explanation")}
|
||||
</div>}
|
||||
<div className='mt-4 px-4'>
|
||||
{data.filter(secret => secret.type == "shared")[0]?.value &&
|
||||
{data[0]?.value &&
|
||||
<div className='flex flex-row items-center justify-between my-2 pl-1 pr-2'>
|
||||
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.override")}</p>
|
||||
<Toggle
|
||||
enabled={overrideEnabled}
|
||||
setEnabled={setOverrideEnabled}
|
||||
addOverride={addOverride}
|
||||
keyName={data[0]?.key}
|
||||
value={data[0]?.value}
|
||||
addOverride={modifyValueOverride}
|
||||
pos={data[0]?.pos}
|
||||
id={data[0]?.id}
|
||||
comment={data[0]?.comment}
|
||||
deleteOverride={deleteOverride}
|
||||
sharedToHide={sharedToHide}
|
||||
setSharedToHide={setSharedToHide}
|
||||
/>
|
||||
</div>}
|
||||
<div className={`relative ${!overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}>
|
||||
<DashboardInputField
|
||||
onChangeHandler={modifyValue}
|
||||
onChangeHandler={modifyValueOverride}
|
||||
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}
|
||||
position={data[0]?.pos}
|
||||
value={overrideEnabled ? data[0]?.valueOverride : data[0]?.value}
|
||||
isDuplicate={false}
|
||||
blurred={true}
|
||||
/>
|
||||
<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} />
|
||||
<GenerateSecretMenu modifyValue={modifyValueOverride} position={data[0]?.pos} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SecretVersionList secretId={data[0]?.id} />
|
||||
<CommentField comment={data.filter(secret => secret.type == "shared")[0]?.comment} modifyComment={modifyComment} position={data.filter(secret => secret.type == "shared")[0]?.pos} />
|
||||
<CommentField comment={data[0]?.comment} modifyComment={modifyComment} position={data[0]?.pos} />
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}>
|
||||
@ -176,7 +159,7 @@ const SideBar = ({
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
<DeleteActionButton
|
||||
onSubmit={() => deleteRow({ ids: overrideEnabled ? data.map(secret => secret.id) : [data.filter(secret => secret.type == "shared")[0]?.id], secretName: data[0]?.key })}
|
||||
onSubmit={() => deleteRow({ ids: data.map(secret => secret.id), secretName: data[0]?.key })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,4 @@
|
||||
interface SecretDataProps {
|
||||
type: 'personal' | 'shared';
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
||||
import { SecretDataProps } from "public/data/frequentInterfaces";
|
||||
|
||||
/**
|
||||
* This function downloads the secrets as a .env file
|
||||
@ -16,16 +9,16 @@ interface SecretDataProps {
|
||||
const checkOverrides = async ({ data }: { data: SecretDataProps[]; }) => {
|
||||
let secrets : SecretDataProps[] = data!.map((secret) => Object.create(secret));
|
||||
const overridenSecrets = data!.filter(
|
||||
(secret) => secret.type === 'personal'
|
||||
(secret) => (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ? 'shared' : 'personal'
|
||||
);
|
||||
if (overridenSecrets.length) {
|
||||
overridenSecrets.forEach((secret) => {
|
||||
const index = secrets!.findIndex(
|
||||
(_secret) => _secret.key === secret.key && _secret.type === 'shared'
|
||||
(_secret) => _secret.key === secret.key && (secret.valueOverride == undefined || secret?.value != secret?.valueOverride)
|
||||
);
|
||||
secrets![index].value = secret.value;
|
||||
});
|
||||
secrets = secrets!.filter((secret) => secret.type === 'shared');
|
||||
secrets = secrets!.filter((secret) => (secret.valueOverride == undefined || secret?.value != secret?.valueOverride));
|
||||
}
|
||||
return secrets;
|
||||
}
|
||||
|
@ -1,16 +1,9 @@
|
||||
import { SecretDataProps } from "public/data/frequentInterfaces";
|
||||
|
||||
import { envMapping } from "../../../public/data/frequentConstants";
|
||||
import checkOverrides from './checkOverrides';
|
||||
|
||||
|
||||
interface SecretDataProps {
|
||||
type: 'personal' | 'shared';
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function downloads the secrets as a .env file
|
||||
* @param {object} obj
|
||||
|
@ -1,19 +1,12 @@
|
||||
// import YAML from 'yaml';
|
||||
// import { YAMLSeq } from 'yaml/types';
|
||||
|
||||
import { SecretDataProps } from "public/data/frequentInterfaces";
|
||||
|
||||
// import { envMapping } from "../../../public/data/frequentConstants";
|
||||
// import checkOverrides from './checkOverrides';
|
||||
|
||||
|
||||
interface SecretDataProps {
|
||||
type: 'personal' | 'shared';
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function downloads the secrets as a .yml file
|
||||
* @param {object} obj
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { SecretDataProps } from "public/data/frequentInterfaces";
|
||||
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
|
||||
const crypto = require("crypto");
|
||||
@ -9,15 +11,6 @@ const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
|
||||
|
||||
interface SecretDataProps {
|
||||
type: 'personal' | 'shared';
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
interface EncryptedSecretProps {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
@ -106,7 +99,7 @@ const encryptSecrets = async ({ secretsToEncrypt, workspaceId, env }: { secretsT
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
type: secret.type,
|
||||
type: (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ? 'shared' : 'personal',
|
||||
};
|
||||
|
||||
return result;
|
||||
|
@ -117,15 +117,19 @@ const getSecretsForProject = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const result = tempDecryptedSecrets.map((secret, index) => {
|
||||
const secretKeys = [...new Set(tempDecryptedSecrets.map(secret => secret.key))];
|
||||
|
||||
|
||||
const result = secretKeys.map((key, index) => {
|
||||
return {
|
||||
id: secret['id'],
|
||||
id: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.id,
|
||||
idOverride: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'personal')[0]?.id,
|
||||
pos: index,
|
||||
key: secret['key'],
|
||||
value: secret['value'],
|
||||
type: secret['type'],
|
||||
comment: secret['comment']
|
||||
};
|
||||
key: key,
|
||||
value: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.value,
|
||||
valueOverride: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'personal')[0]?.value,
|
||||
comment: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.comment,
|
||||
}
|
||||
});
|
||||
|
||||
setData(result);
|
||||
|
@ -13,6 +13,15 @@ import { decryptAssymmetric, decryptSymmetric } from "~/components/utilities/cry
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
|
||||
|
||||
export interface SecretDataProps {
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
type: string;
|
||||
id: string;
|
||||
environment: string;
|
||||
}
|
||||
|
||||
interface SideBarProps {
|
||||
toggleSidebar: (value: boolean) => void;
|
||||
setSnapshotData: (value: any) => void;
|
||||
@ -43,8 +52,6 @@ interface EncrypetedSecretVersionListProps {
|
||||
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
|
||||
* @param {function} obj.setSnapshotData - state manager for snapshot data
|
||||
* @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected
|
||||
*
|
||||
*
|
||||
* @returns the sidebar with the options for point-in-time recovery (commits)
|
||||
*/
|
||||
const PITRecoverySidebar = ({
|
||||
@ -111,7 +118,21 @@ const PITRecoverySidebar = ({
|
||||
}
|
||||
})
|
||||
|
||||
setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: decryptedSecretVersions })
|
||||
|
||||
const secretKeys = [...new Set(decryptedSecretVersions.map((secret: SecretDataProps) => secret.key))];
|
||||
|
||||
const result = secretKeys.map((key, index) => {
|
||||
return {
|
||||
id: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].id,
|
||||
pos: index,
|
||||
key: key,
|
||||
environment: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].environment,
|
||||
value: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0]?.value,
|
||||
valueOverride: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'personal')[0]?.value,
|
||||
}
|
||||
});
|
||||
|
||||
setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: result, comment: '' })
|
||||
}
|
||||
|
||||
return <div className={`absolute border-l border-mineshaft-500 ${isLoading ? "bg-bunker-800" : "bg-bunker"} fixed h-full w-96 top-14 right-0 z-40 shadow-xl flex flex-col justify-between`}>
|
||||
@ -125,31 +146,35 @@ const PITRecoverySidebar = ({
|
||||
></Image>
|
||||
</div>
|
||||
) : (
|
||||
<div className='h-min overflow-y-auto'>
|
||||
<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">{t("Point-in-time Recovery")}</p>
|
||||
<div className='p-1' onClick={() => toggleSidebar(false)}>
|
||||
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col px-2 py-2'>
|
||||
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) => <div key={snapshot._id} className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "bg-primary text-black" : "bg-mineshaft-700"} py-3 px-4 mb-2 rounded-md flex flex-row justify-between items-center`}>
|
||||
<div className="flex flex-row items-start">
|
||||
<div className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800" : "text-bunker-200"} text-sm mr-1.5`}>{timeSince(new Date(snapshot.createdAt))}</div>
|
||||
<div className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-900" : "text-bunker-300"} text-sm `}>{" - " + snapshot.secretVersions.length + " Secrets"}</div>
|
||||
</div>
|
||||
<div className='flex flex-col px-2 py-2 overflow-y-auto h-[92vh]'>
|
||||
{secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
|
||||
<div
|
||||
onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
|
||||
className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800 pointer-events-none" : "text-bunker-200 hover:text-primary duration-200 cursor-pointer"} text-sm`}>
|
||||
{id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"}
|
||||
key={snapshot._id}
|
||||
onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
|
||||
className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "bg-primary text-black pointer-events-none" : "bg-mineshaft-700 hover:bg-mineshaft-500 duration-200 cursor-pointer"} py-3 px-4 mb-2 rounded-md flex flex-row justify-between items-center`}
|
||||
>
|
||||
<div className="flex flex-row items-start">
|
||||
<div className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800" : "text-bunker-200"} text-sm mr-1.5`}>{timeSince(new Date(snapshot.createdAt))}</div>
|
||||
<div className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-900" : "text-bunker-300"} text-sm `}>{" - " + snapshot.secretVersions.length + " Secrets"}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800 pointer-events-none" : "text-bunker-200 hover:text-primary duration-200 cursor-pointer"} text-sm`}>
|
||||
{id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"}
|
||||
</div>
|
||||
</div>)}
|
||||
<div className='flex justify-center w-full mb-14'>
|
||||
<div className='items-center w-40'>
|
||||
<Button text="View More" textDisabled="End of History" active={secretSnapshotsMetadata.length % 15 == 0 ? true : false} onButtonPressed={loadMoreSnapshots} size="md" color="mineshaft"/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className='flex justify-center w-full mb-14'>
|
||||
<div className='items-center w-40'>
|
||||
<Button text="View More" textDisabled="End of History" active={secretSnapshotsMetadata.length % 15 == 0 ? true : false} onButtonPressed={loadMoreSnapshots} size="md" color="mineshaft"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
|
||||
});
|
||||
}
|
||||
|
||||
const decryptedSecretVersions = encryptedSecretVersions.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => {
|
||||
const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => {
|
||||
return {
|
||||
createdAt: encryptedSecretVersion.createdAt,
|
||||
value: decryptSymmetric({
|
||||
@ -87,28 +87,33 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
|
||||
</div>
|
||||
) : (
|
||||
<div className='h-48 overflow-y-auto overflow-x-none'>
|
||||
{secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
.map((version: DecryptedSecretVersionListProps, index: number) =>
|
||||
<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'>
|
||||
{(new Date(version.createdAt)).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
{secretVersions
|
||||
? secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
.map((version: DecryptedSecretVersionListProps, index: number) =>
|
||||
<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'>
|
||||
{(new Date(version.createdAt)).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
: (
|
||||
<div className='w-full h-full flex items-center justify-center text-bunker-400'>No version history yet.</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -45,11 +45,12 @@ import getWorkspaces from '../api/workspace/getWorkspaces';
|
||||
|
||||
|
||||
interface SecretDataProps {
|
||||
type: 'personal' | 'shared';
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
valueOverride: string | undefined;
|
||||
id: string;
|
||||
idOverride: string | undefined;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
@ -68,10 +69,11 @@ interface SnapshotProps {
|
||||
secretVersions: {
|
||||
id: string;
|
||||
pos: number;
|
||||
type: "personal" | "shared";
|
||||
environment: string;
|
||||
key: string;
|
||||
value: string;
|
||||
valueOverride: string;
|
||||
comment: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
@ -99,7 +101,7 @@ function findDuplicates(arr: any[]) {
|
||||
*/
|
||||
export default function Dashboard() {
|
||||
const [data, setData] = useState<SecretDataProps[] | null>();
|
||||
const [initialData, setInitialData] = useState<SecretDataProps[]>([]);
|
||||
const [initialData, setInitialData] = useState<SecretDataProps[] | null | undefined>([]);
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceId, setWorkspaceId] = useState('');
|
||||
@ -119,6 +121,7 @@ export default function Dashboard() {
|
||||
const [sharedToHide, setSharedToHide] = useState<string[]>([]);
|
||||
const [snapshotData, setSnapshotData] = useState<SnapshotProps>();
|
||||
const [numSnapshots, setNumSnapshots] = useState<number>();
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
@ -213,16 +216,6 @@ export default function Dashboard() {
|
||||
setInitialData(dataToSort);
|
||||
reorderRows(dataToSort);
|
||||
|
||||
setSharedToHide(
|
||||
dataToSort?.filter(row => (dataToSort
|
||||
?.map((item) => item.key)
|
||||
.filter(
|
||||
(item, index) =>
|
||||
index !==
|
||||
dataToSort?.map((item) => item.key).indexOf(item)
|
||||
).includes(row.key) && row.type == 'shared'))?.map((item) => item.id)
|
||||
)
|
||||
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.log('Error', error);
|
||||
@ -238,39 +231,16 @@ export default function Dashboard() {
|
||||
...data!,
|
||||
{
|
||||
id: guidGenerator(),
|
||||
idOverride: guidGenerator(),
|
||||
pos: data!.length,
|
||||
key: '',
|
||||
value: '',
|
||||
type: 'shared',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function add an ovverrided version of a certain secret to the current user
|
||||
* @param {object} obj
|
||||
* @param {string} obj.id - if of this secret that is about to be overriden
|
||||
* @param {string} obj.keyName - key name of this secret
|
||||
* @param {string} obj.value - value of this secret
|
||||
* @param {string} obj.pos - position of this secret on the dashboard
|
||||
*/
|
||||
const addOverride = ({ id, keyName, value, pos, comment }: overrideProps) => {
|
||||
setIsNew(false);
|
||||
const tempdata: SecretDataProps[] | 1 = [
|
||||
...data!,
|
||||
{
|
||||
id: id,
|
||||
pos: pos,
|
||||
key: keyName,
|
||||
value: value,
|
||||
type: 'personal',
|
||||
comment: comment
|
||||
}
|
||||
];
|
||||
sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical");
|
||||
};
|
||||
|
||||
const deleteRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => {
|
||||
setButtonReady(true);
|
||||
toggleSidebar("None");
|
||||
@ -289,15 +259,15 @@ export default function Dashboard() {
|
||||
setButtonReady(true);
|
||||
|
||||
// find which shared secret corresponds to the overriden version
|
||||
const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id;
|
||||
// const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id;
|
||||
|
||||
// change the sidebar to this shared secret; and unhide it
|
||||
toggleSidebar(sharedVersionOfOverride)
|
||||
setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride))
|
||||
// toggleSidebar(sharedVersionOfOverride)
|
||||
// setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride))
|
||||
|
||||
// resort secrets
|
||||
const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal'))
|
||||
sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical")
|
||||
// const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal'))
|
||||
// sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical")
|
||||
};
|
||||
|
||||
const modifyValue = (value: string, pos: number) => {
|
||||
@ -308,6 +278,14 @@ export default function Dashboard() {
|
||||
setButtonReady(true);
|
||||
};
|
||||
|
||||
const modifyValueOverride = (value: string | undefined, pos: number) => {
|
||||
setData((oldData) => {
|
||||
oldData![pos].valueOverride = value;
|
||||
return [...oldData!];
|
||||
});
|
||||
setButtonReady(true);
|
||||
};
|
||||
|
||||
const modifyKey = (value: string, pos: number) => {
|
||||
setData((oldData) => {
|
||||
oldData![pos].key = value;
|
||||
@ -329,6 +307,10 @@ export default function Dashboard() {
|
||||
modifyValue(value, pos);
|
||||
}, []);
|
||||
|
||||
const listenChangeValueOverride = useCallback((value: string | undefined, pos: number) => {
|
||||
modifyValueOverride(value, pos);
|
||||
}, []);
|
||||
|
||||
const listenChangeKey = useCallback((value: string, pos: number) => {
|
||||
modifyKey(value, pos);
|
||||
}, []);
|
||||
@ -341,6 +323,7 @@ export default function Dashboard() {
|
||||
* Save the changes of environment variables and push them to the database
|
||||
*/
|
||||
const savePush = async (dataToPush?: SecretDataProps[]) => {
|
||||
setSaveLoading(true);
|
||||
let newData: SecretDataProps[] | null | undefined;
|
||||
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
|
||||
if ((dataToPush ?? [])?.length > 0) {
|
||||
@ -349,16 +332,11 @@ export default function Dashboard() {
|
||||
newData = data;
|
||||
}
|
||||
|
||||
const obj = Object.assign(
|
||||
{},
|
||||
...newData!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] }))
|
||||
);
|
||||
|
||||
// Checking if any of the secret keys start with a number - if so, don't do anything
|
||||
const nameErrors = !Object.keys(obj)
|
||||
.map((key) => !isNaN(Number(key[0].charAt(0))))
|
||||
const nameErrors = !newData!
|
||||
.map((secret) => !isNaN(Number(secret.key.charAt(0))))
|
||||
.every((v) => v === false);
|
||||
const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key + item.type)).length > 0;
|
||||
const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
|
||||
|
||||
if (nameErrors) {
|
||||
return createNotification({
|
||||
@ -378,34 +356,64 @@ export default function Dashboard() {
|
||||
setButtonReady(false);
|
||||
|
||||
const secretsToBeDeleted
|
||||
= initialData
|
||||
= initialData!
|
||||
.filter(initDataPoint => !newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id))
|
||||
.map(secret => secret.id);
|
||||
console.log('delete', secretsToBeDeleted.length)
|
||||
|
||||
const secretsToBeAdded
|
||||
= newData!
|
||||
.filter(newDataPoint => !initialData.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id));
|
||||
.filter(newDataPoint => !initialData!.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id));
|
||||
console.log('add', secretsToBeAdded.length)
|
||||
|
||||
const secretsToBeUpdated
|
||||
= newData!.filter(newDataPoint => initialData
|
||||
= newData!.filter(newDataPoint => initialData!
|
||||
.filter(initDataPoint => newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)
|
||||
&& (newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].value != initDataPoint.value
|
||||
|| newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key
|
||||
|| newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment))
|
||||
.map(secret => secret.id).includes(newDataPoint.id));
|
||||
console.log('update', secretsToBeUpdated.length)
|
||||
|
||||
const newOverrides = newData!.filter(newDataPoint => newDataPoint.valueOverride != undefined)
|
||||
const initOverrides = initialData!.filter(initDataPoint => initDataPoint.valueOverride != undefined)
|
||||
|
||||
const overridesToBeDeleted
|
||||
= initOverrides
|
||||
.filter(initDataPoint => !newOverrides!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id))
|
||||
.map(secret => String(secret.idOverride));
|
||||
console.log('override delete', overridesToBeDeleted.length)
|
||||
|
||||
const overridesToBeAdded
|
||||
= newOverrides!
|
||||
.filter(newDataPoint => !initOverrides.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id))
|
||||
.map(override => ({pos: override.pos, key: override.key, value: String(override.valueOverride), valueOverride: override.valueOverride, comment: '', id: String(override.idOverride), idOverride: String(override.idOverride)}));
|
||||
console.log('override add', overridesToBeAdded.length)
|
||||
|
||||
const overridesToBeUpdated
|
||||
= newOverrides!.filter(newDataPoint => initOverrides
|
||||
.filter(initDataPoint => newOverrides!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)
|
||||
&& (newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].valueOverride != initDataPoint.valueOverride
|
||||
|| newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key
|
||||
|| newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment))
|
||||
.map(secret => secret.id).includes(newDataPoint.id))
|
||||
.map(override => ({pos: override.pos, key: override.key, value: String(override.valueOverride), valueOverride: override.valueOverride, comment: '', id: String(override.idOverride), idOverride: String(override.idOverride)}));
|
||||
console.log('override update', overridesToBeUpdated.length)
|
||||
|
||||
if (secretsToBeDeleted.length > 0) {
|
||||
await deleteSecrets({ secretIds: secretsToBeDeleted });
|
||||
if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
|
||||
await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) });
|
||||
}
|
||||
if (secretsToBeAdded.length > 0) {
|
||||
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId, env: envMapping[env] })
|
||||
if (secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
|
||||
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded), workspaceId, env: envMapping[env] });
|
||||
secrets && await addSecrets({ secrets, env: envMapping[env], workspaceId });
|
||||
}
|
||||
if (secretsToBeUpdated.length > 0) {
|
||||
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated, workspaceId, env: envMapping[env] })
|
||||
if (secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
|
||||
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated), workspaceId, env: envMapping[env] });
|
||||
secrets && await updateSecrets({ secrets });
|
||||
}
|
||||
|
||||
setInitialData(newData);
|
||||
|
||||
// If this user has never saved environment variables before, show them a prompt to read docs
|
||||
if (!hasUserEverPushed) {
|
||||
setCheckDocsPopUpVisible(true);
|
||||
@ -414,6 +422,7 @@ export default function Dashboard() {
|
||||
|
||||
// increasing the number of project commits
|
||||
setNumSnapshots((numSnapshots ?? 0) + 1);
|
||||
setSaveLoading(false);
|
||||
};
|
||||
|
||||
const addData = (newData: SecretDataProps[]) => {
|
||||
@ -462,9 +471,8 @@ export default function Dashboard() {
|
||||
data={data.filter((row: SecretDataProps) => row.key == data.filter(row => row.id == sidebarSecretId)[0]?.key)}
|
||||
modifyKey={listenChangeKey}
|
||||
modifyValue={listenChangeValue}
|
||||
modifyValueOverride={listenChangeValueOverride}
|
||||
modifyComment={listenChangeComment}
|
||||
addOverride={addOverride}
|
||||
deleteOverride={deleteOverride}
|
||||
buttonReady={buttonReady}
|
||||
savePush={savePush}
|
||||
sharedToHide={sharedToHide}
|
||||
@ -533,6 +541,7 @@ export default function Dashboard() {
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled={String(t("common:saved"))}
|
||||
loading={saveLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -545,21 +554,11 @@ export default function Dashboard() {
|
||||
.filter(row => reverseEnvMapping[row.environment] == env)
|
||||
.map((sv, position) => {
|
||||
return {
|
||||
id: sv.id, pos: position, type: sv.type, key: sv.key, value: sv.value, comment: ''
|
||||
id: sv.id, idOverride: sv.id, pos: position, valueOverride: sv.valueOverride, key: sv.key, value: sv.value, comment: ''
|
||||
}
|
||||
});
|
||||
setData(rolledBackSecrets);
|
||||
|
||||
setSharedToHide(
|
||||
rolledBackSecrets?.filter(row => (rolledBackSecrets
|
||||
?.map((item) => item.key)
|
||||
.filter(
|
||||
(item, index) =>
|
||||
index !==
|
||||
rolledBackSecrets?.map((item) => item.key).indexOf(item)
|
||||
).includes(row.key) && row.type == 'shared'))?.map((item) => item.id)
|
||||
)
|
||||
|
||||
// Perform the rollback globally
|
||||
performSecretRollback({ workspaceId, version: snapshotData.version })
|
||||
|
||||
@ -663,16 +662,17 @@ export default function Dashboard() {
|
||||
>
|
||||
<div className="px-1 pt-2 bg-mineshaft-800 rounded-md p-2">
|
||||
{!snapshotData && data?.filter(row => row.key?.toUpperCase().includes(searchKeys.toUpperCase()))
|
||||
.filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => (
|
||||
.filter(row => !sharedToHide.includes(row.id)).map((keyPair) => (
|
||||
<KeyPair
|
||||
key={keyPair.id}
|
||||
keyPair={keyPair}
|
||||
modifyValue={listenChangeValue}
|
||||
modifyValueOverride={listenChangeValueOverride}
|
||||
modifyKey={listenChangeKey}
|
||||
isBlurred={blurred}
|
||||
isDuplicate={findDuplicates(
|
||||
data?.map((item) => item.key + item.type)
|
||||
)?.includes(keyPair.key + keyPair.type)}
|
||||
data?.map((item) => item.key)
|
||||
)?.includes(keyPair.key)}
|
||||
toggleSidebar={toggleSidebar}
|
||||
sidebarSecretId={sidebarSecretId}
|
||||
isSnapshot={false}
|
||||
@ -681,22 +681,26 @@ export default function Dashboard() {
|
||||
{snapshotData && snapshotData.secretVersions?.sort((a, b) => a.key.localeCompare(b.key))
|
||||
.filter(row => reverseEnvMapping[row.environment] == snapshotEnv)
|
||||
.filter(row => row.key.toUpperCase().includes(searchKeys.toUpperCase()))
|
||||
.filter(row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions
|
||||
.filter(
|
||||
row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions
|
||||
?.map((item) => item.key)
|
||||
.filter(
|
||||
(item, index) =>
|
||||
index !==
|
||||
snapshotData.secretVersions?.map((item) => item.key).indexOf(item)
|
||||
).includes(row.key) && row.type == 'shared'))?.map((item) => item.id).includes(row.id) && row.type == 'shared')).map((keyPair) => (
|
||||
).includes(row.key)))?.map((item) => item.id).includes(row.id))
|
||||
)
|
||||
.map((keyPair) => (
|
||||
<KeyPair
|
||||
key={keyPair.id}
|
||||
keyPair={keyPair}
|
||||
modifyValue={listenChangeValue}
|
||||
modifyValueOverride={listenChangeValueOverride}
|
||||
modifyKey={listenChangeKey}
|
||||
isBlurred={blurred}
|
||||
isDuplicate={findDuplicates(
|
||||
data?.map((item) => item.key + item.type)
|
||||
)?.includes(keyPair.key + keyPair.type)}
|
||||
data?.map((item) => item.key)
|
||||
)?.includes(keyPair.key)}
|
||||
toggleSidebar={toggleSidebar}
|
||||
sidebarSecretId={sidebarSecretId}
|
||||
isSnapshot={true}
|
||||
|
8
frontend/public/data/frequentInterfaces.ts
Normal file
8
frontend/public/data/frequentInterfaces.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface SecretDataProps {
|
||||
pos: number;
|
||||
key: string;
|
||||
value: string;
|
||||
valueOverride: string | undefined;
|
||||
id: string;
|
||||
comment: string;
|
||||
}
|
Reference in New Issue
Block a user