mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 14:27:30 +00:00
Merge pull request #295 from mocherfaoui/inf-compare-secrets
add new modal to compare secrets across environments
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
import { SetStateAction, useEffect, useState } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
import { WorkspaceEnv } from '@app/hooks/api/types';
|
||||||
|
|
||||||
|
import getSecretsForProject from '../utilities/secrets/getSecretsForProject';
|
||||||
|
import { Modal, ModalContent } from '../v2';
|
||||||
|
|
||||||
|
interface Secrets {
|
||||||
|
label: string;
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompareSecretsModalProps {
|
||||||
|
compareModal: boolean;
|
||||||
|
setCompareModal: React.Dispatch<SetStateAction<boolean>>;
|
||||||
|
selectedEnv: WorkspaceEnv;
|
||||||
|
workspaceEnvs: WorkspaceEnv[];
|
||||||
|
workspaceId: string;
|
||||||
|
currentSecret: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompareSecretsModal = ({
|
||||||
|
compareModal,
|
||||||
|
setCompareModal,
|
||||||
|
selectedEnv,
|
||||||
|
workspaceEnvs,
|
||||||
|
workspaceId,
|
||||||
|
currentSecret
|
||||||
|
}: CompareSecretsModalProps) => {
|
||||||
|
const [secrets, setSecrets] = useState<Secrets[]>([]);
|
||||||
|
|
||||||
|
const getEnvSecrets = async () => {
|
||||||
|
const workspaceEnvironments = workspaceEnvs?.filter((env) => env !== selectedEnv);
|
||||||
|
const newSecrets = await Promise.all(
|
||||||
|
workspaceEnvironments.map(async (env) => {
|
||||||
|
// #TODO: optimize this query somehow...
|
||||||
|
const allSecrets = await getSecretsForProject({ env: env.slug, workspaceId });
|
||||||
|
const secret =
|
||||||
|
allSecrets.find((item) => item.key === currentSecret.key)?.value ?? 'Not found';
|
||||||
|
return { label: env.name, secret };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setSecrets([{ label: selectedEnv.name, secret: currentSecret.value }, ...newSecrets]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (compareModal) {
|
||||||
|
(async () => {
|
||||||
|
await getEnvSecrets();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}, [compareModal]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={compareModal} onOpenChange={setCompareModal}>
|
||||||
|
<ModalContent title={currentSecret?.key} onOpenAutoFocus={(e) => e.preventDefault()}>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{secrets.length === 0 ? (
|
||||||
|
<div className="flex items-center bg-bunker-900 justify-center h-full py-4 rounded-md">
|
||||||
|
<Image
|
||||||
|
src="/images/loading/loading.gif"
|
||||||
|
height={60}
|
||||||
|
width={100}
|
||||||
|
alt="infisical loading indicator"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
secrets.map((item) => (
|
||||||
|
<div key={`${currentSecret.key}${item.label}`} className="space-y-0.5">
|
||||||
|
<p className="text-sm text-bunker-300">{item.label}</p>
|
||||||
|
<input
|
||||||
|
defaultValue={item.secret}
|
||||||
|
className="h-no-capture border border-mineshaft-500 text-md min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full rounded-md bg-bunker-800 px-2 py-1.5 font-mono text-gray-400 caret-white outline-none duration-200 no-scrollbar focus:ring-2 focus:ring-primary/50 "
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CompareSecretsModal;
|
||||||
@@ -6,10 +6,12 @@ import { faX } from '@fortawesome/free-solid-svg-icons';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
import SecretVersionList from '@app/ee/components/SecretVersionList';
|
import SecretVersionList from '@app/ee/components/SecretVersionList';
|
||||||
|
import { WorkspaceEnv } from '@app/hooks/api/types';
|
||||||
|
|
||||||
import Button from '../basic/buttons/Button';
|
import Button from '../basic/buttons/Button';
|
||||||
import Toggle from '../basic/Toggle';
|
import Toggle from '../basic/Toggle';
|
||||||
import CommentField from './CommentField';
|
import CommentField from './CommentField';
|
||||||
|
import CompareSecretsModal from './CompareSecretsModal';
|
||||||
import DashboardInputField from './DashboardInputField';
|
import DashboardInputField from './DashboardInputField';
|
||||||
import { DeleteActionButton } from './DeleteActionButton';
|
import { DeleteActionButton } from './DeleteActionButton';
|
||||||
import GenerateSecretMenu from './GenerateSecretMenu';
|
import GenerateSecretMenu from './GenerateSecretMenu';
|
||||||
@@ -40,6 +42,9 @@ interface SideBarProps {
|
|||||||
sharedToHide: string[];
|
sharedToHide: string[];
|
||||||
setSharedToHide: (values: string[]) => void;
|
setSharedToHide: (values: string[]) => void;
|
||||||
deleteRow: (props: DeleteRowFunctionProps) => void;
|
deleteRow: (props: DeleteRowFunctionProps) => void;
|
||||||
|
workspaceEnvs: WorkspaceEnv[];
|
||||||
|
selectedEnv: WorkspaceEnv;
|
||||||
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,11 +68,15 @@ const SideBar = ({
|
|||||||
modifyComment,
|
modifyComment,
|
||||||
buttonReady,
|
buttonReady,
|
||||||
savePush,
|
savePush,
|
||||||
deleteRow
|
deleteRow,
|
||||||
|
workspaceEnvs,
|
||||||
|
selectedEnv,
|
||||||
|
workspaceId
|
||||||
}: SideBarProps) => {
|
}: SideBarProps) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [overrideEnabled, setOverrideEnabled] = useState(data[0].valueOverride !== undefined);
|
const [overrideEnabled, setOverrideEnabled] = useState(data[0]?.valueOverride !== undefined);
|
||||||
|
const [compareModal, setCompareModal] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -171,20 +180,38 @@ const SideBar = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-start max-w-sm mt-4 px-4 mt-full mb-8">
|
<div className="mt-full mt-4 mb-4 flex max-w-sm flex-col justify-start space-y-2 px-4">
|
||||||
<Button
|
<div>
|
||||||
text={String(t('common:save-changes'))}
|
<Button
|
||||||
onButtonPressed={savePush}
|
text="Compare secret across environments"
|
||||||
color="primary"
|
color="mineshaft"
|
||||||
size="md"
|
size="md"
|
||||||
active={buttonReady}
|
onButtonPressed={() => setCompareModal(true)}
|
||||||
textDisabled="Saved"
|
/>
|
||||||
/>
|
<CompareSecretsModal
|
||||||
<DeleteActionButton
|
compareModal={compareModal}
|
||||||
onSubmit={() =>
|
setCompareModal={setCompareModal}
|
||||||
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
|
currentSecret={{ key: data[0]?.key, value: data[0]?.value }}
|
||||||
}
|
workspaceEnvs={workspaceEnvs}
|
||||||
/>
|
selectedEnv={selectedEnv}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<Button
|
||||||
|
text={String(t('common:save-changes'))}
|
||||||
|
onButtonPressed={savePush}
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
active={buttonReady}
|
||||||
|
textDisabled="Saved"
|
||||||
|
/>
|
||||||
|
<DeleteActionButton
|
||||||
|
onSubmit={() =>
|
||||||
|
deleteRow({ ids: data.map((secret) => secret.id), secretName: data[0]?.key })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ interface SecretProps {
|
|||||||
|
|
||||||
interface FunctionProps {
|
interface FunctionProps {
|
||||||
env: string;
|
env: string;
|
||||||
setIsKeyAvailable: any;
|
setIsKeyAvailable?: any;
|
||||||
setData: any;
|
setData?: any;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,9 @@ const getSecretsForProject = async ({
|
|||||||
|
|
||||||
const latestKey = await getLatestFileKey({ workspaceId });
|
const latestKey = await getLatestFileKey({ workspaceId });
|
||||||
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
|
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
|
||||||
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
|
if (typeof setIsKeyAvailable === 'function') {
|
||||||
|
setIsKeyAvailable(!latestKey ? encryptedSecrets.length === 0 : true);
|
||||||
|
}
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
|
||||||
|
|
||||||
@@ -131,7 +133,10 @@ const getSecretsForProject = async ({
|
|||||||
)[0]?.comment
|
)[0]?.comment
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setData(result);
|
if (typeof setData === 'function') {
|
||||||
|
setData(result);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Something went wrong during accessing or decripting secrets.');
|
console.log('Something went wrong during accessing or decripting secrets.');
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
|||||||
<Card
|
<Card
|
||||||
isRounded
|
isRounded
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'fixed top-1/2 left-1/2 max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn drop-shadow-md',
|
'fixed top-1/2 left-1/2 max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn drop-shadow-2xl z-50',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -539,6 +539,9 @@ export default function Dashboard() {
|
|||||||
modifyValueOverride={listenChangeValueOverride}
|
modifyValueOverride={listenChangeValueOverride}
|
||||||
modifyComment={listenChangeComment}
|
modifyComment={listenChangeComment}
|
||||||
buttonReady={buttonReady}
|
buttonReady={buttonReady}
|
||||||
|
workspaceEnvs={workspaceEnvs}
|
||||||
|
selectedEnv={selectedEnv}
|
||||||
|
workspaceId={workspaceId}
|
||||||
savePush={savePush}
|
savePush={savePush}
|
||||||
sharedToHide={sharedToHide}
|
sharedToHide={sharedToHide}
|
||||||
setSharedToHide={setSharedToHide}
|
setSharedToHide={setSharedToHide}
|
||||||
@@ -870,7 +873,6 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full" />
|
|
||||||
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ export default function Users() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full" />
|
|
||||||
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user