import { useMemo } from "react"; import { faArrowDown, faArrowUp, faCopy, faEdit, faEllipsisV, faEye, faIdBadge, faMagnifyingGlass, faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { ServerIcon, WrenchIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, EmptyState, IconButton, Input, Pagination, Table, TableContainer, TableSkeleton, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; import { Badge, DocumentationLinkBadge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization, useSubscription } from "@app/context"; import { isCustomOrgRole } from "@app/helpers/roles"; import { getUserTablePreference, PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useDeleteOrgRole, useGetOrgRoles, useUpdateOrg } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { TOrgRole } from "@app/hooks/api/roles/types"; import { DuplicateOrgRoleModal } from "@app/pages/organization/RoleByIDPage/components/DuplicateOrgRoleModal"; import { RoleModal } from "@app/pages/organization/RoleByIDPage/components/RoleModal"; enum RolesOrderBy { Name = "name", Slug = "slug", Type = "type" } export const OrgRoleTable = () => { const navigate = useNavigate(); const { currentOrg, isSubOrganization } = useOrganization(); const orgId = currentOrg?.id || ""; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "role", "deleteRole", "duplicateRole", "upgradePlan" ] as const); const { data: roles, isPending: isRolesLoading } = useGetOrgRoles(orgId); const { mutateAsync: deleteRole } = useDeleteOrgRole(); const { mutateAsync: updateOrg } = useUpdateOrg(); const { subscription } = useSubscription(); const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TOrgRole; await deleteRole({ orgId, id }); createNotification({ type: "success", text: "Successfully removed the role" }); handlePopUpClose("deleteRole"); }; const handleSetRoleAsDefault = async (defaultMembershipRoleSlug: string) => { const isCustomRole = isCustomOrgRole(defaultMembershipRoleSlug); if (isCustomRole && subscription && !subscription?.rbac) { handlePopUpOpen("upgradePlan", { text: "Your current plan does not include access to set a custom default organization role. To unlock this feature, please upgrade to Infisical Pro plan." }); return; } await updateOrg({ orgId, defaultMembershipRoleSlug }); createNotification({ type: "success", text: "Successfully updated default membership role" }); handlePopUpClose("deleteRole"); }; const { orderDirection, toggleOrderDirection, orderBy, setOrderDirection, setOrderBy, search, setSearch, page, perPage, setPerPage, setPage, offset } = usePagination(RolesOrderBy.Type, { initPerPage: getUserTablePreference("orgRolesTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); setUserTablePreference("orgRolesTable", PreferenceKey.PerPage, newPerPage); }; const filteredRoles = useMemo( () => roles ?.filter((role) => { const { slug, name } = role; const searchValue = search.trim().toLowerCase(); return ( name.toLowerCase().includes(searchValue) || slug.toLowerCase().includes(searchValue) ); }) .sort((a, b) => { const [roleOne, roleTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; switch (orderBy) { case RolesOrderBy.Slug: return roleOne.slug.toLowerCase().localeCompare(roleTwo.slug.toLowerCase()); case RolesOrderBy.Type: { const roleOneValue = isCustomOrgRole(roleOne.slug) ? -1 : 1; const roleTwoValue = isCustomOrgRole(roleTwo.slug) ? -1 : 1; return roleTwoValue - roleOneValue; } case RolesOrderBy.Name: default: return roleOne.name.toLowerCase().localeCompare(roleTwo.name.toLowerCase()); } }) ?? [], [roles, orderDirection, search, orderBy] ); useResetPageHelper({ totalCount: filteredRoles.length, offset, setPage }); const handleSort = (column: RolesOrderBy) => { if (column === orderBy) { toggleOrderDirection(); return; } setOrderBy(column); setOrderDirection(OrderByDirection.ASC); }; const getClassName = (col: RolesOrderBy) => twMerge("ml-2", orderBy === col ? "" : "opacity-30"); const getColSortIcon = (col: RolesOrderBy) => orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown; return (

{isSubOrganization ? "Sub-" : ""}Organization Roles

{(isAllowed) => ( )}
setSearch(e.target.value)} leftIcon={} placeholder="Search roles..." className="flex-1" containerClassName="mb-4" /> {isRolesLoading && } {filteredRoles?.slice(offset, perPage * page).map((role) => { const { id, name, slug } = role; const isNonMutatable = ["owner", "admin", "member", "no-access"].includes(slug); const isDefaultOrgRole = isCustomOrgRole(slug) ? id === currentOrg?.defaultMembershipRole : slug === currentOrg?.defaultMembershipRole; return ( navigate({ to: "/organization/roles/$roleId", params: { roleId: id } }) } > ); })}
Name handleSort(RolesOrderBy.Name)} >
Slug handleSort(RolesOrderBy.Slug)} >
Type handleSort(RolesOrderBy.Type)} >

{name}

{isDefaultOrgRole && ( Default )}
{slug} {isCustomOrgRole(slug) ? ( <> Custom ) : ( <> Platform )} {(isAllowed) => ( { e.stopPropagation(); navigate({ to: "/organization/roles/$roleId", params: { roleId: id } }); }} isDisabled={!isAllowed} icon={} > {`${isNonMutatable ? "View" : "Edit"} Role`} )} {(isAllowed) => ( { e.stopPropagation(); handlePopUpOpen("duplicateRole", role); }} isDisabled={!isAllowed} icon={} > Duplicate Role )} {!isDefaultOrgRole && ( {(isAllowed) => ( { e.stopPropagation(); handleSetRoleAsDefault(slug); }} icon={} > Set as Default Role )} )} {!isNonMutatable && (
{(isAllowed) => ( { e.stopPropagation(); handlePopUpOpen("deleteRole", role); }} icon={} isDisabled={!isAllowed || isDefaultOrgRole} > Delete Role )}
)}
{Boolean(filteredRoles?.length) && ( )} {!filteredRoles?.length && !isRolesLoading && ( )}
handlePopUpToggle("deleteRole", isOpen)} deleteKey={(popUp?.deleteRole?.data as TOrgRole)?.slug || ""} onClose={() => handlePopUpClose("deleteRole")} onDeleteApproved={handleRoleDelete} /> handlePopUpToggle("upgradePlan", isOpen)} text={popUp.upgradePlan?.data?.text} /> handlePopUpToggle("duplicateRole", isOpen)} roleId={(popUp?.duplicateRole?.data as TOrgRole)?.id} />
); };