Finished the env overview feature

This commit is contained in:
Vladyslav Matsiiako
2023-04-14 18:20:19 -07:00
parent 810554e13c
commit 903560a2d1
13 changed files with 222 additions and 255 deletions
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { FormProvider, useFieldArray, useForm, useWatch } from 'react-hook-form';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useRouter } from 'next/router';
import { yupResolver } from '@hookform/resolvers/yup';
@@ -10,10 +10,11 @@ import {
Button,
Modal,
ModalContent,
TableContainer
TableContainer,
Tooltip
} from '@app/components/v2';
import { useWorkspace } from '@app/context';
import { usePopUp, useToggle } from '@app/hooks';
import { usePopUp } from '@app/hooks';
import {
useCreateWsTag,
useGetProjectSecrets,
@@ -23,26 +24,13 @@ import {
import { WorkspaceEnv } from '@app/hooks/api/types';
import { CreateTagModal } from './components/CreateTagModal';
import { EnvComparisonHeader } from './components/EnvComparisonHeader';
import { EnvComparisonRow } from './components/EnvComparisonRow';
import {
DEFAULT_SECRET_VALUE,
FormData,
schema
} from './DashboardPage.utils';
/*
* Some imp aspects to consider. Here there are multiple stats changing
* Thus ideally we need to use a context. But instead we rely on react hook form
* React hook form provides context and high performance proxy based rendering
* It also handles error handling and transferring states between inputs
*
* Another thing is the purpose of overrideAction
* Before we would remove the value for personal secret when user toggle and user couldn't get it back
* They have to reload the browser or go back all over again
* Instead when user delete we raise a flag so if user decides to go back to toggle personal before saving
* They will get it back
*/
export const DashboardEnvOverview = () => {
const { t } = useTranslation();
const router = useRouter();
@@ -57,11 +45,7 @@ export const DashboardEnvOverview = () => {
'uploadedSecOpts',
'compareSecrets'
] as const);
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle(true);
const [snapshotId, setSnaphotId] = useState<string | null>(null);
console.log(setIsSecretValueHidden, setSnaphotId)
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv | null>(null);
// const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const { currentWorkspace, isLoading } = useWorkspace();
const workspaceId = currentWorkspace?._id as string;
@@ -77,26 +61,25 @@ export const DashboardEnvOverview = () => {
workspaceId,
onSuccess: (data) => {
// get an env with one of the access available
const env = data.find(({ isReadDenied, isWriteDenied }) => !isWriteDenied || !isReadDenied);
const env = data.find(({ isReadDenied }) => !isReadDenied);
if (env) {
setSelectedEnv(env);
}
}
});
const userAvailableEnvs = wsEnv?.filter(
({ isReadDenied }) => !isReadDenied
);
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
workspaceId,
env: wsEnv?.map(env => env.slug) ?? [],
env: userAvailableEnvs?.map(env => env.slug) ?? [],
decryptFileKey: latestFileKey!,
isPaused: Boolean(snapshotId)
isPaused: false
});
console.log(333333, secrets, [... new Set(secrets?.secrets.map((secret: any) => secret.key))])
// mutation calls
// const { mutateAsync: batchSecretOp } = useBatchSecretsOp();
// const { mutateAsync: performSecretRollback } = usePerformSecretRollback();
// const { mutateAsync: registerUserAction } = useRegisterUserAction();
const { mutateAsync: createWsTag } = useCreateWsTag();
const method = useForm<FormData>({
@@ -116,27 +99,8 @@ export const DashboardEnvOverview = () => {
// reset
} = method;
const formSecrets = useWatch({ control, name: 'secrets' });
console.log(formSecrets)
const { fields, prepend,
// append, remove, update
} = useFieldArray({ control, name: 'secrets' });
console.log(987, fields, secrets?.secrets.map((secret: any) => secret.key))
const isRollbackMode = Boolean(snapshotId);
const isReadOnly = selectedEnv?.isWriteDenied;
const isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
// const canDoRollback = !isReadOnly && !isAddOnly;
// const onSortSecrets = () => {
// const dir = sortDir === 'asc' ? 'desc' : 'asc';
// const sec = getValues('secrets') || [];
// const sortedSec = sec.sort((a, b) =>
// dir === 'asc' ? a?.key?.localeCompare(b?.key || '') : b?.key?.localeCompare(a?.key || '')
// );
// setValue('secrets', sortedSec);
// setSortDir(dir);
// };
const onCreateWsTag = async (tagName: string) => {
try {
@@ -169,11 +133,8 @@ export const DashboardEnvOverview = () => {
// when secrets is not loading and secrets list is empty
const isDashboardSecretEmpty = !isSecretsLoading && !formSecrets?.length;
const isSecretEmpty = (!isRollbackMode && isDashboardSecretEmpty);
const userAvailableEnvs = wsEnv?.filter(
({ isReadDenied, isWriteDenied }) => !isReadDenied || !isWriteDenied
);
const numSecretsMissingPerEnv = userAvailableEnvs?.map(envir => ({[envir.slug]: [... new Set(secrets?.secrets.map((secret: any) => secret.key))].length - [... new Set(secrets?.secrets.filter(s => s.env === envir.slug).map((secret: any) => secret.key))].length})).reduce((acc, cur) => ({ ...acc, ...cur }), {})
return (
<div className="container mx-auto max-w-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
@@ -183,71 +144,116 @@ export const DashboardEnvOverview = () => {
<div className="relative right-5">
<NavHeader pageName={t('dashboard:title')} isProjectRelated />
</div>
<div className="mt-8 ml-1">
<div className="mt-6 ml-1">
<p className="text-3xl font-semibold text-bunker-100">Secrets Overview</p>
<p className="text-md text-bunker-300">Put your secrets to work with the <span className="text-primary">Infisical CLI</span></p>
</div>
<div className={`${isSecretEmpty ? "" : ""} flex flex-row items-start justify-center mt-10 h-[calc(100vh-270px)] overflow-y-scroll overflow-x-hidden no-scrollbar no-scrollbar::-webkit-scrollbar`}>
{!isSecretEmpty && (
<TableContainer className='border-0'>
<table className="secret-table relative bg-bunker-800">
<EnvComparisonHeader userAvailableEnvs={userAvailableEnvs} />
<tbody className="overflow-y-auto max-h-screen">
{[... new Set(secrets?.secrets.map((secret: any) => secret.key))].map((key, index) => (
<EnvComparisonRow
key={key}
secrets={secrets?.secrets.filter(secret => secret.key === key)}
isReadOnly={isReadOnly}
isAddOnly={isAddOnly}
index={index}
isSecretValueHidden={isSecretValueHidden}
userAvailableEnvs={userAvailableEnvs}
/>
))}
</tbody>
<tfoot>
<tr className="group min-w-full flex flex-row items-center border-none mt-4">
<td className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-transparent'>0</div></td>
<td className="border-none">
<div className="min-w-[220px] lg:min-w-[240px] xl:min-w-[280px] relative flex items-center justify-end w-full text-transparent">1</div>
</td>
{userAvailableEnvs?.map(env => {
return <>
<td className="w-10 px-4 flex items-center justify-center h-10 border-none">
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
</td>
<td className="flex flex-row w-full justify-center h-10 items-center border-none">
<Button
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
isDisabled={isReadOnly || isRollbackMode}
variant="outline_bg"
colorSchema="primary"
isFullWidth
className="h-10"
>
Explore {env.name}
</Button>
</td>
</>
})}
</tr>
</tfoot>
</table>
</TableContainer>
)}
{/* <div className="ml-10 h-full flex items-start justify-center">
<Button
leftIcon={<FontAwesomeIcon icon={faPlus}/>}
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
isDisabled={isReadOnly || isRollbackMode}
variant="outline_bg"
colorSchema="primary"
isFullWidth
className="h-10"
<p className="text-md text-bunker-300">Inject your secrets using
<a
className="text-primary/80 hover:text-primary mx-1"
href="https://infisical.com/docs/cli/overview"
target="_blank"
rel="noopener noreferrer"
>
Add Environment
</Button>
</div> */}
Infisical CLI
</a>
or
<a
className="text-primary/80 hover:text-primary mx-1"
href="https://infisical.com/docs/sdks/overview"
target="_blank"
rel="noopener noreferrer"
>
Infisical SDKs
</a> </p>
</div>
<div className="overflow-y-auto">
<div className="sticky top-0 absolute flex flex-row h-10 bg-mineshaft-800 border border-mineshaft-600 rounded-md mt-8 min-w-[60.3rem]">
<div className="sticky top-0 w-10 px-4 flex items-center justify-center border-none">
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
</div>
<div className="sticky top-0 border-none">
<div className="min-w-[200px] lg:min-w-[220px] xl:min-w-[250px] relative flex items-center justify-start h-full w-full">
<div className="text-sm font-medium ">Secret</div>
</div>
</div>
{numSecretsMissingPerEnv && userAvailableEnvs?.map(env => {
return <div key={`header-${env.slug}`} className="flex flex-row w-full bg-mineshaft-800 rounded-md items-center border-none min-w-[11rem]">
<div className="text-sm font-medium w-full text-center text-bunker-200/[.99] flex flex-row justify-center">
{env.name}
{numSecretsMissingPerEnv[env.slug] > 0 && <div className="bg-red rounded-sm h-[1.1rem] w-[1.1rem] mt-0.5 text-bunker-100 ml-2.5 text-xs border border-red-400 flex items-center justify-center cursor-default">
<Tooltip content={`${numSecretsMissingPerEnv[env.slug]} secrets missing compared to other environments`}><span className="text-bunker-100">{numSecretsMissingPerEnv[env.slug]}</span></Tooltip>
</div>}
</div>
</div>
})}
</div>
<div className={`${isDashboardSecretEmpty ? "" : ""} flex flex-row items-start justify-center mt-3 h-full max-h-[calc(100vh-370px)] min-w-[60.3rem] flex-grow w-full overflow-x-hidden no-scrollbar no-scrollbar::-webkit-scrollbar`}>
{!isDashboardSecretEmpty && (
<TableContainer className='border-none'>
<table className="relative secret-table w-full relative bg-bunker-800">
<tbody className="overflow-y-auto max-h-screen">
{[... new Set(secrets?.secrets.map((secret: any) => secret.key))].map((key, index) => (
<EnvComparisonRow
key={`row-${key}`}
secrets={secrets?.secrets.filter(secret => secret.key === key)}
isReadOnly={isReadOnly}
index={index}
isSecretValueHidden
userAvailableEnvs={userAvailableEnvs}
/>
))}
</tbody>
</table>
</TableContainer>
)}
{isDashboardSecretEmpty &&
<div className='flex flex-row h-40 rounded-md mt-1 w-full'>
<div className="sticky top-0 w-10 px-4 flex items-center justify-center border-none">
<div className='text-center w-10 text-xs text-transparent'>{0}</div>
</div>
<div className="sticky top-0 border-none">
<div className="min-w-[200px] lg:min-w-[220px] xl:min-w-[250px] relative flex items-center justify-start h-full w-full">
<div className="text-sm font-medium text-transparent">Secret</div>
</div>
</div>
<div className="flex flex-col w-full bg-mineshaft-800 text-bunker-300 rounded-md items-center justify-center border-none mx-2 min-w-[11rem]">
<span className="mb-1">No secrets are available in this project yet.</span>
<span>You can go into any environment to add secrets there.</span>
</div>
</div>}
{/* In future, we should add an option to add environments here
<div className="ml-10 h-full flex items-start justify-center">
<Button
leftIcon={<FontAwesomeIcon icon={faPlus}/>}
onClick={() => prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false })}
variant="outline_bg"
colorSchema="primary"
isFullWidth
className="h-10"
>
Add Environment
</Button>
</div> */}
</div>
<div className="group min-w-full flex flex-row items-center mt-4">
<div className="w-10 h-10 px-4 flex items-center justify-center border-none"><div className='text-center w-10 text-xs text-transparent'>0</div></div>
<div className="flex flex-row justify-between items-center min-w-[200px] lg:min-w-[220px] xl:min-w-[250px]">
<span className="text-transparent">0</span>
<button type="button" className='mr-2 text-transparent'>1</button>
</div>
{userAvailableEnvs?.map(env => {
return <div key={`button-${env.slug}`} className="flex flex-row w-full justify-center h-10 items-center border-none mb-1 mx-2 min-w-[10rem]">
<Button
onClick={() => router.push(`${router.asPath }?env=${env.slug}`)}
variant="outline_bg"
colorSchema="primary"
isFullWidth
className="h-10"
>
Explore {env.name}
</Button>
</div>
})}
</div>
</div>
</form>
<Modal