Merge pull request #1548 from rhythmbhiwani/overview-page-enchanced

Overview Page Bug Fixes and Enhancement
This commit is contained in:
Maidul Islam
2024-03-12 11:58:42 -04:00
committed by GitHub
4 changed files with 188 additions and 127 deletions
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; import { faCheckCircle, faCircle } from "@fortawesome/free-regular-svg-icons";
import { import {
faAngleDown, faAngleDown,
faArrowDown, faArrowDown,
@@ -18,7 +18,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import NavHeader from "@app/components/navigation/NavHeader"; import NavHeader from "@app/components/navigation/NavHeader";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
Button, Button,
DropdownMenu, DropdownMenu,
@@ -108,7 +108,11 @@ export const SecretOverviewPage = () => {
}, [isWorkspaceLoading, workspaceId, router.isReady]); }, [isWorkspaceLoading, workspaceId, router.isReady]);
const userAvailableEnvs = currentWorkspace?.environments || []; const userAvailableEnvs = currentWorkspace?.environments || [];
const [visibleEnvs, setVisisbleEnvs] = useState(userAvailableEnvs); const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs);
useEffect(() => {
setVisibleEnvs(userAvailableEnvs);
}, [userAvailableEnvs]);
const { const {
data: secrets, data: secrets,
@@ -208,9 +212,9 @@ export const SecretOverviewPage = () => {
const handleEnvSelect = (envId: string) => { const handleEnvSelect = (envId: string) => {
if (visibleEnvs.map((env) => env.id).includes(envId)) { if (visibleEnvs.map((env) => env.id).includes(envId)) {
setVisisbleEnvs(visibleEnvs.filter((env) => env.id !== envId)); setVisibleEnvs(visibleEnvs.filter((env) => env.id !== envId));
} else { } else {
setVisisbleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId))); setVisibleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId)));
} }
}; };
@@ -390,41 +394,44 @@ export const SecretOverviewPage = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} /> <FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
<div className="flex flex-row items-center justify-center space-x-2"> <div className="flex flex-row items-center justify-center space-x-2">
<DropdownMenu> {userAvailableEnvs.length > 0 && (
<DropdownMenuTrigger asChild> <DropdownMenu>
<IconButton <DropdownMenuTrigger asChild>
ariaLabel="Environments" <IconButton
variant="plain" ariaLabel="Environments"
size="sm" variant="plain"
className="mr-2 flex w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 hover:border-primary/60 hover:bg-primary/10" size="sm"
> className="flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 hover:border-primary/60 hover:bg-primary/10"
<Tooltip content="Choose visible environments" className="mb-2"> >
<FontAwesomeIcon icon={faList} /> <Tooltip content="Choose visible environments" className="mb-2">
</Tooltip> <FontAwesomeIcon icon={faList} />
</IconButton> </Tooltip>
</DropdownMenuTrigger> </IconButton>
<DropdownMenuContent align="end"> </DropdownMenuTrigger>
<DropdownMenuLabel>Choose visible environments</DropdownMenuLabel> <DropdownMenuContent align="end">
{userAvailableEnvs.map((avaiableEnv) => { <DropdownMenuLabel>Choose visible environments</DropdownMenuLabel>
const { id: envId, name } = avaiableEnv; {userAvailableEnvs.map((availableEnv) => {
const { id: envId, name } = availableEnv;
const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId); const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId);
return ( return (
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleEnvSelect(envId)} onClick={() => handleEnvSelect(envId)}
key={envId} key={envId}
icon={ icon={
isEnvSelected && ( isEnvSelected ? (
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} /> <FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
) ) : (
} <FontAwesomeIcon className="text-mineshaft-400" icon={faCircle} />
iconPos="left" )
> }
<div className="flex items-center">{name}</div> iconPos="left"
</DropdownMenuItem> >
); <div className="flex items-center">{name}</div>
})} </DropdownMenuItem>
{/* <DropdownMenuItem className="px-1.5" asChild> );
})}
{/* <DropdownMenuItem className="px-1.5" asChild>
<Button <Button
size="xs" size="xs"
className="w-full" className="w-full"
@@ -436,8 +443,9 @@ export const SecretOverviewPage = () => {
Create an environment Create an environment
</Button> </Button>
</DropdownMenuItem> */} </DropdownMenuItem> */}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
)}
<div className="w-80"> <div className="w-80">
<Input <Input
className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80" className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
@@ -447,62 +455,64 @@ export const SecretOverviewPage = () => {
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/> />
</div> </div>
<div> {userAvailableEnvs.length > 0 && (
<ProjectPermissionCan <div>
I={ProjectPermissionActions.Create} <ProjectPermissionCan
a={subject(ProjectPermissionSub.Secrets, { secretPath })} I={ProjectPermissionActions.Create}
> a={subject(ProjectPermissionSub.Secrets, { secretPath })}
{(isAllowed) => ( >
<Button {(isAllowed) => (
variant="outline_bg" <Button
leftIcon={<FontAwesomeIcon icon={faPlus} />} variant="outline_bg"
onClick={() => handlePopUpOpen("addSecretsInAllEnvs")} leftIcon={<FontAwesomeIcon icon={faPlus} />}
className="h-10 rounded-r-none" onClick={() => handlePopUpOpen("addSecretsInAllEnvs")}
isDisabled={!isAllowed} className="h-10 rounded-r-none"
> isDisabled={!isAllowed}
Add Secret
</Button>
)}
</ProjectPermissionCan>
<DropdownMenu
open={popUp.misc.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("misc", isOpen)}
>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="add-folder-or-import"
variant="outline_bg"
className="rounded-l-none bg-mineshaft-600 p-3"
>
<FontAwesomeIcon icon={faAngleDown} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<div className="flex flex-col space-y-1 p-1.5">
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { secretPath })}
> >
{(isAllowed) => ( Add Secret
<Button </Button>
leftIcon={<FontAwesomeIcon icon={faFolderPlus} />} )}
onClick={() => { </ProjectPermissionCan>
handlePopUpOpen("addFolder"); <DropdownMenu
handlePopUpClose("misc"); open={popUp.misc.isOpen}
}} onOpenChange={(isOpen) => handlePopUpToggle("misc", isOpen)}
isDisabled={!isAllowed} >
variant="outline_bg" <DropdownMenuTrigger asChild>
className="h-10" <IconButton
isFullWidth ariaLabel="add-folder-or-import"
> variant="outline_bg"
Add Folder className="rounded-l-none bg-mineshaft-600 p-3"
</Button> >
)} <FontAwesomeIcon icon={faAngleDown} />
</ProjectPermissionCan> </IconButton>
</div> </DropdownMenuTrigger>
</DropdownMenuContent> <DropdownMenuContent align="end">
</DropdownMenu> <div className="flex flex-col space-y-1 p-1.5">
</div> <ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { secretPath })}
>
{(isAllowed) => (
<Button
leftIcon={<FontAwesomeIcon icon={faFolderPlus} />}
onClick={() => {
handlePopUpOpen("addFolder");
handlePopUpClose("misc");
}}
isDisabled={!isAllowed}
variant="outline_bg"
className="h-10"
isFullWidth
>
Add Folder
</Button>
)}
</ProjectPermissionCan>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -565,14 +575,25 @@ export const SecretOverviewPage = () => {
className="bg-mineshaft-700" className="bg-mineshaft-700"
/> />
)} )}
{isTableEmpty && !isTableLoading && ( {userAvailableEnvs.length > 0 && visibleEnvs.length === 0 && (
<Tr> <Tr>
<Td colSpan={visibleEnvs.length + 1}> <Td colSpan={visibleEnvs.length + 1}>
<EmptyState title="Let's add some secrets" icon={faFolderBlank} iconSize="3x"> <EmptyState title="You have no visible environments" iconSize="3x" />
</Td>
</Tr>
)}
{userAvailableEnvs.length === 0 && (
<Tr>
<Td colSpan={visibleEnvs.length + 1}>
<EmptyState
title="You have no environments, start by adding some"
iconSize="3x"
>
<Link <Link
href={{ href={{
pathname: "/project/[id]/secrets/[env]", pathname: "/project/[id]/settings",
query: { id: workspaceId, env: visibleEnvs?.[0]?.slug } query: { id: workspaceId },
hash: "environments"
}} }}
> >
<Button <Button
@@ -581,13 +602,38 @@ export const SecretOverviewPage = () => {
colorSchema="primary" colorSchema="primary"
size="md" size="md"
> >
Go to {visibleEnvs?.[0]?.name} Add environments
</Button> </Button>
</Link> </Link>
</EmptyState> </EmptyState>
</Td> </Td>
</Tr> </Tr>
)} )}
{isTableEmpty && !isTableLoading && visibleEnvs.length > 0 && (
<Tr>
<Td colSpan={visibleEnvs.length + 1}>
<EmptyState
title={
searchFilter
? "No secret found for your search, add one now"
: "Let's add some secrets"
}
icon={faFolderBlank}
iconSize="3x"
>
<Button
className="mt-4"
variant="outline_bg"
colorSchema="primary"
size="md"
onClick={() => handlePopUpOpen("addSecretsInAllEnvs")}
>
Add Secrets
</Button>
</EmptyState>
</Td>
</Tr>
)}
{!isTableLoading && {!isTableLoading &&
filteredFolderNames.map((folderName, index) => ( filteredFolderNames.map((folderName, index) => (
<SecretOverviewFolderRow <SecretOverviewFolderRow
@@ -599,22 +645,19 @@ export const SecretOverviewPage = () => {
/> />
))} ))}
{!isTableLoading && {!isTableLoading &&
(visibleEnvs?.length > 0 ? ( visibleEnvs?.length > 0 &&
filteredSecretNames.map((key, index) => ( filteredSecretNames.map((key, index) => (
<SecretOverviewTableRow <SecretOverviewTableRow
secretPath={secretPath} secretPath={secretPath}
onSecretCreate={handleSecretCreate} onSecretCreate={handleSecretCreate}
onSecretDelete={handleSecretDelete} onSecretDelete={handleSecretDelete}
onSecretUpdate={handleSecretUpdate} onSecretUpdate={handleSecretUpdate}
key={`overview-${key}-${index + 1}`} key={`overview-${key}-${index + 1}`}
environments={visibleEnvs} environments={visibleEnvs}
secretKey={key} secretKey={key}
getSecretByKey={getSecretByKey} getSecretByKey={getSecretByKey}
expandableColWidth={expandableTableWidth} expandableColWidth={expandableTableWidth}
/> />
))
) : (
<PermissionDeniedBanner />
))} ))}
</TBody> </TBody>
<TFoot> <TFoot>
@@ -171,7 +171,7 @@ export const CreateSecretForm = ({
)} )}
/> />
<FormLabel label="Environments" className="mb-2" /> <FormLabel label="Environments" className="mb-2" />
<div className="thin-scrollbar grid max-h-64 grid-cols-3 gap-4 overflow-auto "> <div className="thin-scrollbar grid max-h-64 grid-cols-3 gap-4 overflow-auto py-2">
{environments.map((env) => { {environments.map((env) => {
return ( return (
<Controller <Controller
@@ -183,13 +183,23 @@ export const CreateSecretForm = ({
isChecked={field.value} isChecked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
id={`secret-input-${env.slug}`} id={`secret-input-${env.slug}`}
className="!justify-start"
> >
{env.name} <span className="flex w-full flex-row items-center justify-start whitespace-pre-wrap">
{getSecretByKey(env.slug, newSecretKey) && ( <span title={env.name} className="truncate">
<Tooltip content="Secret exists. Will be overwritten"> {env.name}
<FontAwesomeIcon icon={faWarning} className="ml-1 text-yellow-400" /> </span>
</Tooltip> <span>
)} {getSecretByKey(env.slug, newSecretKey) && (
<Tooltip
className="max-w-[150px]"
content="Secret already exists, and it will be overwritten"
>
<FontAwesomeIcon icon={faWarning} className="ml-1 text-yellow-400" />
</Tooltip>
)}
</span>
</span>
</Checkbox> </Checkbox>
)} )}
/> />
@@ -148,8 +148,13 @@ export const SecretOverviewTableRow = ({
key={`secret-expanded-${slug}-${secretKey}`} key={`secret-expanded-${slug}-${secretKey}`}
className="hover:bg-mineshaft-700" className="hover:bg-mineshaft-700"
> >
<td className="flex" style={{ padding: "0.25rem 1rem" }}> <td
<div className="flex h-8 items-center">{name}</div> className="flex h-full items-center"
style={{ padding: "0.25rem 1rem" }}
>
<div title={name} className="flex h-8 w-[8rem] items-center ">
<span className="truncate">{name}</span>
</div>
</td> </td>
<td className="col-span-2 h-8 w-full"> <td className="col-span-2 h-8 w-full">
<SecretEditRow <SecretEditRow
@@ -63,7 +63,10 @@ export const EnvironmentSection = () => {
}; };
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div
id="environments"
className="mb-6 scroll-m-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<div className="mb-8 flex justify-between"> <div className="mb-8 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Environments</p> <p className="text-xl font-semibold text-mineshaft-100">Environments</p>
<div> <div>