mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 18:27:19 +00:00
feat: fixed metadata to be single origin and return in all scopes
equally and some ui improvement
This commit is contained in:
@@ -141,7 +141,15 @@ export const registerIdentityProjectMembershipRouter = async (server: FastifyZod
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({
|
identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true, projectId: true }).extend({
|
||||||
authMethods: z.array(z.string())
|
authMethods: z.array(z.string()),
|
||||||
|
metadata: z
|
||||||
|
.object({
|
||||||
|
id: z.string().trim().min(1),
|
||||||
|
key: z.string().trim().min(1),
|
||||||
|
value: z.string().trim().min(1)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ export const identityV2DALFactory = (db: TDbClient) => {
|
|||||||
const doc = await db
|
const doc = await db
|
||||||
.replicaNode()(TableName.Identity)
|
.replicaNode()(TableName.Identity)
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`);
|
||||||
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
|
||||||
.andOn(`${TableName.Identity}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
|
||||||
})
|
})
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.IdentityUniversalAuth,
|
TableName.IdentityUniversalAuth,
|
||||||
@@ -134,9 +132,7 @@ export const identityV2DALFactory = (db: TDbClient) => {
|
|||||||
const query = db
|
const query = db
|
||||||
.replicaNode()(TableName.Identity)
|
.replicaNode()(TableName.Identity)
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`);
|
||||||
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
|
||||||
.andOn(`${TableName.Identity}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
|
||||||
})
|
})
|
||||||
.where(`${TableName.Identity}.orgId`, scopeData.orgId)
|
.where(`${TableName.Identity}.orgId`, scopeData.orgId)
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
|
|||||||
@@ -189,9 +189,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder.on(`paginatedIdentity.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`);
|
||||||
.on(`paginatedIdentity.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
|
||||||
.andOn(`paginatedIdentity.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
|
||||||
})
|
})
|
||||||
.leftJoin<TIdentityUniversalAuths>(
|
.leftJoin<TIdentityUniversalAuths>(
|
||||||
TableName.IdentityUniversalAuth,
|
TableName.IdentityUniversalAuth,
|
||||||
@@ -449,9 +447,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
|
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
|
||||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`);
|
||||||
.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
|
||||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
|
||||||
})
|
})
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.IdentityUniversalAuth,
|
TableName.IdentityUniversalAuth,
|
||||||
|
|||||||
@@ -222,8 +222,6 @@ export const identityServiceFactory = ({
|
|||||||
|
|
||||||
const identityDetails = await identityDAL.findById(id);
|
const identityDetails = await identityDAL.findById(id);
|
||||||
|
|
||||||
console.log("has project id", identityDetails);
|
|
||||||
|
|
||||||
if (identityDetails.projectId) {
|
if (identityDetails.projectId) {
|
||||||
throw new BadRequestError({ message: `Identity is managed by project` });
|
throw new BadRequestError({ message: `Identity is managed by project` });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
|||||||
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||||
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`);
|
||||||
.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`)
|
|
||||||
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
|
||||||
})
|
})
|
||||||
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
||||||
.where(`${TableName.Membership}.actorIdentityId`, identityId)
|
.where(`${TableName.Membership}.actorIdentityId`, identityId)
|
||||||
@@ -216,7 +214,10 @@ export const membershipIdentityDALFactory = (db: TDbClient) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
return data?.[0];
|
const el = data?.[0];
|
||||||
|
if (!el) return el;
|
||||||
|
|
||||||
|
return { ...el, identity: { ...el.identity, metadata: el.metadata } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "MembershipGetByIdentityId" });
|
throw new DatabaseError({ error, name: "MembershipGetByIdentityId" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,3 +99,18 @@ export const useGetProjectIdentityMembership = (projectId: string, identityId: s
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetProjectIdentityMembershipV2 = (projectId: string, identityId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
enabled: Boolean(projectId && identityId),
|
||||||
|
queryKey: projectKeys.getProjectIdentityMembershipDetailsV2(projectId, identityId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { identityMembership }
|
||||||
|
} = await apiRequest.get<{ identityMembership: IdentityProjectMembership }>(
|
||||||
|
`/api/v2/projects/${projectId}/identity-memberships/${identityId}`
|
||||||
|
);
|
||||||
|
return identityMembership;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export const projectKeys = {
|
|||||||
[{ projectId }, "project-identity-memberships"] as const,
|
[{ projectId }, "project-identity-memberships"] as const,
|
||||||
getProjectIdentityMembershipDetails: (projectId: string, identityId: string) =>
|
getProjectIdentityMembershipDetails: (projectId: string, identityId: string) =>
|
||||||
[{ projectId, identityId }, "project-identity-membership-details"] as const,
|
[{ projectId, identityId }, "project-identity-membership-details"] as const,
|
||||||
|
getProjectIdentityMembershipDetailsV2: (projectId: string, identityId: string) =>
|
||||||
|
[{ projectId, identityId }, "project-identity-membership-details"] as const,
|
||||||
// allows invalidation using above key without knowing params
|
// allows invalidation using above key without knowing params
|
||||||
getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) =>
|
getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) =>
|
||||||
[...projectKeys.getProjectIdentityMemberships(projectId), params] as const,
|
[...projectKeys.getProjectIdentityMemberships(projectId), params] as const,
|
||||||
|
|||||||
+68
-60
@@ -242,67 +242,75 @@ export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div>
|
{isOrgIdentity && (
|
||||||
<FormLabel label="Metadata" />
|
<>
|
||||||
</div>
|
<div>
|
||||||
<div className="mb-3 flex flex-col space-y-2">
|
<FormLabel label="Metadata" />
|
||||||
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
|
|
||||||
<div key={metadataFieldId} className="flex items-end space-x-2">
|
|
||||||
<div className="grow">
|
|
||||||
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name={`metadata.${i}.key`}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
isError={Boolean(error?.message)}
|
|
||||||
errorText={error?.message}
|
|
||||||
className="mb-0"
|
|
||||||
>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grow">
|
|
||||||
{i === 0 && (
|
|
||||||
<FormLabel label="Value" className="text-xs text-mineshaft-400" isOptional />
|
|
||||||
)}
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name={`metadata.${i}.value`}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
isError={Boolean(error?.message)}
|
|
||||||
errorText={error?.message}
|
|
||||||
className="mb-0"
|
|
||||||
>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="delete key"
|
|
||||||
className="bottom-0.5 h-9"
|
|
||||||
variant="outline_bg"
|
|
||||||
onClick={() => metadataFormFields.remove(i)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="mb-3 flex flex-col space-y-2">
|
||||||
<div className="mt-2 flex justify-end">
|
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
|
||||||
<Button
|
<div key={metadataFieldId} className="flex items-end space-x-2">
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
<div className="grow">
|
||||||
size="xs"
|
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
|
||||||
variant="outline_bg"
|
<Controller
|
||||||
onClick={() => metadataFormFields.append({ key: "", value: "" })}
|
control={control}
|
||||||
>
|
name={`metadata.${i}.key`}
|
||||||
Add Key
|
render={({ field, fieldState: { error } }) => (
|
||||||
</Button>
|
<FormControl
|
||||||
</div>
|
isError={Boolean(error?.message)}
|
||||||
</div>
|
errorText={error?.message}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grow">
|
||||||
|
{i === 0 && (
|
||||||
|
<FormLabel
|
||||||
|
label="Value"
|
||||||
|
className="text-xs text-mineshaft-400"
|
||||||
|
isOptional
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`metadata.${i}.value`}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="delete key"
|
||||||
|
className="bottom-0.5 h-9"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => metadataFormFields.remove(i)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="mt-2 flex justify-end">
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => metadataFormFields.append({ key: "", value: "" })}
|
||||||
|
>
|
||||||
|
Add Key
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ import { usePopUp } from "@app/hooks";
|
|||||||
import {
|
import {
|
||||||
useAssumeProjectPrivileges,
|
useAssumeProjectPrivileges,
|
||||||
useDeleteProjectIdentityMembership,
|
useDeleteProjectIdentityMembership,
|
||||||
useGetProjectIdentityMembership
|
useGetProjectIdentityMembership,
|
||||||
|
useGetProjectIdentityMembershipV2
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { projectIdentityQuery } from "@app/hooks/api/projectIdentity";
|
import { projectIdentityQuery } from "@app/hooks/api/projectIdentity";
|
||||||
@@ -48,7 +49,7 @@ const Page = () => {
|
|||||||
const { currentProject, projectId } = useProject();
|
const { currentProject, projectId } = useProject();
|
||||||
|
|
||||||
const { data: identityMembershipDetails, isPending: isMembershipDetailsLoading } =
|
const { data: identityMembershipDetails, isPending: isMembershipDetailsLoading } =
|
||||||
useGetProjectIdentityMembership(projectId, identityId);
|
useGetProjectIdentityMembershipV2(projectId, identityId);
|
||||||
|
|
||||||
const { mutateAsync: deleteMutateAsync, isPending: isDeletingIdentity } =
|
const { mutateAsync: deleteMutateAsync, isPending: isDeletingIdentity } =
|
||||||
useDeleteProjectIdentityMembership();
|
useDeleteProjectIdentityMembership();
|
||||||
@@ -204,7 +205,7 @@ const Page = () => {
|
|||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div className="flex gap-x-4">
|
<div className="flex gap-x-4">
|
||||||
{identity && (
|
{identity ? (
|
||||||
<div className="flex w-72 flex-col gap-y-4">
|
<div className="flex w-72 flex-col gap-y-4">
|
||||||
<ProjectIdentityDetailsSection
|
<ProjectIdentityDetailsSection
|
||||||
identity={identity}
|
identity={identity}
|
||||||
@@ -215,6 +216,16 @@ const Page = () => {
|
|||||||
refetchIdentity={() => refetchIdentity()}
|
refetchIdentity={() => refetchIdentity()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className="flex w-72 flex-col gap-y-4">
|
||||||
|
<ProjectIdentityDetailsSection
|
||||||
|
identity={{ ...identityMembershipDetails?.identity, projectId: "" }}
|
||||||
|
isOrgIdentity
|
||||||
|
membership={identityMembershipDetails!}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<IdentityRoleDetailsSection
|
<IdentityRoleDetailsSection
|
||||||
|
|||||||
+3
-2
@@ -26,6 +26,7 @@ import {
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
|
useProject,
|
||||||
useProjectPermission
|
useProjectPermission
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
@@ -46,13 +47,13 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
|
|||||||
] as const);
|
] as const);
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const identityId = identityMembershipDetails?.identity?.id;
|
const identityId = identityMembershipDetails?.identity?.id;
|
||||||
const projectId = identityMembershipDetails?.project?.id;
|
const { projectId } = useProject();
|
||||||
|
|
||||||
const { mutateAsync: deletePrivilege } = useDeleteIdentityProjectAdditionalPrivilege();
|
const { mutateAsync: deletePrivilege } = useDeleteIdentityProjectAdditionalPrivilege();
|
||||||
|
|
||||||
const { data: identityProjectPrivileges, isPending } = useListIdentityProjectPrivileges({
|
const { data: identityProjectPrivileges, isPending } = useListIdentityProjectPrivileges({
|
||||||
identityId: identityMembershipDetails?.identity?.id,
|
identityId: identityMembershipDetails?.identity?.id,
|
||||||
projectId: identityMembershipDetails?.project?.id
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlePrivilegeDelete = async () => {
|
const handlePrivilegeDelete = async () => {
|
||||||
|
|||||||
+45
-32
@@ -34,10 +34,11 @@ import { ProjectIdentityModal } from "@app/pages/project/AccessControlPage/compo
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
identity: TProjectIdentity;
|
identity: TProjectIdentity;
|
||||||
|
isOrgIdentity?: boolean;
|
||||||
membership: IdentityProjectMembership;
|
membership: IdentityProjectMembership;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) => {
|
export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, membership }: Props) => {
|
||||||
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
||||||
initialState: "Copy ID to clipboard"
|
initialState: "Copy ID to clipboard"
|
||||||
});
|
});
|
||||||
@@ -76,21 +77,23 @@ export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) =
|
|||||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
<h3 className="text-lg font-medium text-mineshaft-100">Identity Details</h3>
|
<h3 className="text-lg font-medium text-mineshaft-100">Identity Details</h3>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
{!isOrgIdentity && (
|
||||||
<Button
|
<DropdownMenuTrigger asChild>
|
||||||
size="xs"
|
<Button
|
||||||
rightIcon={
|
size="xs"
|
||||||
<FontAwesomeIcon
|
rightIcon={
|
||||||
className="ml-1 transition-transform duration-200 group-data-[state=open]:rotate-180"
|
<FontAwesomeIcon
|
||||||
icon={faChevronDown}
|
className="ml-1 transition-transform duration-200 group-data-[state=open]:rotate-180"
|
||||||
/>
|
icon={faChevronDown}
|
||||||
}
|
/>
|
||||||
colorSchema="secondary"
|
}
|
||||||
className="group select-none"
|
colorSchema="secondary"
|
||||||
>
|
className="group select-none"
|
||||||
Options
|
>
|
||||||
</Button>
|
Options
|
||||||
</DropdownMenuTrigger>
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
)}
|
||||||
<DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
|
<DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionIdentityActions.Edit}
|
I={ProjectPermissionIdentityActions.Edit}
|
||||||
@@ -162,25 +165,35 @@ export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-sm font-medium text-mineshaft-300">Last Login Auth Method</p>
|
<p className="text-sm font-medium text-mineshaft-300">Managed By</p>
|
||||||
<p className="text-sm text-mineshaft-300">
|
<p className="text-sm text-mineshaft-300">
|
||||||
{membership.lastLoginAuthMethod
|
{identity.projectId ? "Project" : "Organization"}
|
||||||
? identityAuthToNameMap[membership.lastLoginAuthMethod]
|
|
||||||
: "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<p className="text-sm font-medium text-mineshaft-300">Last Login Time</p>
|
|
||||||
<p className="text-sm text-mineshaft-300">
|
|
||||||
{membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<p className="text-sm font-medium text-mineshaft-300">Delete Protection</p>
|
|
||||||
<p className="text-sm text-mineshaft-300">
|
|
||||||
{identity.hasDeleteProtection ? "On" : "Off"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{!isOrgIdentity && (
|
||||||
|
<>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-medium text-mineshaft-300">Last Login Auth Method</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{membership.lastLoginAuthMethod
|
||||||
|
? identityAuthToNameMap[membership.lastLoginAuthMethod]
|
||||||
|
: "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-medium text-mineshaft-300">Last Login Time</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-sm font-medium text-mineshaft-300">Delete Protection</p>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
{identity.hasDeleteProtection ? "On" : "Off"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-mineshaft-300">Metadata</p>
|
<p className="text-sm font-medium text-mineshaft-300">Metadata</p>
|
||||||
{identity?.metadata?.length ? (
|
{identity?.metadata?.length ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user