feat(ui): finished ui for identity additional privilege

This commit is contained in:
Akhil Mohan
2024-03-15 21:19:09 +05:30
parent 8529fac098
commit 5f84de039f
17 changed files with 569 additions and 279 deletions

View File

@ -41,6 +41,18 @@ export type IdentityMembership = {
temporaryAccessStartTime: string | null;
temporaryAccessEndTime: string | null;
}[];
additionalPrivileges: {
id: string;
name: string;
description: string | null | undefined;
slug: string;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessEndTime: string | null | undefined;
temporaryAccessStartTime: string | null | undefined;
isTemporary: boolean;
createdAt: string;
}[];
createdAt: string;
updatedAt: string;
};

View File

@ -0,0 +1,7 @@
export {
useCreateIdentityProjectAdditionalPrivilege,
useDeleteIdentityProjectAdditionalPrivilege,
useUpdateIdentityProjectAdditionalPrivilege
} from "./mutation";
export { useGetIdentityProjectPrivilegeDetails } from "./queries";
export type { TIdentityProjectPrivilege } from "./types";

View File

@ -0,0 +1,74 @@
import { packRules } from "@casl/ability/extra";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { workspaceKeys } from "../workspace/queries";
import {
TCreateIdentityProjectPrivilegeDTO,
TDeleteIdentityProjectPrivilegeDTO,
TIdentityProjectPrivilege,
TUpdateIdentityProjectPrivlegeDTO
} from "./types";
export const useCreateIdentityProjectAdditionalPrivilege = () => {
const queryClient = useQueryClient();
return useMutation<
{ privilege: TIdentityProjectPrivilege },
{},
TCreateIdentityProjectPrivilegeDTO
>({
mutationFn: async (dto) => {
const { data } = await apiRequest.post("/api/v1/additional-privilege/identity", {
...dto,
permissions: packRules(dto.permissions)
});
return data.privilege;
},
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(projectId));
}
});
};
export const useUpdateIdentityProjectAdditionalPrivilege = () => {
const queryClient = useQueryClient();
return useMutation<
{ privilege: TIdentityProjectPrivilege },
{},
TUpdateIdentityProjectPrivlegeDTO
>({
mutationFn: async (dto) => {
const { data } = await apiRequest.patch(
`/api/v1/additional-privilege/identity/${dto.privilegeId}`,
{ ...dto, permissions: dto.permissions ? packRules(dto.permissions) : undefined }
);
return data.privilege;
},
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(projectId));
}
});
};
export const useDeleteIdentityProjectAdditionalPrivilege = () => {
const queryClient = useQueryClient();
return useMutation<
{ privilege: TIdentityProjectPrivilege },
{},
TDeleteIdentityProjectPrivilegeDTO
>({
mutationFn: async (dto) => {
const { data } = await apiRequest.delete(
`/api/v1/additional-privilege/identity/${dto.privilegeId}`
);
return data.privilege;
},
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIdentityMemberships(projectId));
}
});
};

View File

@ -0,0 +1,31 @@
import { PackRule, unpackRules } from "@casl/ability/extra";
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TProjectPermission } from "../roles/types";
import { TIdentityProjectPrivilege } from "./types";
export const identitiyProjectPrivilegeKeys = {
details: (privilegeId: string) => ["project-user-privilege", { privilegeId }] as const
};
const fetchIdentityProjectPrivilegeDetails = async (privilegeId: string) => {
const {
data: { privilege }
} = await apiRequest.get<{
privilege: Omit<TIdentityProjectPrivilege, "permissions"> & { permissions: unknown };
}>(`/api/v1/additional-privilege/identity/${privilegeId}`);
return {
...privilege,
permissions: unpackRules(privilege.permissions as PackRule<TProjectPermission>[])
};
};
export const useGetIdentityProjectPrivilegeDetails = (privilegeId: string) => {
return useQuery({
enabled: Boolean(privilegeId),
queryKey: identitiyProjectPrivilegeKeys.details(privilegeId),
queryFn: () => fetchIdentityProjectPrivilegeDetails(privilegeId)
});
};

View File

@ -0,0 +1,48 @@
import { TProjectPermission } from "../roles/types";
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
Relative = "relative"
}
export type TIdentityProjectPrivilege = {
projectMembershipId: string;
slug: string;
name: string;
isTemporary: boolean;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
temporaryMode?: string | null | undefined;
temporaryRange?: string | null | undefined;
temporaryAccessStartTime?: string | null | undefined;
temporaryAccessEndTime?: Date | null | undefined;
permissions?: TProjectPermission[];
};
export type TCreateIdentityProjectPrivilegeDTO = {
identityId: string;
projectId: string;
slug: string;
name: string;
description?: string;
isTemporary?: boolean;
temporaryMode?: IdentityProjectAdditionalPrivilegeTemporaryMode;
temporaryRange?: string;
temporaryAccessStartTime?: string;
permissions: TProjectPermission[];
};
export type TUpdateIdentityProjectPrivlegeDTO = {
privilegeId: string;
projectId: string;
} & Partial<Omit<TCreateIdentityProjectPrivilegeDTO, "projectMembershipId" | "projectId">>;
export type TDeleteIdentityProjectPrivilegeDTO = {
privilegeId: string;
projectId: string;
};
export type TGetIdentityProejctPrivilegeDetails = {
privilegeId: string;
};

View File

@ -6,6 +6,7 @@ export * from "./bots";
export * from "./dynamicSecret";
export * from "./dynamicSecretLease";
export * from "./identities";
export * from "./identityProjectAdditionalPrivilege";
export * from "./incidentContacts";
export * from "./integrationAuth";
export * from "./integrations";

View File

@ -22,8 +22,11 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
import { Button, FormControl, Input } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import {
useCreateIdentityProjectAdditionalPrivilege,
useCreateProjectUserAdditionalPrivilege,
useGetIdentityProjectPrivilegeDetails,
useGetProjectUserPrivilegeDetails,
useUpdateIdentityProjectAdditionalPrivilege,
useUpdateProjectUserAdditionalPrivilege
} from "@app/hooks/api";
@ -32,7 +35,6 @@ import {
formRolePermission2API,
formSchema,
rolePermission2Form,
// rolePermission2Form,
TFormSchema
} from "../ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils";
import { SecretRollbackPermission } from "../ProjectRoleListTab/components/ProjectRoleModifySection/SecretRollbackPermission";
@ -124,14 +126,26 @@ type Props = {
actorId: string;
};
export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, workspaceId }: Props) => {
export const AdditionalPrivilegeForm = ({
onGoBack,
privilegeId,
actorId,
workspaceId,
isIdentity
}: Props) => {
const { createNotification } = useNotificationContext();
const isNewRole = !privilegeId;
const { data: projectUserPrivilegeDetails } = useGetProjectUserPrivilegeDetails(
privilegeId || ""
privilegeId && !isIdentity ? privilegeId : ""
);
const { data: identityProjectPrivilegeDetails } = useGetIdentityProjectPrivilegeDetails(
isIdentity && privilegeId ? privilegeId : ""
);
const privileges = isIdentity ? identityProjectPrivilegeDetails : projectUserPrivilegeDetails;
const {
handleSubmit,
register,
@ -141,24 +155,36 @@ export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, worksp
control
} = useForm<TFormSchema>({
resolver: zodResolver(formSchema),
values: projectUserPrivilegeDetails && {
...projectUserPrivilegeDetails,
description: projectUserPrivilegeDetails.description || "",
permissions: rolePermission2Form(projectUserPrivilegeDetails.permissions)
values: privileges && {
...privileges,
description: privileges.description || "",
permissions: rolePermission2Form(privileges.permissions)
}
});
const createProjectUserAdditionalPrivilege = useCreateProjectUserAdditionalPrivilege();
const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege();
const createIdentityProjectAdditionalPrivilege = useCreateIdentityProjectAdditionalPrivilege();
const updateIdentityProjectAdditionalPrivilege = useUpdateIdentityProjectAdditionalPrivilege();
const handleRoleUpdate = async (el: TFormSchema) => {
try {
await updateProjectUserAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
privilegeId: privilegeId as string,
workspaceId
});
if (isIdentity) {
await updateIdentityProjectAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
privilegeId: privilegeId as string,
projectId: workspaceId
});
} else {
await updateProjectUserAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
privilegeId: privilegeId as string,
workspaceId
});
}
createNotification({ type: "success", text: "Successfully update privilege" });
onGoBack();
} catch (err) {
@ -174,12 +200,21 @@ export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, worksp
}
try {
await createProjectUserAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
projectMembershipId: actorId,
workspaceId
});
if (isIdentity) {
await createIdentityProjectAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
identityId: actorId,
projectId: workspaceId
});
} else {
await createProjectUserAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
projectMembershipId: actorId,
workspaceId
});
}
createNotification({ type: "success", text: "Created new privilege" });
onGoBack();
} catch (err) {

View File

@ -19,7 +19,10 @@ import {
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteProjectUserAdditionalPrivilege } from "@app/hooks/api";
import {
useDeleteIdentityProjectAdditionalPrivilege,
useDeleteProjectUserAdditionalPrivilege
} from "@app/hooks/api";
import { TWorkspaceUser } from "@app/hooks/api/types";
import { AdditionalPrivilegeForm } from "./AdditionalPrivilegeForm";
@ -28,40 +31,51 @@ import { AdditionalPrivilegeTemporaryAccess } from "./AdditionalPrivilegeTempora
type Props = {
onGoBack: VoidFunction;
name: string;
projectMembershipId: string;
isIdentity?: boolean;
// isIdentity id - identity id else projectMembershipId
actorId: string;
privileges: TWorkspaceUser["additionalPrivileges"];
};
export const AdditionalPrivilegeSection = ({
onGoBack,
privileges = [],
projectMembershipId,
name
actorId,
name,
isIdentity
}: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"modifyPrivilege",
"deletePrivilege"
] as const);
const { createNotification } = useNotificationContext();
const deleteProjectUserAdditionalPrivilege = useDeleteProjectUserAdditionalPrivilege();
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id || "";
const deleteProjectUserAdditionalPrivilege = useDeleteProjectUserAdditionalPrivilege();
const deleteProjectIdentityAdditionalPrivilege = useDeleteIdentityProjectAdditionalPrivilege();
const onPrivilegeDelete = async (privilegeId: string) => {
try {
await deleteProjectUserAdditionalPrivilege.mutateAsync({
privilegeId,
workspaceId
});
if (isIdentity) {
await deleteProjectIdentityAdditionalPrivilege.mutateAsync({
privilegeId,
projectId: workspaceId
});
} else {
await deleteProjectUserAdditionalPrivilege.mutateAsync({
privilegeId,
workspaceId
});
}
handlePopUpClose("deletePrivilege");
createNotification({
type: "success",
text: "Successfully removed user privilege"
text: "Successfully removed privilege"
});
} catch (err) {
createNotification({
type: "error",
text: "Failed to delete user privilege"
text: "Failed to delete privilege"
});
}
};
@ -83,7 +97,8 @@ export const AdditionalPrivilegeSection = ({
onGoBack={() => handlePopUpClose("modifyPrivilege")}
privilegeId={privilegeDetails?.id}
workspaceId={workspaceId}
actorId={projectMembershipId}
isIdentity={isIdentity}
actorId={actorId}
/>
</motion.div>
);
@ -119,7 +134,11 @@ export const AdditionalPrivilegeSection = ({
</div>
<div className="mt-6 flex flex-col space-y-4">
{privileges.length === 0 && (
<EmptyState title="User has no additional privileges" iconSize="3x" icon={faUserShield} />
<EmptyState
title={`${isIdentity ? "Machine identity" : "User"} has no additional privileges`}
iconSize="3x"
icon={faUserShield}
/>
)}
{privileges.map(({ id, name: privilegeName, description, slug, ...dto }) => (
<div
@ -137,6 +156,7 @@ export const AdditionalPrivilegeSection = ({
</div>
<div className="flex items-center space-x-4">
<AdditionalPrivilegeTemporaryAccess
isIdentity={isIdentity}
privilegeId={id}
workspaceId={workspaceId}
temporaryConfig={!dto.isTemporary ? { isTemporary: false } : { ...dto }}

View File

@ -18,7 +18,11 @@ import {
Tooltip
} from "@app/components/v2";
import { usePopUp } from "@app/hooks";
import { useUpdateProjectUserAdditionalPrivilege } from "@app/hooks/api";
import {
useUpdateIdentityProjectAdditionalPrivilege,
useUpdateProjectUserAdditionalPrivilege
} from "@app/hooks/api";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/identityProjectAdditionalPrivilege/types";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
const temporaryRoleFormSchema = z.object({
@ -30,6 +34,7 @@ type TTemporaryRoleFormSchema = z.infer<typeof temporaryRoleFormSchema>;
type TTemporaryRoleFormProps = {
privilegeId: string;
workspaceId: string;
isIdentity?: boolean;
temporaryConfig?: {
isTemporary?: boolean;
temporaryAccessEndTime?: string | null;
@ -41,7 +46,8 @@ type TTemporaryRoleFormProps = {
export const AdditionalPrivilegeTemporaryAccess = ({
temporaryConfig: defaultValues = {},
workspaceId,
privilegeId
privilegeId,
isIdentity
}: TTemporaryRoleFormProps) => {
const { popUp, handlePopUpToggle } = usePopUp(["setTempRole"] as const);
const { createNotification } = useNotificationContext();
@ -56,17 +62,29 @@ export const AdditionalPrivilegeTemporaryAccess = ({
isTemporaryFieldValue && new Date() > new Date(defaultValues.temporaryAccessEndTime || "");
const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege();
const updateProjectIdentityAdditionalPrivilege = useUpdateIdentityProjectAdditionalPrivilege();
const handleGrantTemporaryAccess = async (el: TTemporaryRoleFormSchema) => {
try {
await updateProjectUserAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
workspaceId,
isTemporary: true,
temporaryRange: el.temporaryRange,
temporaryAccessStartTime: new Date().toISOString(),
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative
});
if (isIdentity) {
await updateProjectIdentityAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
projectId: workspaceId,
isTemporary: true,
temporaryRange: el.temporaryRange,
temporaryAccessStartTime: new Date().toISOString(),
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative
});
} else {
await updateProjectUserAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
workspaceId,
isTemporary: true,
temporaryRange: el.temporaryRange,
temporaryAccessStartTime: new Date().toISOString(),
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative
});
}
createNotification({ type: "success", text: "Successfully updated access" });
handlePopUpToggle("setTempRole");
} catch (err) {
@ -77,11 +95,19 @@ export const AdditionalPrivilegeTemporaryAccess = ({
const handleRevokeTemporaryAccess = async () => {
try {
await updateProjectUserAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
workspaceId,
isTemporary: false
});
if (isIdentity) {
await updateProjectIdentityAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
projectId: workspaceId,
isTemporary: false
});
} else {
await updateProjectUserAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
workspaceId,
isTemporary: false
});
}
createNotification({ type: "success", text: "Successfully updated access" });
handlePopUpToggle("setTempRole");
} catch (err) {

View File

@ -1,17 +1,270 @@
import Link from "next/link";
import {
faArrowUpRightFromSquare,
faPlus,
faServer,
faUserShield,
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { IdentitySection } from "./components";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { withProjectPermission } from "@app/hoc";
import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
export const IdentityTab = () => {
return (
<motion.div
key="panel-identity"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<IdentitySection />
</motion.div>
);
};
import { AdditionalPrivilegeSection } from "../AdditionalPrivilegeSection";
import { IdentityModal } from "./components/IdentityModal";
import { IdentityRoles } from "./components/IdentityRoles";
export const IdentityTab = withProjectPermission(
() => {
const { createNotification } = useNotificationContext();
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id ?? "";
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity",
"deleteIdentity",
"upgradePlan",
"additionalPrivilege"
] as const);
const onRemoveIdentitySubmit = async (identityId: string) => {
try {
await deleteMutateAsync({
identityId,
workspaceId
});
createNotification({
text: "Successfully removed identity from project",
type: "success"
});
handlePopUpClose("deleteIdentity");
} catch (err) {
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to remove identity from project";
createNotification({
text,
type: "error"
});
}
};
if (popUp.additionalPrivilege.isOpen) {
const privilegeDetails = popUp?.additionalPrivilege?.data as {
name: string;
index: number;
identityId: string;
};
return (
<motion.div
key="panel-additional-permission"
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<AdditionalPrivilegeSection
isIdentity
onGoBack={() => handlePopUpClose("additionalPrivilege")}
privileges={data?.[privilegeDetails.index]?.additionalPrivileges || []}
name={privilegeDetails.name}
actorId={privilegeDetails.identityId}
/>
</motion.div>
);
}
return (
<motion.div
key="panel-identity"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<div className="flex w-full justify-end pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
Documentation{" "}
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs"
/>
</span>
</Link>
</div>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("identity")}
isDisabled={!isAllowed}
>
Add identity
</Button>
)}
</ProjectPermissionCan>
</div>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Role</Th>
<Th>Added on</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(
({ identity: { id, name }, roles, createdAt, additionalPrivileges }, index) => {
const hasAdditionalPrivilege = Boolean(additionalPrivileges.length);
return (
<Tr className="h-10" key={`st-v3-${id}`}>
<Td>{name}</Td>
<Td>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IdentityRoles
roles={roles}
disableEdit={!isAllowed}
identityId={id}
/>
)}
</ProjectPermissionCan>
</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<div className="flex items-center space-x-2">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Member}
allowedLabel="Additional Privilege"
>
{(isAllowed) => (
<IconButton
size="lg"
variant="plain"
ariaLabel="update"
className={twMerge(hasAdditionalPrivilege && "text-primary")}
isDisabled={!isAllowed}
onClick={() =>
handlePopUpOpen("additionalPrivilege", {
name,
index,
identityId: id
})
}
>
<FontAwesomeIcon icon={faUserShield} />
</IconButton>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</div>
</Td>
</Tr>
);
}
)}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={7}>
<EmptyState
title="No identities have been added to this project"
icon={faServer}
/>
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure want to remove ${(popUp?.deleteIdentity?.data as { name: string })?.name || ""
} from the project?`}
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onRemoveIdentitySubmit(
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
)
}
/>
</div>
</motion.div>
);
},
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Identity }
);

View File

@ -1,107 +0,0 @@
import Link from "next/link";
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { withProjectPermission } from "@app/hoc";
import { useDeleteIdentityFromWorkspace } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { IdentityModal } from "./IdentityModal";
import { IdentityTable } from "./IdentityTable";
export const IdentitySection = withProjectPermission(
() => {
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id ?? "";
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity",
"deleteIdentity",
"upgradePlan"
] as const);
const onRemoveIdentitySubmit = async (identityId: string) => {
try {
await deleteMutateAsync({
identityId,
workspaceId
});
createNotification({
text: "Successfully removed identity from project",
type: "success"
});
handlePopUpClose("deleteIdentity");
} catch (err) {
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to remove identity from project";
createNotification({
text,
type: "error"
});
}
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<div className="flex w-full justify-end pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
Documentation{" "}
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs"
/>
</span>
</Link>
</div>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("identity")}
isDisabled={!isAllowed}
>
Add identity
</Button>
)}
</ProjectPermissionCan>
</div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure want to remove ${
(popUp?.deleteIdentity?.data as { name: string })?.name || ""
} from the project?`}
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onRemoveIdentitySubmit(
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
)
}
/>
</div>
);
},
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Identity }
);

View File

@ -1,108 +0,0 @@
import { faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityRoles } from "./IdentityRoles";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteIdentity", "identity"]>,
data?: {
identityId?: string;
name?: string;
}
) => void;
};
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Role</Th>
<Th>Added on</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(({ identity: { id, name }, roles, createdAt }) => {
return (
<Tr className="h-10" key={`st-v3-${id}`}>
<Td>{name}</Td>
<Td>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IdentityRoles roles={roles} disableEdit={!isAllowed} identityId={id} />
)}
</ProjectPermissionCan>
</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={7}>
<EmptyState title="No identities have been added to this project" icon={faServer} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
);
};

View File

@ -1 +0,0 @@
export { IdentitySection } from "./IdentitySection";

View File

@ -1 +0,0 @@
export { IdentitySection } from "./IdentitySection";

View File

@ -215,7 +215,7 @@ export const MemberListTab = () => {
onGoBack={() => handlePopUpClose("additionalPrivilege")}
privileges={members?.[privilegeDetails.index]?.additionalPrivileges || []}
name={privilegeDetails.name}
projectMembershipId={privilegeDetails.projectMembershipId}
actorId={privilegeDetails.projectMembershipId}
/>
</motion.div>
);