mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-10 07:58:15 -05:00
improvements: address feedback
This commit is contained in:
79
frontend/src/components/v3/generic/Accordion/Accordion.tsx
Normal file
79
frontend/src/components/v3/generic/Accordion/Accordion.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as React from "react";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "../../utils";
|
||||
|
||||
function UnstableAccordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
||||
return (
|
||||
<AccordionPrimitive.Root
|
||||
data-slot="accordion"
|
||||
className="border border-border bg-container"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableAccordionItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
||||
return (
|
||||
<AccordionPrimitive.Item
|
||||
data-slot="accordion-item"
|
||||
className={cn("border-b border-border last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableAccordionTrigger({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
||||
return (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
data-slot="accordion-trigger"
|
||||
className={cn(
|
||||
"flex min-h-12 flex-1 items-center gap-4 border-border bg-container px-4 text-left text-sm font-medium",
|
||||
"transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
||||
"disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
||||
"cursor-pointer hover:bg-foreground/5",
|
||||
"data-[state=open]:bg-foreground/5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0 text-label transition-transform duration-200" />
|
||||
{children}
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableAccordionContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
||||
return (
|
||||
<AccordionPrimitive.Content
|
||||
data-slot="accordion-content"
|
||||
className="overflow-hidden text-sm transition data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("p-6", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
UnstableAccordion,
|
||||
UnstableAccordionContent,
|
||||
UnstableAccordionItem,
|
||||
UnstableAccordionTrigger
|
||||
};
|
||||
1
frontend/src/components/v3/generic/Accordion/index.ts
Normal file
1
frontend/src/components/v3/generic/Accordion/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Accordion";
|
||||
@@ -25,8 +25,8 @@ const badgeVariants = cva(
|
||||
},
|
||||
variant: {
|
||||
ghost: "text-foreground border-none",
|
||||
default: "bg-foreground text-background [a&,button&]:hover:bg-primary/35",
|
||||
outline: "text-foreground border-foreground border",
|
||||
default: "bg-label text-background border-label [a&,button&]:hover:bg-primary/35",
|
||||
outline: "text-label border-label border",
|
||||
neutral: "bg-neutral/15 border-neutral/10 text-neutral [a&,button&]:hover:bg-neutral/35",
|
||||
success: "bg-success/15 border-success/10 text-success [a&,button&]:hover:bg-success/35",
|
||||
info: "bg-info/15 border-info/10 border text-info [a&,button&]:hover:bg-info/35",
|
||||
|
||||
@@ -8,7 +8,10 @@ function UnstableCard({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn("flex h-fit flex-col gap-6 rounded-[6px] text-foreground shadow-sm", className)}
|
||||
className={cn(
|
||||
"flex h-fit flex-col gap-6 rounded-md border border-border bg-card p-5 text-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -19,7 +22,8 @@ function UnstableCardHeader({ className, ...props }: React.ComponentProps<"div">
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-center gap-1 border-mineshaft-400/60 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-center gap-1",
|
||||
"border-border has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -31,10 +35,7 @@ function UnstableCardTitle({ className, ...props }: React.ComponentProps<"div">)
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 leading-none font-semibold [&>svg]:inline-block [&>svg]:size-[18px]",
|
||||
className
|
||||
)}
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -42,14 +43,7 @@ function UnstableCardTitle({ className, ...props }: React.ComponentProps<"div">)
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn(
|
||||
"flex items-center gap-1 text-sm text-accent [&>svg]:inline-block [&>svg]:size-[12px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<div data-slot="card-description" className={cn("text-sm text-accent", className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ function UnstableDropdownMenuContent({
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)",
|
||||
"z-50 overflow-x-hidden overflow-y-auto rounded-[6px] border border-border/50 bg-popover p-1 text-xs text-foreground shadow-md",
|
||||
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
"z-50 overflow-x-hidden overflow-y-auto rounded-[6px] border border-border bg-popover p-1 text-xs text-foreground shadow-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -70,7 +70,7 @@ function UnstableEmptyDescription({ className, ...props }: React.ComponentProps<
|
||||
<div
|
||||
data-slot="empty-description"
|
||||
className={cn(
|
||||
"text-xs/relaxed text-muted [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||
"text-xs/relaxed text-muted [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-project",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -35,8 +35,8 @@ const iconButtonVariants = cva(
|
||||
"border-danger/75 bg-danger/40 text-foreground hover:bg-danger/50 hover:border-danger"
|
||||
},
|
||||
size: {
|
||||
xs: "h-6 w-6 [&>svg]:size-4 rounded-[5px] [&>svg]:stroke-[1.75]",
|
||||
sm: "h-8 w-8 [&>svg]:size-5 [&>svg]:stroke-[1.5]",
|
||||
xs: "h-7 w-7 [&>svg]:size-3.5 [&>svg]:stroke-[1.75]",
|
||||
sm: "h-8 w-8 [&>svg]:size-4 [&>svg]:stroke-[1.5]",
|
||||
md: "h-9 w-9 [&>svg]:size-6 [&>svg]:stroke-[1.5]",
|
||||
lg: "h-10 w-10 [&>svg]:size-7 [&>svg]:stroke-[1.5]"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ function UnstableTable({ className, ...props }: React.ComponentProps<"table">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="table-container"
|
||||
className="relative w-full overflow-x-auto border border-border/50 bg-mineshaft-800/50"
|
||||
className="relative w-full overflow-x-auto border border-border bg-container"
|
||||
>
|
||||
<table
|
||||
data-slot="table"
|
||||
@@ -39,10 +39,7 @@ function UnstableTableFooter({ className, ...props }: React.ComponentProps<"tfoo
|
||||
return (
|
||||
<tfoot
|
||||
data-slot="table-footer"
|
||||
className={cn(
|
||||
"border-t border-border/50 bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
className={cn("border-t border-border font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -53,7 +50,7 @@ function UnstableTableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||
<tr
|
||||
data-slot="table-row"
|
||||
className={cn(
|
||||
"border-b border-border/50 transition-colors hover:bg-foreground/5 data-[state=selected]:bg-foreground/5",
|
||||
"border-b border-border transition-colors hover:bg-foreground/5 data-[state=selected]:bg-foreground/5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -66,7 +63,7 @@ function UnstableTableHead({ className, ...props }: React.ComponentProps<"th">)
|
||||
<th
|
||||
data-slot="table-head"
|
||||
className={cn(
|
||||
"h-[30px] border-x-0 border-t-0 border-b border-border/50 px-3 text-left align-middle text-xs whitespace-nowrap text-accent text-mineshaft-400 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
"h-[30px] border-x-0 border-t-0 border-b border-border px-3 text-left align-middle text-xs whitespace-nowrap text-accent [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -91,7 +88,7 @@ function UnstableTableCaption({ className, ...props }: React.ComponentProps<"cap
|
||||
return (
|
||||
<caption
|
||||
data-slot="table-caption"
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./Accordion";
|
||||
export * from "./Alert";
|
||||
export * from "./Badge";
|
||||
export * from "./Button";
|
||||
|
||||
@@ -18,7 +18,7 @@ export type TIdentity = {
|
||||
updatedAt: string;
|
||||
hasDeleteProtection: boolean;
|
||||
authMethods: IdentityAuthMethod[];
|
||||
activeLockoutAuthMethods: string[];
|
||||
activeLockoutAuthMethods: IdentityAuthMethod[];
|
||||
metadata?: Array<TMetadata & { id: string }>;
|
||||
};
|
||||
|
||||
|
||||
@@ -49,14 +49,14 @@
|
||||
--color-sub-org: #96ff59;
|
||||
--color-project: #e0ed34;
|
||||
--color-neutral: #adaeb0;
|
||||
--color-border: #323439;
|
||||
--color-border: #2b2c30;
|
||||
--color-label: #adaeb0;
|
||||
--color-muted: #707174;
|
||||
--color-popover: #111419;
|
||||
--color-popover: #141617;
|
||||
--color-ring: #2d2f33;
|
||||
--color-container: #16181a;
|
||||
--color-card: #16181a;
|
||||
--color-accent: #7d7f80;
|
||||
--color-muted-foreground: ;
|
||||
--color-container: #1a1c1e;
|
||||
|
||||
/*legacy color schema */
|
||||
--color-org-v1: #30b3ff;
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
} from "@app/context";
|
||||
import { useDeleteOrgIdentity, useGetOrgIdentityMembershipById } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal";
|
||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||
|
||||
import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
|
||||
@@ -125,14 +124,16 @@ const Page = () => {
|
||||
identityId={identityId}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col gap-y-4">
|
||||
{!isAuthHidden && (
|
||||
<IdentityAuthenticationSection
|
||||
identityId={identityId}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
)}
|
||||
<IdentityProjectsSection identityId={identityId} />
|
||||
</div>
|
||||
<IdentityProjectsSection identityId={identityId} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -171,14 +172,6 @@ const Page = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ViewIdentityAuthModal
|
||||
isOpen={popUp.viewAuthMethod.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)}
|
||||
authMethod={popUp.viewAuthMethod.data?.authMethod}
|
||||
lockedOut={popUp.viewAuthMethod.data?.lockedOut || false}
|
||||
identityId={identityId}
|
||||
onResetAllLockouts={popUp.viewAuthMethod.data?.refetchIdentity}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { faCog, faLock, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, Tooltip } from "@app/components/v2";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
useGetOrgIdentityMembershipById
|
||||
} from "@app/hooks/api";
|
||||
import { IdentityAuthMethod, useGetOrgIdentityMembershipById } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { ViewIdentityAuth } from "../ViewIdentityAuth";
|
||||
|
||||
type Props = {
|
||||
identityId: string;
|
||||
handlePopUpOpen: (
|
||||
@@ -23,37 +21,42 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
|
||||
const { data, refetch } = useGetOrgIdentityMembershipById(identityId);
|
||||
|
||||
return data ? (
|
||||
<div className="mt-4 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">
|
||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-medium text-mineshaft-100">Authentication</h3>
|
||||
{!Object.values(IdentityAuthMethod).every((method) =>
|
||||
data.identity.authMethods.includes(method)
|
||||
) && (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Edit}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("identityAuthMethod", {
|
||||
identityId,
|
||||
name: data.identity.name,
|
||||
allAuthMethods: data.identity.authMethods
|
||||
});
|
||||
}}
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
{data.identity.authMethods.length ? "Add" : "Create"} Auth Method
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
{data.identity.authMethods.length > 0 ? (
|
||||
<div className="flex flex-col divide-y divide-mineshaft-400/50">
|
||||
{data.identity.authMethods.map((authMethod) => (
|
||||
<button
|
||||
key={authMethod}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("viewAuthMethod", {
|
||||
authMethod,
|
||||
lockedOut: data.identity.activeLockoutAuthMethods.includes(authMethod),
|
||||
refetchIdentity: refetch
|
||||
})
|
||||
}
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between bg-mineshaft-900 px-4 py-2 text-sm hover:bg-mineshaft-700 data-[state=open]:bg-mineshaft-600"
|
||||
>
|
||||
<span>{identityAuthToNameMap[authMethod]}</span>
|
||||
<div className="flex gap-2">
|
||||
{data.identity.activeLockoutAuthMethods.includes(authMethod) && (
|
||||
<Tooltip content="Auth method has active lockouts">
|
||||
<FontAwesomeIcon icon={faLock} size="xs" className="text-red-400/50" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<FontAwesomeIcon icon={faCog} size="xs" className="text-mineshaft-400" />
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<ViewIdentityAuth
|
||||
activeLockoutAuthMethods={data.identity.activeLockoutAuthMethods}
|
||||
identityId={identityId}
|
||||
authMethods={data.identity.authMethods}
|
||||
onResetAllLockouts={refetch}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full space-y-2 pt-2">
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
@@ -61,30 +64,6 @@ export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: P
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!Object.values(IdentityAuthMethod).every((method) =>
|
||||
data.identity.authMethods.includes(method)
|
||||
) && (
|
||||
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("identityAuthMethod", {
|
||||
identityId,
|
||||
name: data.identity.name,
|
||||
allAuthMethods: data.identity.authMethods
|
||||
});
|
||||
}}
|
||||
variant="outline_bg"
|
||||
className="mt-3 w-full"
|
||||
size="xs"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
{data.identity.authMethods.length ? "Add" : "Create"} Auth Method
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { Detail, DetailLabel, DetailValue } from "@app/components/v3";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => {
|
||||
return (
|
||||
<Detail className={className}>
|
||||
<DetailLabel>{label}</DetailLabel>
|
||||
<DetailValue>
|
||||
{children ? (
|
||||
<p className="break-words">{children}</p>
|
||||
) : (
|
||||
<p className="text-muted italic">Not set</p>
|
||||
)}
|
||||
</DetailValue>
|
||||
</Detail>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityAliCloudAuth } from "@app/hooks/api";
|
||||
import { IdentityAliCloudAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityAliCloudAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityAliCloudAuth(identityId);
|
||||
|
||||
@@ -34,23 +31,8 @@ export const ViewIdentityAliCloudAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityAliCloudAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -0,0 +1,276 @@
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { EllipsisIcon, LockIcon } from "lucide-react";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { DeleteActionModal, Modal, ModalContent, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
Badge,
|
||||
UnstableAccordion,
|
||||
UnstableAccordionContent,
|
||||
UnstableAccordionItem,
|
||||
UnstableAccordionTrigger,
|
||||
UnstableDropdownMenu,
|
||||
UnstableDropdownMenuContent,
|
||||
UnstableDropdownMenuItem,
|
||||
UnstableDropdownMenuTrigger,
|
||||
UnstableIconButton
|
||||
} from "@app/components/v3";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
useDeleteIdentityAliCloudAuth,
|
||||
useDeleteIdentityAwsAuth,
|
||||
useDeleteIdentityAzureAuth,
|
||||
useDeleteIdentityGcpAuth,
|
||||
useDeleteIdentityJwtAuth,
|
||||
useDeleteIdentityKubernetesAuth,
|
||||
useDeleteIdentityLdapAuth,
|
||||
useDeleteIdentityOciAuth,
|
||||
useDeleteIdentityOidcAuth,
|
||||
useDeleteIdentityTlsCertAuth,
|
||||
useDeleteIdentityTokenAuth,
|
||||
useDeleteIdentityUniversalAuth
|
||||
} from "@app/hooks/api";
|
||||
import { IdentityAliCloudAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm";
|
||||
import { IdentityAwsAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm";
|
||||
import { IdentityAzureAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm";
|
||||
import { IdentityGcpAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm";
|
||||
import { IdentityJwtAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm";
|
||||
import { IdentityKubernetesAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm";
|
||||
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm";
|
||||
import { IdentityOciAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm";
|
||||
import { IdentityOidcAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm";
|
||||
import { IdentityTlsCertAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm";
|
||||
import { IdentityTokenAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm";
|
||||
import { IdentityUniversalAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm";
|
||||
|
||||
import { ViewIdentityAliCloudAuthContent } from "./ViewIdentityAliCloudAuthContent";
|
||||
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
|
||||
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
|
||||
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
|
||||
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
|
||||
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
|
||||
import { ViewIdentityLdapAuthContent } from "./ViewIdentityLdapAuthContent";
|
||||
import { ViewIdentityOciAuthContent } from "./ViewIdentityOciAuthContent";
|
||||
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
|
||||
import { ViewIdentityTlsCertAuthContent } from "./ViewIdentityTlsCertAuthContent";
|
||||
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
|
||||
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
|
||||
|
||||
type Props = {
|
||||
identityId: string;
|
||||
authMethods: IdentityAuthMethod[];
|
||||
onResetAllLockouts: () => void;
|
||||
activeLockoutAuthMethods: IdentityAuthMethod[];
|
||||
};
|
||||
|
||||
const AuthMethodComponentMap = {
|
||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: ViewIdentityUniversalAuthContent,
|
||||
[IdentityAuthMethod.TOKEN_AUTH]: ViewIdentityTokenAuthContent,
|
||||
[IdentityAuthMethod.TLS_CERT_AUTH]: ViewIdentityTlsCertAuthContent,
|
||||
[IdentityAuthMethod.KUBERNETES_AUTH]: ViewIdentityKubernetesAuthContent,
|
||||
[IdentityAuthMethod.LDAP_AUTH]: ViewIdentityLdapAuthContent,
|
||||
[IdentityAuthMethod.OCI_AUTH]: ViewIdentityOciAuthContent,
|
||||
[IdentityAuthMethod.OIDC_AUTH]: ViewIdentityOidcAuthContent,
|
||||
[IdentityAuthMethod.GCP_AUTH]: ViewIdentityGcpAuthContent,
|
||||
[IdentityAuthMethod.AWS_AUTH]: ViewIdentityAwsAuthContent,
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: ViewIdentityAliCloudAuthContent,
|
||||
[IdentityAuthMethod.AZURE_AUTH]: ViewIdentityAzureAuthContent,
|
||||
[IdentityAuthMethod.JWT_AUTH]: ViewIdentityJwtAuthContent
|
||||
};
|
||||
|
||||
const EditAuthMethodMap = {
|
||||
[IdentityAuthMethod.KUBERNETES_AUTH]: IdentityKubernetesAuthForm,
|
||||
[IdentityAuthMethod.GCP_AUTH]: IdentityGcpAuthForm,
|
||||
[IdentityAuthMethod.TLS_CERT_AUTH]: IdentityTlsCertAuthForm,
|
||||
[IdentityAuthMethod.AWS_AUTH]: IdentityAwsAuthForm,
|
||||
[IdentityAuthMethod.AZURE_AUTH]: IdentityAzureAuthForm,
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: IdentityAliCloudAuthForm,
|
||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: IdentityUniversalAuthForm,
|
||||
[IdentityAuthMethod.TOKEN_AUTH]: IdentityTokenAuthForm,
|
||||
[IdentityAuthMethod.OCI_AUTH]: IdentityOciAuthForm,
|
||||
[IdentityAuthMethod.OIDC_AUTH]: IdentityOidcAuthForm,
|
||||
[IdentityAuthMethod.JWT_AUTH]: IdentityJwtAuthForm,
|
||||
[IdentityAuthMethod.LDAP_AUTH]: IdentityLdapAuthForm
|
||||
};
|
||||
|
||||
export const Content = ({
|
||||
identityId,
|
||||
authMethods,
|
||||
onResetAllLockouts,
|
||||
activeLockoutAuthMethods
|
||||
}: Pick<
|
||||
Props,
|
||||
"authMethods" | "identityId" | "onResetAllLockouts" | "activeLockoutAuthMethods"
|
||||
>) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"revokeAuthMethod",
|
||||
"identityAuthMethod",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
|
||||
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
|
||||
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
|
||||
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
|
||||
const { mutateAsync: revokeTlsCertAuth } = useDeleteIdentityTlsCertAuth();
|
||||
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
|
||||
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
|
||||
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
|
||||
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
|
||||
|
||||
const RemoveAuthMethodMap = {
|
||||
[IdentityAuthMethod.KUBERNETES_AUTH]: revokeKubernetesAuth,
|
||||
[IdentityAuthMethod.GCP_AUTH]: revokeGcpAuth,
|
||||
[IdentityAuthMethod.TLS_CERT_AUTH]: revokeTlsCertAuth,
|
||||
[IdentityAuthMethod.AWS_AUTH]: revokeAwsAuth,
|
||||
[IdentityAuthMethod.AZURE_AUTH]: revokeAzureAuth,
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: revokeAliCloudAuth,
|
||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: revokeUniversalAuth,
|
||||
[IdentityAuthMethod.TOKEN_AUTH]: revokeTokenAuth,
|
||||
[IdentityAuthMethod.OCI_AUTH]: revokeOciAuth,
|
||||
[IdentityAuthMethod.OIDC_AUTH]: revokeOidcAuth,
|
||||
[IdentityAuthMethod.JWT_AUTH]: revokeJwtAuth,
|
||||
[IdentityAuthMethod.LDAP_AUTH]: revokeLdapAuth
|
||||
};
|
||||
|
||||
const handleDeleteAuthMethod = async (authMethod: IdentityAuthMethod) => {
|
||||
await RemoveAuthMethodMap[authMethod]({
|
||||
identityId,
|
||||
...(projectId
|
||||
? { projectId }
|
||||
: {
|
||||
organizationId: orgId
|
||||
})
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed auth method",
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpToggle("revokeAuthMethod", false);
|
||||
};
|
||||
|
||||
const EditForm = popUp.identityAuthMethod?.data
|
||||
? EditAuthMethodMap[popUp.identityAuthMethod.data as IdentityAuthMethod]
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<UnstableAccordion type={authMethods.length === 1 ? "single" : "multiple"} collapsible>
|
||||
{authMethods.map((authMethod) => {
|
||||
const Component = AuthMethodComponentMap[authMethod];
|
||||
|
||||
return (
|
||||
<UnstableAccordionItem key={authMethod} value={authMethod}>
|
||||
<UnstableAccordionTrigger>
|
||||
<span className="mr-auto">{identityAuthToNameMap[authMethod]}</span>
|
||||
{activeLockoutAuthMethods?.includes(authMethod) && (
|
||||
<Tooltip content="Auth method has active lockouts">
|
||||
<Badge isSquare variant="danger">
|
||||
<LockIcon />
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
<UnstableDropdownMenu>
|
||||
<UnstableDropdownMenuTrigger asChild>
|
||||
<UnstableIconButton variant="ghost" size="xs">
|
||||
<EllipsisIcon />
|
||||
</UnstableIconButton>
|
||||
</UnstableDropdownMenuTrigger>
|
||||
<UnstableDropdownMenuContent align="end">
|
||||
<UnstableDropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("identityAuthMethod", authMethod);
|
||||
}}
|
||||
>
|
||||
Edit Auth Method
|
||||
</UnstableDropdownMenuItem>
|
||||
<UnstableDropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("revokeAuthMethod", authMethod);
|
||||
}}
|
||||
variant="danger"
|
||||
>
|
||||
Remove Auth Method
|
||||
</UnstableDropdownMenuItem>
|
||||
</UnstableDropdownMenuContent>
|
||||
</UnstableDropdownMenu>
|
||||
</UnstableAccordionTrigger>
|
||||
<UnstableAccordionContent>
|
||||
<Component
|
||||
identityId={identityId}
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod", authMethod)}
|
||||
onDelete={() => handlePopUpOpen("revokeAuthMethod", authMethod)}
|
||||
onResetAllLockouts={onResetAllLockouts}
|
||||
lockedOut={activeLockoutAuthMethods?.includes(authMethod)}
|
||||
/>
|
||||
</UnstableAccordionContent>
|
||||
</UnstableAccordionItem>
|
||||
);
|
||||
})}
|
||||
</UnstableAccordion>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp?.revokeAuthMethod?.isOpen}
|
||||
title={`Are you sure you want to remove ${popUp?.revokeAuthMethod?.data ? identityAuthToNameMap[popUp?.revokeAuthMethod?.data as IdentityAuthMethod] : "this auth method"} on this identity?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
|
||||
deleteKey="confirm"
|
||||
buttonText="Remove"
|
||||
onDeleteApproved={() =>
|
||||
handleDeleteAuthMethod(popUp?.revokeAuthMethod?.data as IdentityAuthMethod)
|
||||
}
|
||||
/>
|
||||
<Modal
|
||||
isOpen={popUp?.identityAuthMethod?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("identityAuthMethod", isOpen)}
|
||||
>
|
||||
<ModalContent
|
||||
title={`Edit ${popUp?.identityAuthMethod?.data ? identityAuthToNameMap[popUp?.identityAuthMethod?.data as IdentityAuthMethod] : "this auth method"}`}
|
||||
>
|
||||
{EditForm && (
|
||||
<EditForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewIdentityAuth = ({
|
||||
authMethods,
|
||||
identityId,
|
||||
onResetAllLockouts,
|
||||
activeLockoutAuthMethods
|
||||
}: Props) => {
|
||||
return (
|
||||
<Content
|
||||
identityId={identityId}
|
||||
authMethods={authMethods}
|
||||
activeLockoutAuthMethods={activeLockoutAuthMethods}
|
||||
onResetAllLockouts={() => onResetAllLockouts()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityAwsAuth } from "@app/hooks/api";
|
||||
import { IdentityAwsAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityAwsAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityAwsAuth(identityId);
|
||||
|
||||
@@ -31,23 +28,8 @@ export const ViewIdentityAwsAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityAwsAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityAzureAuth } from "@app/hooks/api";
|
||||
import { IdentityAzureAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityAzureAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityAzureAuth(identityId);
|
||||
|
||||
@@ -31,23 +28,8 @@ export const ViewIdentityAzureAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityAzureAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityGcpAuth } from "@app/hooks/api";
|
||||
import { IdentityGcpAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityGcpAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityGcpAuth(identityId);
|
||||
|
||||
@@ -31,23 +28,8 @@ export const ViewIdentityGcpAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityGcpAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -5,18 +5,15 @@ import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v3";
|
||||
import { useGetIdentityJwtAuth } from "@app/hooks/api";
|
||||
import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums";
|
||||
import { IdentityJwtAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm";
|
||||
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
|
||||
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth/ViewIdentityContentWrapper";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
|
||||
export const ViewIdentityJwtAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityJwtAuth(identityId);
|
||||
|
||||
@@ -34,23 +31,8 @@ export const ViewIdentityJwtAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityJwtAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -6,7 +6,6 @@ import { EyeIcon } from "lucide-react";
|
||||
import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v3";
|
||||
import { gatewaysQueryKeys, useGetIdentityKubernetesAuth } from "@app/hooks/api";
|
||||
import { IdentityKubernetesAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -14,10 +13,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityKubernetesAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data: gateways } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
@@ -44,23 +41,8 @@ export const ViewIdentityKubernetesAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityKubernetesAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -4,8 +4,7 @@ import { EyeIcon } from "lucide-react";
|
||||
import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v3";
|
||||
import { useClearIdentityLdapAuthLockouts, useGetIdentityLdapAuth } from "@app/hooks/api";
|
||||
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm";
|
||||
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
|
||||
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth/ViewIdentityContentWrapper";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { LockoutFields } from "./IdentityAuthLockoutFields";
|
||||
@@ -13,10 +12,8 @@ import { ViewAuthMethodProps } from "./types";
|
||||
|
||||
export const ViewIdentityLdapAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp,
|
||||
onEdit,
|
||||
lockedOut,
|
||||
onResetAllLockouts
|
||||
}: ViewAuthMethodProps) => {
|
||||
@@ -37,23 +34,8 @@ export const ViewIdentityLdapAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityLdapAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityOciAuth } from "@app/hooks/api";
|
||||
import { IdentityOciAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -10,10 +9,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityOciAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityOciAuth(identityId);
|
||||
|
||||
@@ -31,23 +28,8 @@ export const ViewIdentityOciAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityOciAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -4,18 +4,15 @@ import { EyeIcon } from "lucide-react";
|
||||
import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v3";
|
||||
import { useGetIdentityOidcAuth } from "@app/hooks/api";
|
||||
import { IdentityOidcAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm";
|
||||
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
|
||||
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth/ViewIdentityContentWrapper";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
|
||||
export const ViewIdentityOidcAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityOidcAuth(identityId);
|
||||
|
||||
@@ -33,23 +30,8 @@ export const ViewIdentityOidcAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityOidcAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -4,7 +4,6 @@ import { EyeIcon } from "lucide-react";
|
||||
import { EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v3";
|
||||
import { useGetIdentityTlsCertAuth } from "@app/hooks/api";
|
||||
import { IdentityTlsCertAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
@@ -12,10 +11,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityTlsCertAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit,
|
||||
onDelete
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityTlsCertAuth(identityId);
|
||||
|
||||
@@ -36,23 +33,8 @@ export const ViewIdentityTlsCertAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityTlsCertAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -2,7 +2,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityTokenAuth, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
|
||||
import { IdentityTokenAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { IdentityTokenAuthTokensTable } from "./IdentityTokenAuthTokensTable";
|
||||
@@ -11,10 +10,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityTokenAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
onEdit
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityTokenAuth(identityId);
|
||||
const { data: tokens = [], isPending: clientSecretsPending } =
|
||||
@@ -34,23 +31,8 @@ export const ViewIdentityTokenAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityTokenAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
useGetIdentityUniversalAuth,
|
||||
useGetIdentityUniversalAuthClientSecrets
|
||||
} from "@app/hooks/api";
|
||||
import { IdentityUniversalAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { LockoutFields } from "./IdentityAuthLockoutFields";
|
||||
@@ -18,10 +17,8 @@ import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityUniversalAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp,
|
||||
onEdit,
|
||||
lockedOut,
|
||||
onResetAllLockouts
|
||||
}: ViewAuthMethodProps) => {
|
||||
@@ -51,23 +48,8 @@ export const ViewIdentityUniversalAuthContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityUniversalAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
identityId={identityId}
|
||||
>
|
||||
<ViewIdentityContentWrapper onEdit={onEdit} onDelete={onDelete} identityId={identityId}>
|
||||
{Number(data.accessTokenPeriod) > 0 ? (
|
||||
<IdentityAuthFieldDisplay label="Access Token Period (seconds)">
|
||||
{data.accessTokenPeriod}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ViewIdentityAuth";
|
||||
@@ -0,0 +1,7 @@
|
||||
export type ViewAuthMethodProps = {
|
||||
identityId: string;
|
||||
onDelete: () => void;
|
||||
onEdit: () => void;
|
||||
lockedOut: boolean;
|
||||
onResetAllLockouts: () => void;
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const IdentityAuthFieldDisplay = ({ label, children, className }: Props) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<span className="text-sm text-mineshaft-400">{label}</span>
|
||||
{children ? (
|
||||
<p className="text-base leading-4 break-words">{children}</p>
|
||||
) : (
|
||||
<p className="text-base leading-4 text-bunker-400 italic">Not set</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,214 +0,0 @@
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
useDeleteIdentityAliCloudAuth,
|
||||
useDeleteIdentityAwsAuth,
|
||||
useDeleteIdentityAzureAuth,
|
||||
useDeleteIdentityGcpAuth,
|
||||
useDeleteIdentityJwtAuth,
|
||||
useDeleteIdentityKubernetesAuth,
|
||||
useDeleteIdentityLdapAuth,
|
||||
useDeleteIdentityOciAuth,
|
||||
useDeleteIdentityOidcAuth,
|
||||
useDeleteIdentityTlsCertAuth,
|
||||
useDeleteIdentityTokenAuth,
|
||||
useDeleteIdentityUniversalAuth
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
import { ViewIdentityAliCloudAuthContent } from "./ViewIdentityAliCloudAuthContent";
|
||||
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
|
||||
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
|
||||
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
|
||||
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
|
||||
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
|
||||
import { ViewIdentityLdapAuthContent } from "./ViewIdentityLdapAuthContent";
|
||||
import { ViewIdentityOciAuthContent } from "./ViewIdentityOciAuthContent";
|
||||
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
|
||||
import { ViewIdentityTlsCertAuthContent } from "./ViewIdentityTlsCertAuthContent";
|
||||
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
|
||||
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
|
||||
|
||||
type Props = {
|
||||
identityId: string;
|
||||
authMethod?: IdentityAuthMethod;
|
||||
lockedOut: boolean;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
onDeleteAuthMethod: () => void;
|
||||
onResetAllLockouts: () => void;
|
||||
};
|
||||
|
||||
type TRevokeOptions = {
|
||||
identityId: string;
|
||||
} & ({ projectId: string } | { organizationId: string });
|
||||
|
||||
export const Content = ({
|
||||
identityId,
|
||||
authMethod,
|
||||
lockedOut,
|
||||
onDeleteAuthMethod,
|
||||
onResetAllLockouts
|
||||
}: Pick<
|
||||
Props,
|
||||
"authMethod" | "lockedOut" | "identityId" | "onDeleteAuthMethod" | "onResetAllLockouts"
|
||||
>) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"revokeAuthMethod",
|
||||
"upgradePlan",
|
||||
"identityAuthMethod"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
|
||||
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
|
||||
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
|
||||
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
|
||||
const { mutateAsync: revokeTlsCertAuth } = useDeleteIdentityTlsCertAuth();
|
||||
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
|
||||
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
|
||||
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
|
||||
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
|
||||
|
||||
let Component: (props: ViewAuthMethodProps) => JSX.Element;
|
||||
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
|
||||
|
||||
const handleDelete = () => handlePopUpOpen("revokeAuthMethod");
|
||||
|
||||
switch (authMethod) {
|
||||
case IdentityAuthMethod.UNIVERSAL_AUTH:
|
||||
revokeMethod = revokeUniversalAuth;
|
||||
Component = ViewIdentityUniversalAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.TOKEN_AUTH:
|
||||
revokeMethod = revokeTokenAuth;
|
||||
Component = ViewIdentityTokenAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.KUBERNETES_AUTH:
|
||||
revokeMethod = revokeKubernetesAuth;
|
||||
Component = ViewIdentityKubernetesAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.GCP_AUTH:
|
||||
revokeMethod = revokeGcpAuth;
|
||||
Component = ViewIdentityGcpAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.TLS_CERT_AUTH:
|
||||
revokeMethod = revokeTlsCertAuth;
|
||||
Component = ViewIdentityTlsCertAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.AWS_AUTH:
|
||||
revokeMethod = revokeAwsAuth;
|
||||
Component = ViewIdentityAwsAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.AZURE_AUTH:
|
||||
revokeMethod = revokeAzureAuth;
|
||||
Component = ViewIdentityAzureAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.OCI_AUTH:
|
||||
revokeMethod = revokeOciAuth;
|
||||
Component = ViewIdentityOciAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.ALICLOUD_AUTH:
|
||||
revokeMethod = revokeAliCloudAuth;
|
||||
Component = ViewIdentityAliCloudAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.OIDC_AUTH:
|
||||
revokeMethod = revokeOidcAuth;
|
||||
Component = ViewIdentityOidcAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.JWT_AUTH:
|
||||
revokeMethod = revokeJwtAuth;
|
||||
Component = ViewIdentityJwtAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.LDAP_AUTH:
|
||||
revokeMethod = revokeLdapAuth;
|
||||
Component = ViewIdentityLdapAuthContent;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Auth Method: ${authMethod}`);
|
||||
}
|
||||
|
||||
const handleDeleteAuthMethod = async () => {
|
||||
await revokeMethod({
|
||||
identityId,
|
||||
...(projectId
|
||||
? { projectId }
|
||||
: {
|
||||
organizationId: orgId
|
||||
})
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed auth method",
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpToggle("revokeAuthMethod", false);
|
||||
onDeleteAuthMethod();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Component
|
||||
identityId={identityId}
|
||||
onDelete={handleDelete}
|
||||
onResetAllLockouts={onResetAllLockouts}
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
lockedOut={lockedOut}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp?.revokeAuthMethod?.isOpen}
|
||||
title={`Are you sure you want to remove ${identityAuthToNameMap[authMethod]} on this identity?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
|
||||
deleteKey="confirm"
|
||||
buttonText="Remove"
|
||||
onDeleteApproved={handleDeleteAuthMethod}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewIdentityAuthModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
authMethod,
|
||||
identityId,
|
||||
lockedOut,
|
||||
onResetAllLockouts
|
||||
}: Omit<Props, "onDeleteAuthMethod">) => {
|
||||
if (!identityId || !authMethod) return null;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent className="max-w-2xl" title={identityAuthToNameMap[authMethod]}>
|
||||
<Content
|
||||
identityId={identityId}
|
||||
authMethod={authMethod}
|
||||
lockedOut={lockedOut}
|
||||
onDeleteAuthMethod={() => onOpenChange(false)}
|
||||
onResetAllLockouts={() => onResetAllLockouts()}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ViewIdentityAuthModal";
|
||||
@@ -1,14 +0,0 @@
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
export type ViewAuthMethodProps = {
|
||||
identityId: string;
|
||||
onDelete: () => void;
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan", "identityAuthMethod"]>) => void;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
popUp: UsePopUpState<["revokeAuthMethod", "upgradePlan", "identityAuthMethod"]>;
|
||||
lockedOut: boolean;
|
||||
onResetAllLockouts: () => void;
|
||||
};
|
||||
@@ -179,14 +179,13 @@ const Page = () => {
|
||||
search={{
|
||||
selectedTab: ProjectAccessControlTabs.Identities
|
||||
}}
|
||||
className="mb-3 flex w-fit items-center gap-x-1 text-sm text-mineshaft-400 transition duration-100 hover:text-mineshaft-400/80"
|
||||
className="mb-4 flex w-fit items-center gap-x-1 text-sm text-mineshaft-400 transition duration-100 hover:text-mineshaft-400/80"
|
||||
>
|
||||
<ChevronLeftIcon size={16} />
|
||||
Project Machine Identities
|
||||
</Link>
|
||||
<PageHeader
|
||||
scope={currentProject.type}
|
||||
className="mb-20"
|
||||
description={`Configure and manage${isProjectIdentity ? " machine identity and " : " "}project access control`}
|
||||
title={identityMembershipDetails.identity.name}
|
||||
>
|
||||
@@ -256,14 +255,14 @@ const Page = () => {
|
||||
</UnstableDropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</PageHeader>
|
||||
<div className="flex flex-col gap-20 lg:flex-row">
|
||||
<div className="flex flex-col gap-5 lg:flex-row">
|
||||
<ProjectIdentityDetailsSection
|
||||
identity={identity || { ...identityMembershipDetails?.identity, projectId: "" }}
|
||||
isOrgIdentity={!isProjectIdentity}
|
||||
membership={identityMembershipDetails!}
|
||||
/>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-y-20">
|
||||
<div className="flex flex-1 flex-col gap-y-5">
|
||||
{identity ? (
|
||||
<ProjectIdentityAuthenticationSection
|
||||
identity={identity}
|
||||
|
||||
@@ -81,7 +81,9 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
|
||||
return (
|
||||
<>
|
||||
<UnstableCard>
|
||||
<UnstableCardHeader className="border-b">
|
||||
<UnstableCardHeader
|
||||
// className="border-b"
|
||||
>
|
||||
<UnstableCardTitle>Project Additional Privileges</UnstableCardTitle>
|
||||
<UnstableCardDescription>
|
||||
Assign one-off policies to this machine identity
|
||||
@@ -96,7 +98,7 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<UnstableButton
|
||||
variant="project"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
handlePopUpOpen("modifyPrivilege");
|
||||
|
||||
@@ -96,7 +96,9 @@ export const IdentityRoleDetailsSection = ({
|
||||
return (
|
||||
<>
|
||||
<UnstableCard>
|
||||
<UnstableCardHeader className="border-b">
|
||||
<UnstableCardHeader
|
||||
// className="border-b"
|
||||
>
|
||||
<UnstableCardTitle>Project Roles</UnstableCardTitle>
|
||||
<UnstableCardDescription>
|
||||
Manage roles assigned to this machine identity
|
||||
@@ -112,7 +114,7 @@ export const IdentityRoleDetailsSection = ({
|
||||
{(isAllowed) => (
|
||||
<UnstableButton
|
||||
size="xs"
|
||||
variant="project"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
handlePopUpOpen("modifyRole");
|
||||
}}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { subject } from "@casl/ability";
|
||||
import { EllipsisIcon, LockIcon, PlusIcon } from "lucide-react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Tooltip } from "@app/components/v2";
|
||||
import { DeleteActionModal } from "@app/components/v2";
|
||||
import {
|
||||
Badge,
|
||||
UnstableButton,
|
||||
UnstableCard,
|
||||
UnstableCardAction,
|
||||
@@ -17,20 +17,29 @@ import {
|
||||
UnstableEmptyContent,
|
||||
UnstableEmptyDescription,
|
||||
UnstableEmptyHeader,
|
||||
UnstableEmptyTitle,
|
||||
UnstableIconButton,
|
||||
UnstableTable,
|
||||
UnstableTableBody,
|
||||
UnstableTableCell,
|
||||
UnstableTableHead,
|
||||
UnstableTableHeader,
|
||||
UnstableTableRow
|
||||
UnstableEmptyTitle
|
||||
} from "@app/components/v3";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/context";
|
||||
import { IdentityAuthMethod, identityAuthToNameMap, TProjectIdentity } from "@app/hooks/api";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
TProjectIdentity,
|
||||
useDeleteIdentityAliCloudAuth,
|
||||
useDeleteIdentityAwsAuth,
|
||||
useDeleteIdentityAzureAuth,
|
||||
useDeleteIdentityGcpAuth,
|
||||
useDeleteIdentityJwtAuth,
|
||||
useDeleteIdentityKubernetesAuth,
|
||||
useDeleteIdentityLdapAuth,
|
||||
useDeleteIdentityOciAuth,
|
||||
useDeleteIdentityOidcAuth,
|
||||
useDeleteIdentityTlsCertAuth,
|
||||
useDeleteIdentityTokenAuth,
|
||||
useDeleteIdentityUniversalAuth
|
||||
} from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { IdentityAuthMethodModal } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
|
||||
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal";
|
||||
import { ViewIdentityAuth } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuth";
|
||||
|
||||
type Props = {
|
||||
identity: TProjectIdentity;
|
||||
@@ -39,17 +48,58 @@ type Props = {
|
||||
|
||||
export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity }: Props) => {
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
|
||||
"viewAuthMethod",
|
||||
"identityAuthMethod",
|
||||
"upgradePlan"
|
||||
"upgradePlan",
|
||||
"revokeAuthMethod"
|
||||
]);
|
||||
|
||||
const hasAuthMethods = Boolean(identity.authMethods.length);
|
||||
|
||||
const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth();
|
||||
const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth();
|
||||
const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth();
|
||||
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
|
||||
const { mutateAsync: revokeTlsCertAuth } = useDeleteIdentityTlsCertAuth();
|
||||
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
|
||||
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
|
||||
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
|
||||
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
|
||||
|
||||
const RemoveAuthMap = {
|
||||
[IdentityAuthMethod.KUBERNETES_AUTH]: revokeKubernetesAuth,
|
||||
[IdentityAuthMethod.GCP_AUTH]: revokeGcpAuth,
|
||||
[IdentityAuthMethod.TLS_CERT_AUTH]: revokeTlsCertAuth,
|
||||
[IdentityAuthMethod.AWS_AUTH]: revokeAwsAuth,
|
||||
[IdentityAuthMethod.AZURE_AUTH]: revokeAzureAuth,
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: revokeAliCloudAuth,
|
||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: revokeUniversalAuth,
|
||||
[IdentityAuthMethod.TOKEN_AUTH]: revokeTokenAuth,
|
||||
[IdentityAuthMethod.OCI_AUTH]: revokeOciAuth,
|
||||
[IdentityAuthMethod.OIDC_AUTH]: revokeOidcAuth,
|
||||
[IdentityAuthMethod.JWT_AUTH]: revokeJwtAuth,
|
||||
[IdentityAuthMethod.LDAP_AUTH]: revokeLdapAuth
|
||||
};
|
||||
|
||||
const handleDeleteAuthMethod = async (authMethod: IdentityAuthMethod) => {
|
||||
await RemoveAuthMap[authMethod]({
|
||||
identityId: identity.id,
|
||||
projectId: identity.projectId!
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed auth method",
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpToggle("revokeAuthMethod", false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UnstableCard>
|
||||
<UnstableCardHeader className="border-b">
|
||||
<UnstableCardHeader>
|
||||
<UnstableCardTitle>Authentication</UnstableCardTitle>
|
||||
<UnstableCardDescription>Configure authentication methods</UnstableCardDescription>
|
||||
{hasAuthMethods &&
|
||||
@@ -65,7 +115,7 @@ export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<UnstableButton
|
||||
variant="project"
|
||||
variant="outline"
|
||||
isFullWidth
|
||||
size="xs"
|
||||
isDisabled={!isAllowed}
|
||||
@@ -87,43 +137,12 @@ export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity
|
||||
</UnstableCardHeader>
|
||||
<UnstableCardContent>
|
||||
{identity.authMethods.length > 0 ? (
|
||||
<UnstableTable>
|
||||
<UnstableTableHeader>
|
||||
<UnstableTableHead className="w-full">Method</UnstableTableHead>
|
||||
<UnstableTableHead className="w-5" />
|
||||
</UnstableTableHeader>
|
||||
<UnstableTableBody>
|
||||
{identity.authMethods.map((authMethod) => (
|
||||
<UnstableTableRow
|
||||
key={authMethod}
|
||||
className="cursor-pointer"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("viewAuthMethod", {
|
||||
authMethod,
|
||||
lockedOut: identity.activeLockoutAuthMethods?.includes(authMethod) ?? false,
|
||||
refetchIdentity
|
||||
})
|
||||
}
|
||||
>
|
||||
<UnstableTableCell>{identityAuthToNameMap[authMethod]}</UnstableTableCell>
|
||||
<UnstableTableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
{identity.activeLockoutAuthMethods?.includes(authMethod) && (
|
||||
<Tooltip content="Auth method has active lockouts">
|
||||
<Badge isSquare variant="danger">
|
||||
<LockIcon />
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
<UnstableIconButton variant="ghost" size="xs">
|
||||
<EllipsisIcon />
|
||||
</UnstableIconButton>
|
||||
</div>
|
||||
</UnstableTableCell>
|
||||
</UnstableTableRow>
|
||||
))}
|
||||
</UnstableTableBody>
|
||||
</UnstableTable>
|
||||
<ViewIdentityAuth
|
||||
authMethods={identity.authMethods}
|
||||
identityId={identity.id}
|
||||
onResetAllLockouts={refetchIdentity}
|
||||
activeLockoutAuthMethods={identity.activeLockoutAuthMethods}
|
||||
/>
|
||||
) : (
|
||||
<UnstableEmpty className="rounded-sm border bg-mineshaft-800/50">
|
||||
<UnstableEmptyHeader>
|
||||
@@ -175,13 +194,15 @@ export const ProjectIdentityAuthenticationSection = ({ identity, refetchIdentity
|
||||
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
|
||||
/>
|
||||
<ViewIdentityAuthModal
|
||||
isOpen={popUp.viewAuthMethod.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("viewAuthMethod", isOpen)}
|
||||
authMethod={popUp.viewAuthMethod.data?.authMethod}
|
||||
lockedOut={popUp.viewAuthMethod.data?.lockedOut || false}
|
||||
identityId={identity.id}
|
||||
onResetAllLockouts={popUp.viewAuthMethod.data?.refetchIdentity}
|
||||
<DeleteActionModal
|
||||
isOpen={popUp?.revokeAuthMethod?.isOpen}
|
||||
title={`Are you sure you want to remove ${popUp?.revokeAuthMethod?.data ? identityAuthToNameMap[popUp.revokeAuthMethod.data as IdentityAuthMethod] : "this auth method"} on this identity?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("revokeAuthMethod", isOpen)}
|
||||
deleteKey="confirm"
|
||||
buttonText="Remove"
|
||||
onDeleteApproved={() =>
|
||||
handleDeleteAuthMethod(popUp.revokeAuthMethod.data as IdentityAuthMethod)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
DetailValue,
|
||||
OrgIcon,
|
||||
ProjectIcon,
|
||||
UnstableButton,
|
||||
UnstableButtonGroup,
|
||||
UnstableCard,
|
||||
UnstableCardAction,
|
||||
@@ -44,8 +43,10 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
|
||||
|
||||
return (
|
||||
<>
|
||||
<UnstableCard className="w-full max-w-84">
|
||||
<UnstableCardHeader className="border-b">
|
||||
<UnstableCard className="w-full max-w-[22rem]">
|
||||
<UnstableCardHeader
|
||||
// className="border-b"
|
||||
>
|
||||
<UnstableCardTitle>Details</UnstableCardTitle>
|
||||
<UnstableCardDescription>Machine identity details</UnstableCardDescription>
|
||||
{!isOrgIdentity && (
|
||||
@@ -57,17 +58,16 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<UnstableButton
|
||||
<UnstableIconButton
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("editIdentity");
|
||||
}}
|
||||
size="xs"
|
||||
variant="project"
|
||||
variant="outline"
|
||||
>
|
||||
<PencilIcon />
|
||||
Edit Details
|
||||
</UnstableButton>
|
||||
</UnstableIconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</UnstableCardAction>
|
||||
@@ -92,6 +92,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
>
|
||||
{/* TODO(scott): color this should be a button variant */}
|
||||
{isCopyingId ? <CheckIcon /> : <ClipboardListIcon className="text-label" />}
|
||||
</UnstableIconButton>
|
||||
</Tooltip>
|
||||
@@ -118,9 +119,13 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
|
||||
<DetailValue className="flex flex-wrap gap-2">
|
||||
{identity?.metadata?.length ? (
|
||||
identity.metadata?.map((el) => (
|
||||
<UnstableButtonGroup key={el.id}>
|
||||
<Badge>{el.key}</Badge>
|
||||
<Badge variant="outline">{el.value}</Badge>
|
||||
<UnstableButtonGroup className="min-w-0" key={el.id}>
|
||||
<Badge isTruncatable>
|
||||
<span>{el.key}</span>
|
||||
</Badge>
|
||||
<Badge variant="outline" isTruncatable>
|
||||
<span>{el.value}</span>
|
||||
</Badge>
|
||||
</UnstableButtonGroup>
|
||||
))
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user