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