feat: added new secret input component and updated toolbar key special prop to innerKey

This commit is contained in:
akhilmhdh
2023-07-27 16:36:34 +05:30
parent b6b3c8a736
commit bca14dd5c4
34 changed files with 2969 additions and 2505 deletions
@@ -1,9 +1,5 @@
import { useState } from "react";
import {
faContactBook,
faMagnifyingGlass,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { faContactBook, faMagnifyingGlass, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@@ -23,13 +19,11 @@ import {
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import {
useDeleteIncidentContact,
useGetOrgIncidentContact} from "@app/hooks/api";
import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api";
export const OrgIncidentContactsTable = () => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { data: contacts, isLoading } = useGetOrgIncidentContact(currentOrg?._id ?? "");
const [searchContact, setSearchContact] = useState("");
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
@@ -40,71 +34,71 @@ export const OrgIncidentContactsTable = () => {
const onRemoveIncidentContact = async () => {
try {
const incidentContactEmail = (popUp?.removeContact?.data as { email: string })?.email;
if (!currentOrg?._id) return;
await mutateAsync({
orgId: currentOrg._id,
email: incidentContactEmail
});
const incidentContactEmail = (popUp?.removeContact?.data as { email: string })?.email;
createNotification({
text: "Successfully removed incident contact",
type: "success"
});
handlePopUpClose("removeContact");
if (!currentOrg?._id) return;
await mutateAsync({
orgId: currentOrg._id,
email: incidentContactEmail
});
createNotification({
text: "Successfully removed incident contact",
type: "success"
});
handlePopUpClose("removeContact");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to remove incident contact",
type: "error"
});
console.error(err);
createNotification({
text: "Failed to remove incident contact",
type: "error"
});
}
};
const filteredContacts = contacts ? contacts.filter(({ email }) =>
email.toLocaleLowerCase().includes(searchContact)
) : [];
const filteredContacts = contacts
? contacts.filter(({ email }) => email.toLocaleLowerCase().includes(searchContact))
: [];
return (
<div>
<Input
value={searchContact}
onChange={(e) => setSearchContact(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search incident contact by email..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Email</Th>
<Th aria-label="actions" />
<Input
value={searchContact}
onChange={(e) => setSearchContact(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search incident contact by email..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Email</Th>
<Th aria-label="actions" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={2} innerKey="incident-contact" />}
{filteredContacts?.map(({ email }) => (
<Tr key={email}>
<Td className="w-full">{email}</Td>
<Td className="mr-4">
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeContact", { email })}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Td>
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={2} key="incident-contact" />}
{filteredContacts?.map(({ email }) => (
<Tr key={email}>
<Td className="w-full">{email}</Td>
<Td className="mr-4">
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeContact", { email })}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Td>
</Tr>
))}
</TBody>
</Table>
{filteredContacts?.length === 0 && !isLoading && (
<EmptyState title="No incident contacts found" icon={faContactBook} />
)}
</TableContainer>
))}
</TBody>
</Table>
{filteredContacts?.length === 0 && !isLoading && (
<EmptyState title="No incident contacts found" icon={faContactBook} />
)}
</TableContainer>
<DeleteActionModal
isOpen={popUp.removeContact.isOpen}
deleteKey="remove"
@@ -1,17 +1,14 @@
import { useEffect, useMemo,useState } from "react";
import {
// Controller,
// useForm
} from "react-hook-form";
import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/router";
import {
faCheck,
faCopy,
faMagnifyingGlass,
faPencil,
faPlus,
faServer,
faTrash} from "@fortawesome/free-solid-svg-icons";
import {
faCheck,
faCopy,
faMagnifyingGlass,
faPencil,
faPlus,
faServer,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import { yupResolver } from "@hookform/resolvers/yup";
@@ -21,13 +18,13 @@ import {
Button,
DeleteActionModal,
EmptyState,
// FormControl,
// FormControl,
IconButton,
Input,
Modal,
ModalContent,
// Select,
// SelectItem,
// Select,
// SelectItem,
Table,
TableContainer,
TableSkeleton,
@@ -40,11 +37,15 @@ import {
import { useOrganization, useWorkspace } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
import {
// useCreateServiceAccount,
useDeleteServiceAccount,
useGetServiceAccounts
// useCreateServiceAccount,
useDeleteServiceAccount,
useGetServiceAccounts
} from "@app/hooks/api";
import // Controller,
// useForm
"react-hook-form";
// const serviceAccountExpiration = [
// { label: "1 Day", value: 86400 },
// { label: "7 Days", value: 604800 },
@@ -62,314 +63,312 @@ import {
// type TAddServiceAccountForm = yup.InferType<typeof addServiceAccountFormSchema>;
export const OrgServiceAccountsTable = () => {
const router = useRouter();
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace();
const orgId = currentOrg?._id || "";
const [step, setStep] = useState(0);
const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false);
const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false);
const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false);
const [accessKey] = useState("");
const [publicKey] = useState("");
const [privateKey] = useState("");
const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState("");
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addServiceAccount",
"removeServiceAccount",
] as const);
const router = useRouter();
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace();
const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } = useGetServiceAccounts(orgId);
// const createServiceAccount = useCreateServiceAccount();
const removeServiceAccount = useDeleteServiceAccount();
useEffect(() => {
let timer: NodeJS.Timeout;
if (isAccessKeyCopied) {
timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000);
}
const orgId = currentOrg?._id || "";
const [step, setStep] = useState(0);
const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false);
const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false);
const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false);
const [accessKey] = useState("");
const [publicKey] = useState("");
const [privateKey] = useState("");
const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState("");
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addServiceAccount",
"removeServiceAccount"
] as const);
if (isPublicKeyCopied) {
timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000);
}
const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } =
useGetServiceAccounts(orgId);
if (isPrivateKeyCopied) {
timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000);
}
// const createServiceAccount = useCreateServiceAccount();
const removeServiceAccount = useDeleteServiceAccount();
return () => clearTimeout(timer);
}, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]);
// const {
// control,
// handleSubmit,
// reset,
// formState: { isSubmitting }
// } = useForm<TAddServiceAccountForm>({ resolver: yupResolver(addServiceAccountFormSchema) });
// const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => {
// if (!currentOrg?._id) return;
// const keyPair = generateKeyPair();
// setPublicKey(keyPair.publicKey);
// setPrivateKey(keyPair.privateKey);
// const serviceAccountDetails = await createServiceAccount.mutateAsync({
// name,
// organizationId: currentOrg?._id,
// publicKey: keyPair.publicKey,
// expiresIn: Number(expiresIn)
// });
// setAccessKey(serviceAccountDetails.serviceAccountAccessKey);
// setStep(1);
// reset();
// }
const onRemoveServiceAccount = async () => {
const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id;
await removeServiceAccount.mutateAsync(serviceAccountId);
handlePopUpClose("removeServiceAccount");
useEffect(() => {
let timer: NodeJS.Timeout;
if (isAccessKeyCopied) {
timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000);
}
const filteredServiceAccounts = useMemo(
() =>
serviceAccounts.filter(
({ name }) =>
name.toLowerCase().includes(searchServiceAccountFilter)
),
[serviceAccounts, searchServiceAccountFilter]
);
const renderStep = (stepToRender: number) => {
switch (stepToRender) {
case 0:
return (
<div>
We are currently revising the service account mechanism. In the meantime,
please use service tokens or API key to fetch secrets via API request.
</div>
// <form onSubmit={handleSubmit(onAddServiceAccount)}>
// <Controller
// control={control}
// defaultValue=""
// name="name"
// render={({ field, fieldState: { error } }) => (
// <FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
// <Input {...field} />
// </FormControl>
// )}
// />
// <Controller
// control={control}
// name="expiresIn"
// defaultValue={String(serviceAccountExpiration?.[0]?.value)}
// render={({ field: { onChange, ...field }, fieldState: { error } }) => {
// return (
// <FormControl
// label="Expiration"
// errorText={error?.message}
// isError={Boolean(error)}
// >
// <Select
// defaultValue={field.value}
// {...field}
// onValueChange={(e) => onChange(e)}
// className="w-full"
// >
// {serviceAccountExpiration.map(({ label, value }) => (
// <SelectItem value={String(value)} key={label}>
// {label}
// </SelectItem>
// ))}
// </Select>
// </FormControl>
// );
// }}
// />
// <div className="mt-8 flex items-center">
// <Button
// className="mr-4"
// size="sm"
// type="submit"
// isLoading={isSubmitting}
// isDisabled={isSubmitting}
// >
// Create Service Account
// </Button>
// <Button
// colorSchema="secondary"
// variant="plain"
// onClick={() => handlePopUpClose("addServiceAccount")}
// >
// Cancel
// </Button>
// </div>
// </form>
);
case 1:
return (
<>
<p>Access Key</p>
<div className="flex items-center justify-end rounded-md p-2 text-base text-gray-400 bg-white/[0.07]">
<p className="mr-4 break-all">{accessKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(accessKey);
setIsAccessKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isAccessKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
<p className="mt-4">Public Key</p>
<div className="flex items-center justify-end rounded-md p-2 text-base text-gray-400 bg-white/[0.07]">
<p className="mr-4 break-all">{publicKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(publicKey);
setIsPublicKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isPublicKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
<p className="mt-4">Private Key</p>
<div className="flex items-center justify-end rounded-md p-2 text-base text-gray-400 bg-white/[0.07]">
<p className="mr-4 break-all">{privateKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(privateKey);
setIsPrivateKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isPrivateKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
</>
);
default:
return <div />
}
if (isPublicKeyCopied) {
timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000);
}
return (
<div className="w-full">
<div className="flex justify-between mb-4">
<p className="text-xl font-semibold text-mineshaft-100">Service Accounts</p>
<Button
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
setStep(0);
// reset();
handlePopUpOpen("addServiceAccount");
}}
>
Add Service Account
</Button>
</div>
<Input
value={searchServiceAccountFilter}
onChange={(e) => setSearchServiceAccountFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search service accounts..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Th>Name</Th>
<Th className="w-full">Valid Until</Th>
<Th aria-label="actions" />
</THead>
<TBody>
{isServiceAccountsLoading && <TableSkeleton columns={5} key="org-service-accounts" />}
{!isServiceAccountsLoading && (
filteredServiceAccounts.map(({
name,
expiresAt,
_id: serviceAccountId
}) => {
return (
<Tr key={`org-service-account-${serviceAccountId}`}>
<Td>{name}</Td>
<Td>{new Date(expiresAt).toUTCString()}</Td>
<Td>
<div className="flex">
<IconButton
ariaLabel="edit"
colorSchema="secondary"
onClick={() => {
if (currentWorkspace?._id) {
router.push(`/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}`);
}
}}
className="mr-2"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId })}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
</Td>
</Tr>
);
})
)}
</TBody>
</Table>
{!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && (
<EmptyState title="No service accounts found" icon={faServer} />
)}
</TableContainer>
<Modal
isOpen={popUp?.addServiceAccount?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addServiceAccount", isOpen);
// reset();
if (isPrivateKeyCopied) {
timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]);
// const {
// control,
// handleSubmit,
// reset,
// formState: { isSubmitting }
// } = useForm<TAddServiceAccountForm>({ resolver: yupResolver(addServiceAccountFormSchema) });
// const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => {
// if (!currentOrg?._id) return;
// const keyPair = generateKeyPair();
// setPublicKey(keyPair.publicKey);
// setPrivateKey(keyPair.privateKey);
// const serviceAccountDetails = await createServiceAccount.mutateAsync({
// name,
// organizationId: currentOrg?._id,
// publicKey: keyPair.publicKey,
// expiresIn: Number(expiresIn)
// });
// setAccessKey(serviceAccountDetails.serviceAccountAccessKey);
// setStep(1);
// reset();
// }
const onRemoveServiceAccount = async () => {
const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id;
await removeServiceAccount.mutateAsync(serviceAccountId);
handlePopUpClose("removeServiceAccount");
};
const filteredServiceAccounts = useMemo(
() =>
serviceAccounts.filter(({ name }) => name.toLowerCase().includes(searchServiceAccountFilter)),
[serviceAccounts, searchServiceAccountFilter]
);
const renderStep = (stepToRender: number) => {
switch (stepToRender) {
case 0:
return (
<div>
We are currently revising the service account mechanism. In the meantime, please use
service tokens or API key to fetch secrets via API request.
</div>
// <form onSubmit={handleSubmit(onAddServiceAccount)}>
// <Controller
// control={control}
// defaultValue=""
// name="name"
// render={({ field, fieldState: { error } }) => (
// <FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
// <Input {...field} />
// </FormControl>
// )}
// />
// <Controller
// control={control}
// name="expiresIn"
// defaultValue={String(serviceAccountExpiration?.[0]?.value)}
// render={({ field: { onChange, ...field }, fieldState: { error } }) => {
// return (
// <FormControl
// label="Expiration"
// errorText={error?.message}
// isError={Boolean(error)}
// >
// <Select
// defaultValue={field.value}
// {...field}
// onValueChange={(e) => onChange(e)}
// className="w-full"
// >
// {serviceAccountExpiration.map(({ label, value }) => (
// <SelectItem value={String(value)} key={label}>
// {label}
// </SelectItem>
// ))}
// </Select>
// </FormControl>
// );
// }}
// />
// <div className="mt-8 flex items-center">
// <Button
// className="mr-4"
// size="sm"
// type="submit"
// isLoading={isSubmitting}
// isDisabled={isSubmitting}
// >
// Create Service Account
// </Button>
// <Button
// colorSchema="secondary"
// variant="plain"
// onClick={() => handlePopUpClose("addServiceAccount")}
// >
// Cancel
// </Button>
// </div>
// </form>
);
case 1:
return (
<>
<p>Access Key</p>
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{accessKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(accessKey);
setIsAccessKeyCopied.on();
}}
>
<ModalContent
title="Add Service Account"
subTitle="A service account represents a machine identity such as a VM or application client."
>
{renderStep(step)}
</ModalContent>
</Modal>
<DeleteActionModal
isOpen={popUp.removeServiceAccount.isOpen}
deleteKey="remove"
title="Do you want to remove this service account from the org?"
onChange={(isOpen) => handlePopUpToggle("removeServiceAccount", isOpen)}
onDeleteApproved={onRemoveServiceAccount}
/>
</div>
);
}
>
<FontAwesomeIcon icon={isAccessKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
<p className="mt-4">Public Key</p>
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{publicKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(publicKey);
setIsPublicKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isPublicKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
<p className="mt-4">Private Key</p>
<div className="flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{privateKey}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(privateKey);
setIsPrivateKeyCopied.on();
}}
>
<FontAwesomeIcon icon={isPrivateKeyCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
</div>
</>
);
default:
return <div />;
}
};
return (
<div className="w-full">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Service Accounts</p>
<Button
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
setStep(0);
// reset();
handlePopUpOpen("addServiceAccount");
}}
>
Add Service Account
</Button>
</div>
<Input
value={searchServiceAccountFilter}
onChange={(e) => setSearchServiceAccountFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search service accounts..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Th>Name</Th>
<Th className="w-full">Valid Until</Th>
<Th aria-label="actions" />
</THead>
<TBody>
{isServiceAccountsLoading && (
<TableSkeleton columns={5} innerKey="org-service-accounts" />
)}
{!isServiceAccountsLoading &&
filteredServiceAccounts.map(({ name, expiresAt, _id: serviceAccountId }) => {
return (
<Tr key={`org-service-account-${serviceAccountId}`}>
<Td>{name}</Td>
<Td>{new Date(expiresAt).toUTCString()}</Td>
<Td>
<div className="flex">
<IconButton
ariaLabel="edit"
colorSchema="secondary"
onClick={() => {
if (currentWorkspace?._id) {
router.push(
`/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}`
);
}
}}
className="mr-2"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() =>
handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId })
}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && (
<EmptyState title="No service accounts found" icon={faServer} />
)}
</TableContainer>
<Modal
isOpen={popUp?.addServiceAccount?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addServiceAccount", isOpen);
// reset();
}}
>
<ModalContent
title="Add Service Account"
subTitle="A service account represents a machine identity such as a VM or application client."
>
{renderStep(step)}
</ModalContent>
</Modal>
<DeleteActionModal
isOpen={popUp.removeServiceAccount.isOpen}
deleteKey="remove"
title="Do you want to remove this service account from the org?"
onChange={(isOpen) => handlePopUpToggle("removeServiceAccount", isOpen)}
onDeleteApproved={onRemoveServiceAccount}
/>
</div>
);
};