feat(ui): finished ui for identity additional privilege

This commit is contained in:
Akhil Mohan
2024-03-28 19:55:12 +05:30
parent 8529fac098
commit 5f84de039f
17 changed files with 569 additions and 279 deletions
@@ -41,6 +41,18 @@ export type IdentityMembership = {
temporaryAccessStartTime: string | null; temporaryAccessStartTime: string | null;
temporaryAccessEndTime: 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; createdAt: string;
updatedAt: string; updatedAt: string;
}; };
@@ -0,0 +1,7 @@
export {
useCreateIdentityProjectAdditionalPrivilege,
useDeleteIdentityProjectAdditionalPrivilege,
useUpdateIdentityProjectAdditionalPrivilege
} from "./mutation";
export { useGetIdentityProjectPrivilegeDetails } from "./queries";
export type { TIdentityProjectPrivilege } from "./types";
@@ -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));
}
});
};
@@ -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)
});
};
@@ -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;
};
+1
View File
@@ -6,6 +6,7 @@ export * from "./bots";
export * from "./dynamicSecret"; export * from "./dynamicSecret";
export * from "./dynamicSecretLease"; export * from "./dynamicSecretLease";
export * from "./identities"; export * from "./identities";
export * from "./identityProjectAdditionalPrivilege";
export * from "./incidentContacts"; export * from "./incidentContacts";
export * from "./integrationAuth"; export * from "./integrationAuth";
export * from "./integrations"; export * from "./integrations";
@@ -22,8 +22,11 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
import { Button, FormControl, Input } from "@app/components/v2"; import { Button, FormControl, Input } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub } from "@app/context";
import { import {
useCreateIdentityProjectAdditionalPrivilege,
useCreateProjectUserAdditionalPrivilege, useCreateProjectUserAdditionalPrivilege,
useGetIdentityProjectPrivilegeDetails,
useGetProjectUserPrivilegeDetails, useGetProjectUserPrivilegeDetails,
useUpdateIdentityProjectAdditionalPrivilege,
useUpdateProjectUserAdditionalPrivilege useUpdateProjectUserAdditionalPrivilege
} from "@app/hooks/api"; } from "@app/hooks/api";
@@ -32,7 +35,6 @@ import {
formRolePermission2API, formRolePermission2API,
formSchema, formSchema,
rolePermission2Form, rolePermission2Form,
// rolePermission2Form,
TFormSchema TFormSchema
} from "../ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils"; } from "../ProjectRoleListTab/components/ProjectRoleModifySection/ProjectRoleModifySection.utils";
import { SecretRollbackPermission } from "../ProjectRoleListTab/components/ProjectRoleModifySection/SecretRollbackPermission"; import { SecretRollbackPermission } from "../ProjectRoleListTab/components/ProjectRoleModifySection/SecretRollbackPermission";
@@ -124,14 +126,26 @@ type Props = {
actorId: string; actorId: string;
}; };
export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, workspaceId }: Props) => { export const AdditionalPrivilegeForm = ({
onGoBack,
privilegeId,
actorId,
workspaceId,
isIdentity
}: Props) => {
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const isNewRole = !privilegeId; const isNewRole = !privilegeId;
const { data: projectUserPrivilegeDetails } = useGetProjectUserPrivilegeDetails( const { data: projectUserPrivilegeDetails } = useGetProjectUserPrivilegeDetails(
privilegeId || "" privilegeId && !isIdentity ? privilegeId : ""
); );
const { data: identityProjectPrivilegeDetails } = useGetIdentityProjectPrivilegeDetails(
isIdentity && privilegeId ? privilegeId : ""
);
const privileges = isIdentity ? identityProjectPrivilegeDetails : projectUserPrivilegeDetails;
const { const {
handleSubmit, handleSubmit,
register, register,
@@ -141,24 +155,36 @@ export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, worksp
control control
} = useForm<TFormSchema>({ } = useForm<TFormSchema>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
values: projectUserPrivilegeDetails && { values: privileges && {
...projectUserPrivilegeDetails, ...privileges,
description: projectUserPrivilegeDetails.description || "", description: privileges.description || "",
permissions: rolePermission2Form(projectUserPrivilegeDetails.permissions) permissions: rolePermission2Form(privileges.permissions)
} }
}); });
const createProjectUserAdditionalPrivilege = useCreateProjectUserAdditionalPrivilege(); const createProjectUserAdditionalPrivilege = useCreateProjectUserAdditionalPrivilege();
const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege(); const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege();
const createIdentityProjectAdditionalPrivilege = useCreateIdentityProjectAdditionalPrivilege();
const updateIdentityProjectAdditionalPrivilege = useUpdateIdentityProjectAdditionalPrivilege();
const handleRoleUpdate = async (el: TFormSchema) => { const handleRoleUpdate = async (el: TFormSchema) => {
try { try {
await updateProjectUserAdditionalPrivilege.mutateAsync({ if (isIdentity) {
...el, await updateIdentityProjectAdditionalPrivilege.mutateAsync({
permissions: formRolePermission2API(el.permissions), ...el,
privilegeId: privilegeId as string, permissions: formRolePermission2API(el.permissions),
workspaceId 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" }); createNotification({ type: "success", text: "Successfully update privilege" });
onGoBack(); onGoBack();
} catch (err) { } catch (err) {
@@ -174,12 +200,21 @@ export const AdditionalPrivilegeForm = ({ onGoBack, privilegeId, actorId, worksp
} }
try { try {
await createProjectUserAdditionalPrivilege.mutateAsync({ if (isIdentity) {
...el, await createIdentityProjectAdditionalPrivilege.mutateAsync({
permissions: formRolePermission2API(el.permissions), ...el,
projectMembershipId: actorId, permissions: formRolePermission2API(el.permissions),
workspaceId identityId: actorId,
}); projectId: workspaceId
});
} else {
await createProjectUserAdditionalPrivilege.mutateAsync({
...el,
permissions: formRolePermission2API(el.permissions),
projectMembershipId: actorId,
workspaceId
});
}
createNotification({ type: "success", text: "Created new privilege" }); createNotification({ type: "success", text: "Created new privilege" });
onGoBack(); onGoBack();
} catch (err) { } catch (err) {
@@ -19,7 +19,10 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks"; 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 { TWorkspaceUser } from "@app/hooks/api/types";
import { AdditionalPrivilegeForm } from "./AdditionalPrivilegeForm"; import { AdditionalPrivilegeForm } from "./AdditionalPrivilegeForm";
@@ -28,40 +31,51 @@ import { AdditionalPrivilegeTemporaryAccess } from "./AdditionalPrivilegeTempora
type Props = { type Props = {
onGoBack: VoidFunction; onGoBack: VoidFunction;
name: string; name: string;
projectMembershipId: string; isIdentity?: boolean;
// isIdentity id - identity id else projectMembershipId
actorId: string;
privileges: TWorkspaceUser["additionalPrivileges"]; privileges: TWorkspaceUser["additionalPrivileges"];
}; };
export const AdditionalPrivilegeSection = ({ export const AdditionalPrivilegeSection = ({
onGoBack, onGoBack,
privileges = [], privileges = [],
projectMembershipId, actorId,
name name,
isIdentity
}: Props) => { }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"modifyPrivilege", "modifyPrivilege",
"deletePrivilege" "deletePrivilege"
] as const); ] as const);
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const deleteProjectUserAdditionalPrivilege = useDeleteProjectUserAdditionalPrivilege();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
const deleteProjectUserAdditionalPrivilege = useDeleteProjectUserAdditionalPrivilege();
const deleteProjectIdentityAdditionalPrivilege = useDeleteIdentityProjectAdditionalPrivilege();
const onPrivilegeDelete = async (privilegeId: string) => { const onPrivilegeDelete = async (privilegeId: string) => {
try { try {
await deleteProjectUserAdditionalPrivilege.mutateAsync({ if (isIdentity) {
privilegeId, await deleteProjectIdentityAdditionalPrivilege.mutateAsync({
workspaceId privilegeId,
}); projectId: workspaceId
});
} else {
await deleteProjectUserAdditionalPrivilege.mutateAsync({
privilegeId,
workspaceId
});
}
handlePopUpClose("deletePrivilege"); handlePopUpClose("deletePrivilege");
createNotification({ createNotification({
type: "success", type: "success",
text: "Successfully removed user privilege" text: "Successfully removed privilege"
}); });
} catch (err) { } catch (err) {
createNotification({ createNotification({
type: "error", type: "error",
text: "Failed to delete user privilege" text: "Failed to delete privilege"
}); });
} }
}; };
@@ -83,7 +97,8 @@ export const AdditionalPrivilegeSection = ({
onGoBack={() => handlePopUpClose("modifyPrivilege")} onGoBack={() => handlePopUpClose("modifyPrivilege")}
privilegeId={privilegeDetails?.id} privilegeId={privilegeDetails?.id}
workspaceId={workspaceId} workspaceId={workspaceId}
actorId={projectMembershipId} isIdentity={isIdentity}
actorId={actorId}
/> />
</motion.div> </motion.div>
); );
@@ -119,7 +134,11 @@ export const AdditionalPrivilegeSection = ({
</div> </div>
<div className="mt-6 flex flex-col space-y-4"> <div className="mt-6 flex flex-col space-y-4">
{privileges.length === 0 && ( {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 }) => ( {privileges.map(({ id, name: privilegeName, description, slug, ...dto }) => (
<div <div
@@ -137,6 +156,7 @@ export const AdditionalPrivilegeSection = ({
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<AdditionalPrivilegeTemporaryAccess <AdditionalPrivilegeTemporaryAccess
isIdentity={isIdentity}
privilegeId={id} privilegeId={id}
workspaceId={workspaceId} workspaceId={workspaceId}
temporaryConfig={!dto.isTemporary ? { isTemporary: false } : { ...dto }} temporaryConfig={!dto.isTemporary ? { isTemporary: false } : { ...dto }}
@@ -18,7 +18,11 @@ import {
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { usePopUp } from "@app/hooks"; 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"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
const temporaryRoleFormSchema = z.object({ const temporaryRoleFormSchema = z.object({
@@ -30,6 +34,7 @@ type TTemporaryRoleFormSchema = z.infer<typeof temporaryRoleFormSchema>;
type TTemporaryRoleFormProps = { type TTemporaryRoleFormProps = {
privilegeId: string; privilegeId: string;
workspaceId: string; workspaceId: string;
isIdentity?: boolean;
temporaryConfig?: { temporaryConfig?: {
isTemporary?: boolean; isTemporary?: boolean;
temporaryAccessEndTime?: string | null; temporaryAccessEndTime?: string | null;
@@ -41,7 +46,8 @@ type TTemporaryRoleFormProps = {
export const AdditionalPrivilegeTemporaryAccess = ({ export const AdditionalPrivilegeTemporaryAccess = ({
temporaryConfig: defaultValues = {}, temporaryConfig: defaultValues = {},
workspaceId, workspaceId,
privilegeId privilegeId,
isIdentity
}: TTemporaryRoleFormProps) => { }: TTemporaryRoleFormProps) => {
const { popUp, handlePopUpToggle } = usePopUp(["setTempRole"] as const); const { popUp, handlePopUpToggle } = usePopUp(["setTempRole"] as const);
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
@@ -56,17 +62,29 @@ export const AdditionalPrivilegeTemporaryAccess = ({
isTemporaryFieldValue && new Date() > new Date(defaultValues.temporaryAccessEndTime || ""); isTemporaryFieldValue && new Date() > new Date(defaultValues.temporaryAccessEndTime || "");
const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege(); const updateProjectUserAdditionalPrivilege = useUpdateProjectUserAdditionalPrivilege();
const updateProjectIdentityAdditionalPrivilege = useUpdateIdentityProjectAdditionalPrivilege();
const handleGrantTemporaryAccess = async (el: TTemporaryRoleFormSchema) => { const handleGrantTemporaryAccess = async (el: TTemporaryRoleFormSchema) => {
try { try {
await updateProjectUserAdditionalPrivilege.mutateAsync({ if (isIdentity) {
privilegeId: privilegeId as string, await updateProjectIdentityAdditionalPrivilege.mutateAsync({
workspaceId, privilegeId: privilegeId as string,
isTemporary: true, projectId: workspaceId,
temporaryRange: el.temporaryRange, isTemporary: true,
temporaryAccessStartTime: new Date().toISOString(), temporaryRange: el.temporaryRange,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative 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" }); createNotification({ type: "success", text: "Successfully updated access" });
handlePopUpToggle("setTempRole"); handlePopUpToggle("setTempRole");
} catch (err) { } catch (err) {
@@ -77,11 +95,19 @@ export const AdditionalPrivilegeTemporaryAccess = ({
const handleRevokeTemporaryAccess = async () => { const handleRevokeTemporaryAccess = async () => {
try { try {
await updateProjectUserAdditionalPrivilege.mutateAsync({ if (isIdentity) {
privilegeId: privilegeId as string, await updateProjectIdentityAdditionalPrivilege.mutateAsync({
workspaceId, privilegeId: privilegeId as string,
isTemporary: false projectId: workspaceId,
}); isTemporary: false
});
} else {
await updateProjectUserAdditionalPrivilege.mutateAsync({
privilegeId: privilegeId as string,
workspaceId,
isTemporary: false
});
}
createNotification({ type: "success", text: "Successfully updated access" }); createNotification({ type: "success", text: "Successfully updated access" });
handlePopUpToggle("setTempRole"); handlePopUpToggle("setTempRole");
} catch (err) { } catch (err) {
@@ -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 { 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 = () => { import { AdditionalPrivilegeSection } from "../AdditionalPrivilegeSection";
return ( import { IdentityModal } from "./components/IdentityModal";
<motion.div import { IdentityRoles } from "./components/IdentityRoles";
key="panel-identity"
transition={{ duration: 0.15 }} export const IdentityTab = withProjectPermission(
initial={{ opacity: 0, translateX: 30 }} () => {
animate={{ opacity: 1, translateX: 0 }} const { createNotification } = useNotificationContext();
exit={{ opacity: 0, translateX: 30 }} const { currentWorkspace } = useWorkspace();
>
<IdentitySection /> const workspaceId = currentWorkspace?.id ?? "";
</motion.div>
); 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 }
);
@@ -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 }
);
@@ -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>
);
};
@@ -1 +0,0 @@
export { IdentitySection } from "./IdentitySection";
@@ -1 +0,0 @@
export { IdentitySection } from "./IdentitySection";
@@ -215,7 +215,7 @@ export const MemberListTab = () => {
onGoBack={() => handlePopUpClose("additionalPrivilege")} onGoBack={() => handlePopUpClose("additionalPrivilege")}
privileges={members?.[privilegeDetails.index]?.additionalPrivileges || []} privileges={members?.[privilegeDetails.index]?.additionalPrivileges || []}
name={privilegeDetails.name} name={privilegeDetails.name}
projectMembershipId={privilegeDetails.projectMembershipId} actorId={privilegeDetails.projectMembershipId}
/> />
</motion.div> </motion.div>
); );