mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 12:38:36 +00:00
feat: role list section completed with delete
This commit is contained in:
+2
-2
@@ -5,7 +5,7 @@ import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
||||
import { useOrganization,useWorkspace } from "@app/context";
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useAddIdentityToWorkspace,
|
||||
useGetIdentityProjectMemberships,
|
||||
@@ -50,7 +50,7 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle
|
||||
const projectId = watch("projectId");
|
||||
const { data: projectMemberships } = useGetIdentityProjectMemberships(identityId);
|
||||
const { data: project } = useGetWorkspaceById(projectId);
|
||||
const { data: roles } = useGetProjectRoles(project?.slug ?? "");
|
||||
const { data: roles } = useGetProjectRoles(project?.id ?? "");
|
||||
|
||||
const filteredWorkspaces = useMemo(() => {
|
||||
const wsWorkspaceIds = new Map();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Button } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
import { useGetWorkspaceUserDetails } from "@app/hooks/api";
|
||||
import { faChevronLeft, faUser } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
import { useRouter } from "next/router";
|
||||
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
|
||||
|
||||
export const MemberDetailsPage = withProjectPermission(
|
||||
() => {
|
||||
const router = useRouter();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const membershipId = router.query.membershipId as string;
|
||||
|
||||
const { data: membershipDetails, isLoading: isMembershipDetailsLoading } =
|
||||
useGetWorkspaceUserDetails(workspaceId, membershipId);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
|
||||
<div className="mb-4">
|
||||
<Button
|
||||
variant="link"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
router.push(`/project/${workspaceId}/members`);
|
||||
}}
|
||||
className="mb-4"
|
||||
>
|
||||
Project Access Control
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between ">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">Project User Management</h3>
|
||||
<div>
|
||||
<Button colorSchema="danger" variant="outline_bg" size="xs">
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
{membershipDetails && (
|
||||
<p className="capitalize">
|
||||
{membershipDetails.user.firstName || membershipDetails.user.lastName
|
||||
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
|
||||
: "-"}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-2 text-sm text-gray-400">
|
||||
Joined on{" "}
|
||||
{membershipDetails?.createdAt &&
|
||||
format(new Date(membershipDetails?.createdAt || ""), "yyyy-MM-dd")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MemberRoleDetailsSection
|
||||
membershipDetails={membershipDetails}
|
||||
isMembershipDetailsLoading={isMembershipDetailsLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
subject: ProjectPermissionSub.Member
|
||||
}
|
||||
);
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
DeleteActionModal,
|
||||
IconButton,
|
||||
TableContainer,
|
||||
Td,
|
||||
Table,
|
||||
Tr,
|
||||
Th,
|
||||
THead,
|
||||
TableSkeleton,
|
||||
EmptyState,
|
||||
TBody,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useUser, useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useDeleteProjectRole,
|
||||
useGetWorkspaceUserDetails,
|
||||
useUpdateUserWorkspaceRole
|
||||
} from "@app/hooks/api";
|
||||
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
import { faFolder, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
type Props = {
|
||||
membershipDetails: TWorkspaceUser;
|
||||
isMembershipDetailsLoading?: boolean;
|
||||
};
|
||||
|
||||
export const MemberRoleDetailsSection = ({
|
||||
membershipDetails,
|
||||
isMembershipDetailsLoading
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
const { user } = useUser();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"deleteRole"
|
||||
] as const);
|
||||
const { mutateAsync: updateUserWorkspaceRole } = useUpdateUserWorkspaceRole();
|
||||
|
||||
const userId = user?.id;
|
||||
|
||||
const handleRoleDelete = async () => {
|
||||
const { id } = popUp?.deleteRole?.data as TProjectRole;
|
||||
try {
|
||||
const updatedRole = membershipDetails?.roles?.filter((el) => el.id !== id);
|
||||
await updateUserWorkspaceRole({
|
||||
workspaceId: currentWorkspace?.id || "",
|
||||
roles: updatedRole,
|
||||
membershipId: membershipDetails.id
|
||||
});
|
||||
createNotification({ type: "success", text: "Successfully removed the role" });
|
||||
handlePopUpClose("deleteRole");
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({ type: "error", text: "Failed to delete role" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">Project Roles</h3>
|
||||
{userId !== membershipDetails?.user?.id && membershipDetails?.status !== "invited" && (
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={() => {}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Role</Th>
|
||||
<Th>Type</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isMembershipDetailsLoading && (
|
||||
<TableSkeleton columns={3} innerKey="user-project-memberships" />
|
||||
)}
|
||||
{!isMembershipDetailsLoading &&
|
||||
membershipDetails?.roles?.map((roleDetails) => {
|
||||
return (
|
||||
<Tr className="group h-10" key={`user-project-membership-${roleDetails?.id}`}>
|
||||
<Td>
|
||||
{roleDetails.role === "custom"
|
||||
? roleDetails.customRoleName
|
||||
: roleDetails.role}
|
||||
</Td>
|
||||
<Td>{!roleDetails.isTemporary ? "permanent" : "temporary"}</Td>
|
||||
<Td>
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Tooltip content="Remove">
|
||||
<IconButton
|
||||
colorSchema="danger"
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("deleteRole", {
|
||||
id: roleDetails?.id,
|
||||
slug: roleDetails?.customRoleSlug || roleDetails?.role
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isMembershipDetailsLoading && !membershipDetails?.roles?.length && (
|
||||
<EmptyState title="This user has not been assigned to any projects" icon={faFolder} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteRole.isOpen}
|
||||
deleteKey="remove"
|
||||
title={`Do you want to remove role ${(popUp?.deleteRole?.data as TProjectRole)?.slug}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteRole", isOpen)}
|
||||
onDeleteApproved={() => handleRoleDelete()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { MemberRoleDetailsSection } from "./MemberRoleDetailsSection";
|
||||
@@ -0,0 +1 @@
|
||||
export { MemberDetailsPage } from "./MemberDetailPage";
|
||||
+1
-2
@@ -32,12 +32,11 @@ export const GroupModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const orgId = currentOrg?.id || "";
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
|
||||
const { data: groups } = useGetOrganizationGroups(orgId);
|
||||
const { data: groupMemberships } = useListWorkspaceGroups(currentWorkspace?.id || "");
|
||||
|
||||
const { data: roles } = useGetProjectRoles(projectSlug);
|
||||
const { data: roles } = useGetProjectRoles(currentWorkspace?.id || "");
|
||||
|
||||
const { mutateAsync: addGroupToWorkspaceMutateAsync } = useAddGroupToWorkspace();
|
||||
|
||||
|
||||
+3
-3
@@ -216,9 +216,9 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
|
||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(
|
||||
currentWorkspace?.id ?? ""
|
||||
);
|
||||
const userRolesGroupBySlug = groupBy(roles, ({ customRoleSlug, role }) => customRoleSlug || role);
|
||||
|
||||
const updateGroupWorkspaceRole = useUpdateGroupWorkspaceRole();
|
||||
|
||||
+1
-2
@@ -42,7 +42,6 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
|
||||
const organizationId = currentOrg?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
|
||||
const { data: identityMembershipOrgsData } = useGetIdentityMembershipOrgs({
|
||||
organizationId,
|
||||
@@ -59,7 +58,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
data: roles,
|
||||
isLoading: isRolesLoading,
|
||||
isFetched: isRolesFetched
|
||||
} = useGetProjectRoles(projectSlug);
|
||||
} = useGetProjectRoles(workspaceId);
|
||||
|
||||
const { mutateAsync: addIdentityToWorkspaceMutateAsync } = useAddIdentityToWorkspace();
|
||||
|
||||
|
||||
+1
-2
@@ -65,8 +65,7 @@ export const IdentityRbacSection = ({ identityProjectMember, onOpenUpgradeModal
|
||||
const { subscription } = useSubscription();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId);
|
||||
const { permission } = useProjectPermission();
|
||||
const isMemberEditDisabled = permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { data: members } = useGetWorkspaceUsers(workspaceId);
|
||||
const { data: orgUsers } = useGetOrgUsers(orgId);
|
||||
|
||||
const { data: roles } = useGetProjectRoles(currentWorkspace?.slug || "");
|
||||
const { data: roles } = useGetProjectRoles(currentWorkspace?.id || "");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
||||
+1
-2
@@ -65,8 +65,7 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props)
|
||||
const { subscription } = useSubscription();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
||||
const { data: projectRoles, isLoading: isRolesLoading } = useGetProjectRoles(workspaceId);
|
||||
const { permission } = useProjectPermission();
|
||||
const isMemberEditDisabled = permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
|
||||
+24
-7
@@ -7,6 +7,7 @@ import {
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useRouter } from "next/router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
@@ -65,6 +66,7 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { user } = useUser();
|
||||
const router = useRouter();
|
||||
|
||||
const userId = user?.id || "";
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
@@ -111,7 +113,18 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
||||
const email = u?.email || inviteEmail;
|
||||
|
||||
return (
|
||||
<Tr key={`membership-${membershipId}`} className="group w-full">
|
||||
<Tr
|
||||
key={`membership-${membershipId}`}
|
||||
className="group w-full cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
router.push(`/project/${workspaceId}/members/${membershipId}`);
|
||||
}
|
||||
}}
|
||||
onClick={() => router.push(`/project/${workspaceId}/members/${membershipId}`)}
|
||||
>
|
||||
<Td>{name}</Td>
|
||||
<Td>{email}</Td>
|
||||
<Td>
|
||||
@@ -202,9 +215,11 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
||||
size="sm"
|
||||
variant="plain"
|
||||
ariaLabel="update-role"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("updateRole", { ...projectMember, index })
|
||||
}
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
handlePopUpOpen("updateRole", { ...projectMember, index });
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
@@ -226,9 +241,11 @@ export const MembersTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Prop
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={userId === u?.id || !isAllowed}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("removeMember", { username: u.username })
|
||||
}
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
handlePopUpOpen("removeMember", { username: u.username });
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
|
||||
+2
-3
@@ -34,10 +34,9 @@ export const ProjectRoleList = () => {
|
||||
"deleteRole"
|
||||
] as const);
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
const projectId = currentWorkspace?.id || "";
|
||||
|
||||
const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(projectSlug);
|
||||
const { data: roles, isLoading: isRolesLoading } = useGetProjectRoles(projectId);
|
||||
|
||||
const { mutateAsync: deleteRole } = useDeleteProjectRole();
|
||||
|
||||
@@ -45,7 +44,7 @@ export const ProjectRoleList = () => {
|
||||
const { id } = popUp?.deleteRole?.data as TProjectRole;
|
||||
try {
|
||||
await deleteRole({
|
||||
projectSlug,
|
||||
projectId,
|
||||
id
|
||||
});
|
||||
createNotification({ type: "success", text: "Successfully removed the role" });
|
||||
|
||||
Reference in New Issue
Block a user