feat: added manageby and conditional rendering more items

This commit is contained in:
=
2025-10-20 18:58:43 +05:30
parent 8afc391e97
commit 8824be431f
11 changed files with 57 additions and 15 deletions
@@ -408,7 +408,7 @@ export const samlConfigServiceFactory = ({
}); });
} }
} else if (dto.type === "orgSlug") { } else if (dto.type === "orgSlug") {
const org = await orgDAL.findOne({ slug: dto.orgSlug }); const org = await orgDAL.findOne({ slug: dto.orgSlug, rootOrgId: null });
if (!org) { if (!org) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with slug '${dto.orgSlug}' not found` message: `Organization with slug '${dto.orgSlug}' not found`
@@ -393,7 +393,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true, permissions: true,
description: true description: true
}).optional(), }).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({ identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({
authMethods: z.array(z.string()) authMethods: z.array(z.string())
}) })
}).array(), }).array(),
@@ -25,11 +25,12 @@ import { buildAuthMethods } from "../identity/identity-fns";
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>; export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
export const identityProjectDALFactory = (db: TDbClient) => { export const identityProjectDALFactory = (db: TDbClient) => {
const findByIdentityId = async (identityId: string, tx?: Knex) => { const findByIdentityId = async (identityId: string, orgId: string, tx?: Knex) => {
try { try {
const docs = await (tx || db.replicaNode())(TableName.Membership) const docs = await (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.actorIdentityId`, identityId) .where(`${TableName.Membership}.actorIdentityId`, identityId)
.where(`${TableName.Membership}.scope`, AccessScope.Project) .where(`${TableName.Membership}.scope`, AccessScope.Project)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.whereNotNull(`${TableName.Membership}.actorIdentityId`) .whereNotNull(`${TableName.Membership}.actorIdentityId`)
.join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`) .join(TableName.Project, `${TableName.Membership}.scopeProjectId`, `${TableName.Project}.id`)
.join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`) .join(TableName.Identity, `${TableName.Membership}.actorIdentityId`, `${TableName.Identity}.id`)
@@ -519,6 +519,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"), db.ref("actorIdentityId").withSchema(TableName.Membership).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity), db.ref("hasDeleteProtection").withSchema(TableName.Identity),
db.ref("orgId").withSchema(TableName.Identity).as("identityOrgId"),
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth), db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth), db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
@@ -570,6 +571,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
crPermission, crPermission,
crName, crName,
identityId, identityId,
identityOrgId,
identityName, identityName,
hasDeleteProtection, hasDeleteProtection,
role, role,
@@ -615,6 +617,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
id: identityId as string, id: identityId as string,
name: identityName, name: identityName,
hasDeleteProtection, hasDeleteProtection,
orgId: identityOrgId,
authMethods: buildAuthMethods({ authMethods: buildAuthMethods({
uaId, uaId,
alicloudId, alicloudId,
@@ -477,7 +477,7 @@ export const identityServiceFactory = ({
}); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId); const identityMemberships = await identityProjectDAL.findByIdentityId(identityId, actorOrgId);
return identityMemberships; return identityMemberships;
}; };
@@ -1,5 +1,5 @@
import { useSuspenseQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext, useSearch } from "@tanstack/react-router";
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries"; import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
@@ -9,8 +9,13 @@ export const useOrganization = () => {
select: (el) => el.organizationId select: (el) => el.organizationId
}); });
const subOrganization = useSearch({
strict: false,
select: (el) => el?.subOrganization
});
const { data: currentOrg } = useSuspenseQuery({ const { data: currentOrg } = useSuspenseQuery({
queryKey: organizationKeys.getOrgById(organizationId), queryKey: organizationKeys.getOrgById(organizationId, subOrganization),
queryFn: () => fetchOrganizationById(organizationId), queryFn: () => fetchOrganizationById(organizationId),
staleTime: Infinity staleTime: Infinity
}); });
@@ -42,7 +42,7 @@ export const organizationKeys = {
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const, [...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const, getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const, getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
getOrgById: (orgId: string) => ["organization", { orgId }], getOrgById: (orgId: string, subOrg?: string) => ["organization", { orgId, subOrg }],
getAvailableIdentities: () => ["available-identities"], getAvailableIdentities: () => ["available-identities"],
getAvailableUsers: () => ["available-users"] getAvailableUsers: () => ["available-users"]
}; };
@@ -2,6 +2,7 @@ import { useCallback, useState } from "react";
import { import {
faArrowDown, faArrowDown,
faArrowUp, faArrowUp,
faBuilding,
faCheckCircle, faCheckCircle,
faChevronRight, faChevronRight,
faEdit, faEdit,
@@ -78,7 +79,7 @@ type Filter = {
export const IdentityTable = ({ handlePopUpOpen }: Props) => { export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { currentOrg } = useOrganization(); const { currentOrg, isSubOrganization } = useOrganization();
const { const {
offset, offset,
@@ -286,15 +287,18 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
</IconButton> </IconButton>
</div> </div>
</Th> </Th>
{isSubOrganization && <Th>Managed By</Th>}
<Th className="w-16">{isFetching ? <Spinner size="xs" /> : null}</Th> <Th className="w-16">{isFetching ? <Spinner size="xs" /> : null}</Th>
</Tr> </Tr>
</THead> </THead>
<TBody> <TBody>
{isPending && <TableSkeleton columns={3} innerKey="org-identities" />} {isPending && (
<TableSkeleton columns={isSubOrganization ? 4 : 3} innerKey="org-identities" />
)}
{!isPending && {!isPending &&
data?.identities?.map( data?.identities?.map(
({ ({
identity: { id, name }, identity: { id, name, orgId },
role, role,
customRole, customRole,
lastLoginAuthMethod, lastLoginAuthMethod,
@@ -362,6 +366,14 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
}} }}
</OrgPermissionCan> </OrgPermissionCan>
</Td> </Td>
{isSubOrganization && (
<Td>
<p className="truncate">
<FontAwesomeIcon size="sm" className="mr-1.5" icon={faBuilding} />
{currentOrg.id === orgId ? "Organization" : "Root Organization"}
</p>
</Td>
)}
<Td> <Td>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@@ -91,7 +91,11 @@ const Page = () => {
<PageHeader scope="org" description="Organization Identity" title={data.identity.name} /> <PageHeader scope="org" description="Organization Identity" title={data.identity.name} />
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} /> <IdentityDetailsSection
isOrgIdentity={data.identity.orgId === currentOrg.id}
identityId={identityId}
handlePopUpOpen={handlePopUpOpen}
/>
{!isAuthHidden && ( {!isAuthHidden && (
<IdentityAuthenticationSection <IdentityAuthenticationSection
identityId={identityId} identityId={identityId}
@@ -21,7 +21,7 @@ import {
Tag, Tag,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useTimedReset } from "@app/hooks"; import { useTimedReset } from "@app/hooks";
import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api"; import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -39,6 +39,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({ const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
initialState: "Copy ID to clipboard" initialState: "Copy ID to clipboard"
}); });
const { isSubOrganization } = useOrganization();
const { data } = useGetIdentityById(identityId); const { data } = useGetIdentityById(identityId);
return data ? ( return data ? (
@@ -142,6 +143,14 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
<p className="text-sm font-medium text-mineshaft-300">Name</p> <p className="text-sm font-medium text-mineshaft-300">Name</p>
<p className="text-sm text-mineshaft-300">{data.identity.name}</p> <p className="text-sm text-mineshaft-300">{data.identity.name}</p>
</div> </div>
{isSubOrganization && (
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Manage By</p>
<p className="text-sm text-mineshaft-300">
{isOrgIdentity ? "Organization" : "Root Organization"}
</p>
</div>
)}
{isOrgIdentity && ( {isOrgIdentity && (
<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">Last Login Auth Method</p>
@@ -22,6 +22,7 @@ import {
import { useTimedReset } from "@app/hooks"; import { useTimedReset } from "@app/hooks";
import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api"; import { useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api";
import { SecretSharingAccessType } from "@app/hooks/api/secretSharing"; import { SecretSharingAccessType } from "@app/hooks/api/secretSharing";
import { useSearch } from "@tanstack/react-router";
// values in ms // values in ms
const expiresInOptions = [ const expiresInOptions = [
@@ -87,6 +88,10 @@ export const ShareSecretForm = ({
const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({ const [, isCopyingSecret, setCopyTextSecret] = useTimedReset<string>({
initialState: "Copy to clipboard" initialState: "Copy to clipboard"
}); });
const subOrganization = useSearch({
strict: false,
select: (el) => el?.subOrganization
});
const publicSharedSecretCreator = useCreatePublicSharedSecret(); const publicSharedSecretCreator = useCreatePublicSharedSecret();
const privateSharedSecretCreator = useCreateSharedSecret(); const privateSharedSecretCreator = useCreateSharedSecret();
@@ -148,11 +153,14 @@ export const ShareSecretForm = ({
type: "success" type: "success"
}); });
} else { } else {
const link = `${window.location.origin}/shared/secret/${id}`; const link = new URL(`${window.location.origin}/shared/secret/${id}`);
if (subOrganization) {
link.searchParams.set("subOrganization", subOrganization);
}
setSecretLink(link); setSecretLink(link.toString());
navigator.clipboard.writeText(link); navigator.clipboard.writeText(link.toString());
setCopyTextSecret("secret"); setCopyTextSecret("secret");
createNotification({ createNotification({