fix: sub-org labels

This commit is contained in:
Piyush Gupta
2025-12-06 19:35:29 +05:30
parent 5071fa2f69
commit 72584d8353
9 changed files with 111 additions and 98 deletions
@@ -85,7 +85,7 @@ export const AccessManagementPage = () => {
<PageHeader <PageHeader
scope={isSubOrganization ? "namespace" : "org"} scope={isSubOrganization ? "namespace" : "org"}
title={`${isSubOrganization ? "Sub-Organization" : "Organization"} Access Control`} title={`${isSubOrganization ? "Sub-Organization" : "Organization"} Access Control`}
description="Manage fine-grained access for users, groups, roles, and machine identities within your organization resources." description={`Manage fine-grained access for users, groups, roles, and machine identities within your ${isSubOrganization ? "sub-" : ""}organization resources.`}
> >
{isSubOrganization && ( {isSubOrganization && (
<Link <Link
@@ -6,7 +6,12 @@ import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2"; import { Button, DeleteActionModal } from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3"; import { DocumentationLinkBadge } from "@app/components/v3";
import { OrgPermissionGroupActions, OrgPermissionSubjects, useSubscription } from "@app/context"; import {
OrgPermissionGroupActions,
OrgPermissionSubjects,
useOrganization,
useSubscription
} from "@app/context";
import { useDeleteGroup } from "@app/hooks/api"; import { useDeleteGroup } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
@@ -15,6 +20,7 @@ import { OrgGroupsTable } from "./OrgGroupsTable";
export const OrgGroupsSection = () => { export const OrgGroupsSection = () => {
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { isSubOrganization } = useOrganization();
const { mutateAsync: deleteMutateAsync } = useDeleteGroup(); const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
@@ -51,7 +57,9 @@ export const OrgGroupsSection = () => {
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2"> <div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-x-2"> <div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Organization Groups</p> <p className="text-xl font-medium text-mineshaft-100">
{isSubOrganization ? "Sub-" : ""}Organization Groups
</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/groups" /> <DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/groups" />
</div> </div>
<OrgPermissionCan I={OrgPermissionGroupActions.Create} a={OrgPermissionSubjects.Groups}> <OrgPermissionCan I={OrgPermissionGroupActions.Create} a={OrgPermissionSubjects.Groups}>
@@ -63,7 +71,7 @@ export const OrgGroupsSection = () => {
onClick={() => handleAddGroupModal()} onClick={() => handleAddGroupModal()}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
Create Organization Group Create {isSubOrganization ? "Sub-" : ""}Organization Group
</Button> </Button>
)} )}
</OrgPermissionCan> </OrgPermissionCan>
@@ -71,7 +71,7 @@ enum GroupsOrderBy {
export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { currentOrg } = useOrganization(); const { currentOrg, isSubOrganization } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const { isPending, data: groups = [] } = useGetOrganizationGroups(orgId); const { isPending, data: groups = [] } = useGetOrganizationGroups(orgId);
const { mutateAsync: updateMutateAsync } = useUpdateGroup(); const { mutateAsync: updateMutateAsync } = useUpdateGroup();
@@ -159,7 +159,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search organization groups..." placeholder={`Search ${isSubOrganization ? "sub-" : ""}organization groups...`}
/> />
<TableContainer className="mt-4"> <TableContainer className="mt-4">
<Table> <Table>
@@ -205,7 +205,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
</Th> </Th>
<Th> <Th>
<div className="flex items-center"> <div className="flex items-center">
Organization Role {isSubOrganization ? "Sub-" : ""}Organization Role
<IconButton <IconButton
variant="plain" variant="plain"
className={`ml-2 ${orderBy === GroupsOrderBy.Role ? "" : "opacity-30"}`} className={`ml-2 ${orderBy === GroupsOrderBy.Role ? "" : "opacity-30"}`}
@@ -389,8 +389,8 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
<EmptyState <EmptyState
title={ title={
groups.length groups.length
? "No organization groups match search..." ? `No ${isSubOrganization ? "sub-" : ""}organization groups match search...`
: "No organization groups found" : `No ${isSubOrganization ? "sub-" : ""}organization groups found`
} }
icon={groups.length ? faSearch : faUsers} icon={groups.length ? faSearch : faUsers}
/> />
@@ -1,13 +1,13 @@
import { useState } from "react"; import { useState } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion"; import { InfoIcon } from "lucide-react";
import { LinkIcon, PlusIcon } from "lucide-react"; import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal, Modal, ModalContent } from "@app/components/v2"; import { Button, DeleteActionModal, Modal, ModalContent, Tooltip } from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3"; import { DocumentationLinkBadge } from "@app/components/v3";
import { import {
OrgPermissionIdentityActions, OrgPermissionIdentityActions,
@@ -30,9 +30,8 @@ import { OrgIdentityLinkForm } from "./OrgIdentityLinkForm";
import { OrgIdentityModal } from "./OrgIdentityModal"; import { OrgIdentityModal } from "./OrgIdentityModal";
enum IdentityWizardSteps { enum IdentityWizardSteps {
SelectAction = "select-action", CreateIdentity = "create-identity",
LinkIdentity = "link-identity", LinkIdentity = "link-identity"
OrganizationIdentity = "project-identity"
} }
export const IdentitySection = withPermission( export const IdentitySection = withPermission(
@@ -41,7 +40,7 @@ export const IdentitySection = withPermission(
const { currentOrg, isSubOrganization } = useOrganization(); const { currentOrg, isSubOrganization } = useOrganization();
const orgId = currentOrg?.id || ""; const orgId = currentOrg?.id || "";
const [wizardStep, setWizardStep] = useState(IdentityWizardSteps.SelectAction); const [wizardStep, setWizardStep] = useState(IdentityWizardSteps.CreateIdentity);
const { mutateAsync: deleteMutateAsync } = useDeleteOrgIdentity(); const { mutateAsync: deleteMutateAsync } = useDeleteOrgIdentity();
const { mutateAsync: deleteTemplateMutateAsync } = useDeleteIdentityAuthTemplate(); const { mutateAsync: deleteTemplateMutateAsync } = useDeleteIdentityAuthTemplate();
@@ -100,7 +99,7 @@ export const IdentitySection = withPermission(
<div className="mb-4 flex flex-wrap items-center justify-between gap-2"> <div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-1 items-center gap-x-2"> <div className="flex flex-1 items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100"> <p className="text-xl font-medium text-mineshaft-100">
Organization Machine Identities {isSubOrganization ? "Sub-" : ""}Organization Machine Identities
</p> </p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" /> <DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
</div> </div>
@@ -124,7 +123,7 @@ export const IdentitySection = withPermission(
} }
if (!isSubOrganization) { if (!isSubOrganization) {
setWizardStep(IdentityWizardSteps.OrganizationIdentity); setWizardStep(IdentityWizardSteps.CreateIdentity);
} }
handlePopUpOpen("identity"); handlePopUpOpen("identity");
@@ -197,7 +196,7 @@ export const IdentitySection = withPermission(
onOpenChange={(open) => { onOpenChange={(open) => {
handlePopUpToggle("identity", open); handlePopUpToggle("identity", open);
if (!open) { if (!open) {
setWizardStep(IdentityWizardSteps.SelectAction); setWizardStep(IdentityWizardSteps.CreateIdentity);
} }
}} }}
> >
@@ -214,80 +213,84 @@ export const IdentitySection = withPermission(
: undefined : undefined
} }
> >
<AnimatePresence mode="wait"> {isSubOrganization && (
{wizardStep === IdentityWizardSteps.SelectAction && ( <div className="mb-4 flex items-center justify-center gap-x-2">
<motion.div <div className="flex w-3/4 gap-x-0.5 rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
key="select-type-step" <Button
transition={{ duration: 0.1 }} variant="outline_bg"
initial={{ opacity: 0, translateX: 30 }} onClick={() => {
animate={{ opacity: 1, translateX: 0 }} setWizardStep(IdentityWizardSteps.CreateIdentity);
exit={{ opacity: 0, translateX: -30 }}
>
<div
className="cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(IdentityWizardSteps.OrganizationIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(IdentityWizardSteps.OrganizationIdentity);
}
}} }}
size="xs"
className={twMerge(
"min-w-[2.4rem] flex-1 rounded border-none hover:bg-mineshaft-600",
wizardStep === IdentityWizardSteps.CreateIdentity
? "bg-mineshaft-500"
: "bg-transparent"
)}
> >
<div className="flex items-center gap-2"> Create New
<PlusIcon size="1rem" /> </Button>
<div>Create Machine Identity</div> <Button
</div> variant="outline_bg"
<div className="mt-2 text-xs text-mineshaft-300"> onClick={() => {
Create a new machine identity specifically for this sub-organization. This setWizardStep(IdentityWizardSteps.LinkIdentity);
machine identity will be managed at the sub-organization level.
</div>
</div>
<div
className="mt-4 cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
role="button"
tabIndex={0}
onClick={() => setWizardStep(IdentityWizardSteps.LinkIdentity)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setWizardStep(IdentityWizardSteps.LinkIdentity);
}
}} }}
size="xs"
className={twMerge(
"min-w-[2.4rem] flex-1 rounded border-none hover:bg-mineshaft-600",
wizardStep === IdentityWizardSteps.LinkIdentity
? "bg-mineshaft-500"
: "bg-transparent"
)}
> >
<div className="flex items-center gap-2"> Assign Existing
<LinkIcon size="1rem" /> </Button>
<div>Assign Existing Machine Identity</div> </div>
</div> <Tooltip
<div className="mt-2 text-xs text-mineshaft-300"> className="max-w-sm"
Assign an existing machine identity from your parent organization. The machine position="right"
identity will continue to be managed at its original scope. align="start"
</div> content={
</div> <>
</motion.div> <p className="mb-2 text-mineshaft-300">
)} You can add machine identities to your sub-organization in one of two ways:
{wizardStep === IdentityWizardSteps.OrganizationIdentity && ( </p>
<motion.div <ul className="ml-3.5 flex list-disc flex-col gap-y-4">
key="identity-step" <li className="text-mineshaft-200">
transition={{ duration: 0.1 }} <strong className="font-medium text-mineshaft-100">Create New</strong> -
initial={{ opacity: 0, translateX: 30 }} Create a new machine identity specifically for this sub-organization. This
animate={{ opacity: 1, translateX: 0 }} machine identity will be managed at the sub-organization level.
exit={{ opacity: 0, translateX: -30 }} <p className="mt-2">
This method is recommended for autonomous teams that need to manage
machine identity authentication.
</p>
</li>
<li>
<strong className="font-medium text-mineshaft-100">
Assign Existing
</strong>{" "}
Assign an existing machine identity from your parent organization. The
machine identity will continue to be managed at its original scope.
<p className="mt-2">
This method is recommended for organizations that need to maintain
centralized control.
</p>
</li>
</ul>
</>
}
> >
<OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <InfoIcon size={16} className="text-mineshaft-400" />
</motion.div> </Tooltip>
)} </div>
{wizardStep === IdentityWizardSteps.LinkIdentity && ( )}
<motion.div {wizardStep === IdentityWizardSteps.CreateIdentity && (
key="link-step" <OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
transition={{ duration: 0.1 }} )}
initial={{ opacity: 0, translateX: 30 }} {wizardStep === IdentityWizardSteps.LinkIdentity && (
animate={{ opacity: 1, translateX: 0 }} <OrgIdentityLinkForm onClose={() => handlePopUpClose("identity")} />
exit={{ opacity: 0, translateX: -30 }} )}
>
<OrgIdentityLinkForm onClose={() => handlePopUpClose("identity")} />
</motion.div>
)}
</AnimatePresence>
</ModalContent> </ModalContent>
</Modal> </Modal>
<DeleteActionModal <DeleteActionModal
@@ -205,7 +205,9 @@ export const OrgMembersSection = () => {
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2"> <div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-x-2"> <div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Organization Users</p> <p className="text-xl font-medium text-mineshaft-100">
{isSubOrganization ? "Sub-" : ""}Organization Users
</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/user-identities" /> <DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/user-identities" />
</div> </div>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}> <OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
@@ -336,7 +336,7 @@ export const OrgMembersTable = ({
</DropdownSubMenuTrigger> </DropdownSubMenuTrigger>
<DropdownSubMenuContent className="max-h-80 thin-scrollbar overflow-y-auto rounded-l-none"> <DropdownSubMenuContent className="max-h-80 thin-scrollbar overflow-y-auto rounded-l-none">
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900"> <DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
Filter Organization Users by Role Filter {isSubOrganization ? "Sub-" : ""}Organization Users by Role
</DropdownMenuLabel> </DropdownMenuLabel>
{roles?.map(({ id, slug, name }) => ( {roles?.map(({ id, slug, name }) => (
<DropdownMenuItem <DropdownMenuItem
@@ -365,7 +365,7 @@ export const OrgMembersTable = ({
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search organization users..." placeholder={`Search ${isSubOrganization ? "sub-" : ""}organization users...`}
/> />
</div> </div>
<TableContainer className="mt-4"> <TableContainer className="mt-4">
@@ -434,7 +434,7 @@ export const OrgMembersTable = ({
</Th> </Th>
<Th className="w-1/3"> <Th className="w-1/3">
<div className="flex items-center"> <div className="flex items-center">
Organization Role {isSubOrganization ? "Sub-" : ""}Organization Role
<IconButton <IconButton
variant="plain" variant="plain"
className={`ml-2 ${orderBy === OrgMembersOrderBy.Role ? "" : "opacity-30"}`} className={`ml-2 ${orderBy === OrgMembersOrderBy.Role ? "" : "opacity-30"}`}
@@ -727,8 +727,8 @@ export const OrgMembersTable = ({
<EmptyState <EmptyState
title={ title={
members.length members.length
? "No organization users match search..." ? `No ${isSubOrganization ? "sub-" : ""}organization users match search...`
: "No organization users found" : `No ${isSubOrganization ? "sub-" : ""}organization users found`
} }
icon={members.length ? faSearch : faUsers} icon={members.length ? faSearch : faUsers}
/> />
@@ -207,7 +207,7 @@ export const OrgRoleTable = () => {
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
Add Organization Role Add {isSubOrganization ? "Sub-" : ""}Organization Role
</Button> </Button>
)} )}
</OrgPermissionCan> </OrgPermissionCan>
@@ -216,7 +216,7 @@ export const OrgRoleTable = () => {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search organization roles..." placeholder={`Search ${isSubOrganization ? "sub-" : ""}organization roles...`}
className="flex-1" className="flex-1"
containerClassName="mb-4" containerClassName="mb-4"
/> />
@@ -88,7 +88,7 @@ const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400" className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
> >
<FontAwesomeIcon icon={faChevronLeft} /> <FontAwesomeIcon icon={faChevronLeft} />
Organization Groups {isSubOrganization ? "Sub-" : ""}Organization Groups
</Link> </Link>
<PageHeader <PageHeader
scope={isSubOrganization ? "namespace" : "org"} scope={isSubOrganization ? "namespace" : "org"}
@@ -112,7 +112,7 @@ const Page = withPermission(
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400" className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
> >
<FontAwesomeIcon icon={faChevronLeft} /> <FontAwesomeIcon icon={faChevronLeft} />
Organization Users {isSubOrganization ? "Sub-" : ""}Organization Users
</Link> </Link>
<PageHeader <PageHeader
scope={isSubOrganization ? "namespace" : "org"} scope={isSubOrganization ? "namespace" : "org"}