diff --git a/backend/src/ee/services/group/group-types.ts b/backend/src/ee/services/group/group-types.ts index 9424075ca..1d7c5fc71 100644 --- a/backend/src/ee/services/group/group-types.ts +++ b/backend/src/ee/services/group/group-types.ts @@ -42,6 +42,10 @@ export type TListGroupUsersDTO = { filter?: EFilterReturnedUsers; } & TGenericPermission; +export type TListProjectGroupUsersDTO = TListGroupUsersDTO & { + projectId: string; +}; + export type TAddUserToGroupDTO = { id: string; username: string; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 779707ed1..a10fcc67e 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -89,6 +89,7 @@ export const GROUPS = { limit: "The number of users to return.", username: "The username to search for.", search: "The text string that user email or name will be filtered by.", + projectId: "The ID of the project the group belongs to.", filterUsers: "Whether to filter the list of returned users. 'existingMembers' will only return existing users in the group, 'nonMembers' will only return users not in the group, undefined will return all users in the organization." }, diff --git a/backend/src/server/routes/v2/group-project-router.ts b/backend/src/server/routes/v2/group-project-router.ts index 5a081a3d9..d07e3bd8b 100644 --- a/backend/src/server/routes/v2/group-project-router.ts +++ b/backend/src/server/routes/v2/group-project-router.ts @@ -4,9 +4,11 @@ import { GroupProjectMembershipsSchema, GroupsSchema, ProjectMembershipRole, - ProjectUserMembershipRolesSchema + ProjectUserMembershipRolesSchema, + UsersSchema } from "@app/db/schemas"; -import { ApiDocsTags, PROJECTS } from "@app/lib/api-docs"; +import { EFilterReturnedUsers } from "@app/ee/services/group/group-types"; +import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs"; import { ms } from "@app/lib/ms"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -301,4 +303,61 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) => return { groupMembership }; } }); + + server.route({ + method: "GET", + url: "/:projectId/groups/:groupId/users", + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.ProjectGroups], + description: "Return project group users", + params: z.object({ + projectId: z.string().trim().describe(GROUPS.LIST_USERS.projectId), + groupId: z.string().trim().describe(GROUPS.LIST_USERS.id) + }), + querystring: z.object({ + offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset), + limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit), + username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username), + search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search), + filter: z.nativeEnum(EFilterReturnedUsers).optional().describe(GROUPS.LIST_USERS.filterUsers) + }), + response: { + 200: z.object({ + users: UsersSchema.pick({ + email: true, + username: true, + firstName: true, + lastName: true, + id: true + }) + .merge( + z.object({ + isPartOfGroup: z.boolean(), + joinedGroupAt: z.date().nullable() + }) + ) + .array(), + totalCount: z.number() + }) + } + }, + handler: async (req) => { + const { users, totalCount } = await server.services.groupProject.listProjectGroupUsers({ + id: req.params.groupId, + projectId: req.params.projectId, + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query + }); + + return { users, totalCount }; + } + }); }; diff --git a/backend/src/services/group-project/group-project-service.ts b/backend/src/services/group-project/group-project-service.ts index 1ff2c78d2..a793ecfab 100644 --- a/backend/src/services/group-project/group-project-service.ts +++ b/backend/src/services/group-project/group-project-service.ts @@ -1,6 +1,7 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas"; +import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types"; import { constructPermissionErrorMessage, validatePrivilegeChangeOperation @@ -42,7 +43,7 @@ type TGroupProjectServiceFactoryDep = { projectKeyDAL: Pick; projectRoleDAL: Pick; projectBotDAL: TProjectBotDALFactory; - groupDAL: Pick; + groupDAL: Pick; permissionService: Pick; }; @@ -471,11 +472,54 @@ export const groupProjectServiceFactory = ({ return groupMembership; }; + const listProjectGroupUsers = async ({ + id, + projectId, + offset, + limit, + username, + actor, + actorId, + actorAuthMethod, + actorOrgId, + search, + filter + }: TListProjectGroupUsersDTO) => { + const project = await projectDAL.findById(projectId); + + if (!project) { + throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` }); + } + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.Any + }); + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups); + + const { members, totalCount } = await groupDAL.findAllGroupPossibleMembers({ + orgId: project.orgId, + groupId: id, + offset, + limit, + username, + search, + filter + }); + + return { users: members, totalCount }; + }; + return { addGroupToProject, updateGroupInProject, removeGroupFromProject, listGroupsInProject, - getGroupInProject + getGroupInProject, + listProjectGroupUsers }; }; diff --git a/frontend/src/hooks/api/groups/queries.tsx b/frontend/src/hooks/api/groups/queries.tsx index eb10085bc..6fbe0ec13 100644 --- a/frontend/src/hooks/api/groups/queries.tsx +++ b/frontend/src/hooks/api/groups/queries.tsx @@ -21,7 +21,27 @@ export const groupKeys = { limit: number; search: string; filter?: EFilterReturnedUsers; - }) => [...groupKeys.forGroupUserMemberships(slug), { offset, limit, search, filter }] as const + }) => [...groupKeys.forGroupUserMemberships(slug), { offset, limit, search, filter }] as const, + specificProjectGroupUserMemberships: ({ + projectId, + slug, + offset, + limit, + search, + filter + }: { + slug: string; + projectId: string; + offset: number; + limit: number; + search: string; + filter?: EFilterReturnedUsers; + }) => + [ + ...groupKeys.forGroupUserMemberships(slug), + projectId, + { offset, limit, search, filter } + ] as const }; export const useGetGroupById = (groupId: string) => { @@ -80,3 +100,51 @@ export const useListGroupUsers = ({ } }); }; + +export const useListProjectGroupUsers = ({ + id, + projectId, + groupSlug, + offset = 0, + limit = 10, + search, + filter +}: { + id: string; + groupSlug: string; + projectId: string; + offset: number; + limit: number; + search: string; + filter?: EFilterReturnedUsers; +}) => { + return useQuery({ + queryKey: groupKeys.specificProjectGroupUserMemberships({ + slug: groupSlug, + projectId, + offset, + limit, + search, + filter + }), + enabled: Boolean(groupSlug), + placeholderData: (previousData) => previousData, + queryFn: async () => { + const params = new URLSearchParams({ + offset: String(offset), + limit: String(limit), + search, + ...(filter && { filter }) + }); + + const { data } = await apiRequest.get<{ users: TGroupUser[]; totalCount: number }>( + `/api/v2/workspace/${projectId}/groups/${id}/users`, + { + params + } + ); + + return data; + } + }); +}; diff --git a/frontend/src/hooks/api/workspace/mutations.tsx b/frontend/src/hooks/api/workspace/mutations.tsx index ea7376d3d..5cd7e3bbc 100644 --- a/frontend/src/hooks/api/workspace/mutations.tsx +++ b/frontend/src/hooks/api/workspace/mutations.tsx @@ -50,10 +50,13 @@ export const useUpdateGroupWorkspaceRole = () => { return groupMembership; }, - onSuccess: (_, { projectId }) => { + onSuccess: (_, { projectId, groupId }) => { queryClient.invalidateQueries({ queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectId) }); + queryClient.invalidateQueries({ + queryKey: workspaceKeys.getWorkspaceGroupMembershipDetails(projectId, groupId) + }); } }); }; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index c040a1267..2ae2bc463 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -691,6 +691,21 @@ export const useGetWorkspaceIdentityMembershipDetails = (projectId: string, iden }); }; +export const useGetWorkspaceGroupMembershipDetails = (projectId: string, groupId: string) => { + return useQuery({ + enabled: Boolean(projectId && groupId), + queryKey: workspaceKeys.getWorkspaceGroupMembershipDetails(projectId, groupId), + queryFn: async () => { + const { + data: { groupMembership } + } = await apiRequest.get<{ groupMembership: TGroupMembership }>( + `/api/v2/workspace/${projectId}/groups/${groupId}` + ); + return groupMembership; + } + }); +}; + export const useListWorkspaceGroups = (projectId: string) => { return useQuery({ queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectId), diff --git a/frontend/src/hooks/api/workspace/query-keys.tsx b/frontend/src/hooks/api/workspace/query-keys.tsx index c10616b63..15317d2fd 100644 --- a/frontend/src/hooks/api/workspace/query-keys.tsx +++ b/frontend/src/hooks/api/workspace/query-keys.tsx @@ -36,6 +36,8 @@ export const workspaceKeys = { searchWorkspace: (dto: TSearchProjectsDTO) => ["search-projects", dto] as const, getWorkspaceGroupMemberships: (workspaceId: string) => [{ workspaceId }, "workspace-groups"] as const, + getWorkspaceGroupMembershipDetails: (workspaceId: string, groupId: string) => + [{ workspaceId, groupId }, "workspace-group-membership-details"] as const, getWorkspaceCas: ({ projectSlug }: { projectSlug: string }) => [{ projectSlug }, "workspace-cas"] as const, specificWorkspaceCas: ({ projectSlug, status }: { projectSlug: string; status?: CaStatus }) => diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx index 6472c34c1..f56f852d4 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -152,7 +152,7 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props Email Added On - + diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx index 829ad2908..8ec279a9d 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx @@ -1,8 +1,17 @@ -import { faUserMinus } from "@fortawesome/free-solid-svg-icons"; +import { faEllipsisV, faUserMinus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { OrgPermissionCan } from "@app/components/permissions"; -import { IconButton, Td, Tooltip, Tr } from "@app/components/v2"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + IconButton, + Td, + Tooltip, + Tr +} from "@app/components/v2"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { useOidcManageGroupMembershipsEnabled } from "@app/hooks/api"; import { TGroupUser } from "@app/hooks/api/groups/types"; @@ -38,30 +47,47 @@ export const GroupMembershipRow = ({

{new Date(joinedGroupAt).toLocaleDateString()}

- - - {(isAllowed) => { - return ( - + + + + - handlePopUpOpen("removeMemberFromGroup", { username })} - variant="plain" - colorSchema="danger" - > - - - - ); - }} - + + + + + + {(isAllowed) => { + return ( + +
+ } + onClick={() => handlePopUpOpen("removeMemberFromGroup", { username })} + isDisabled={!isAllowed || isOidcManageGroupMembershipsEnabled} + > + Remove User From Group + +
+
+ ); + }} +
+
+ + ); diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx index 08a17148e..91b6d3186 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx @@ -3,6 +3,7 @@ import { Controller, useForm } from "react-hook-form"; import { faCheck, faClock, faEdit, faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; +import { PopperContentProps } from "@radix-ui/react-popper"; import { twMerge } from "tailwind-merge"; import { z } from "zod"; @@ -28,6 +29,7 @@ import { formatProjectRoleName } from "@app/helpers/roles"; import { usePopUp } from "@app/hooks"; import { useGetProjectRoles, useUpdateGroupWorkspaceRole } from "@app/hooks/api"; import { TGroupMembership } from "@app/hooks/api/groups/types"; +import { TProjectRole } from "@app/hooks/api/roles/types"; import { ProjectUserMembershipTemporaryMode } from "@app/hooks/api/workspace/types"; import { groupBy } from "@app/lib/fn/array"; @@ -196,33 +198,38 @@ type TForm = z.infer; export type TMemberRolesProp = { disableEdit?: boolean; groupId: string; + className?: string; roles: TGroupMembership["roles"]; + popperContentProps?: PopperContentProps; }; const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2; -export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMemberRolesProp) => { +type FormProps = { + projectRoles: Omit[] | undefined; + roles: TGroupMembership["roles"]; + groupId: string; + onClose: VoidFunction; +}; + +const GroupRolesForm = ({ projectRoles, roles, groupId, onClose }: FormProps) => { const { currentWorkspace } = useWorkspace(); - const { popUp, handlePopUpToggle } = usePopUp(["editRole"] as const); + const [searchRoles, setSearchRoles] = useState(""); + const userRolesGroupBySlug = groupBy(roles, ({ customRoleSlug, role }) => customRoleSlug || role); + + const updateGroupWorkspaceRole = useUpdateGroupWorkspaceRole(); + const { handleSubmit, control, - reset, setValue, formState: { isSubmitting, isDirty } } = useForm({ resolver: zodResolver(formSchema) }); - const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles( - currentWorkspace?.id ?? "" - ); - const userRolesGroupBySlug = groupBy(roles, ({ customRoleSlug, role }) => customRoleSlug || role); - - const updateGroupWorkspaceRole = useUpdateGroupWorkspaceRole(); - const handleRoleUpdate = async (data: TForm) => { const selectedRoles = Object.keys(data) .filter((el) => Boolean(data[el].isChecked)) @@ -253,7 +260,7 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember roles: selectedRoles }); createNotification({ text: "Successfully updated group role", type: "success" }); - handlePopUpToggle("editRole"); + onClose(); setSearchRoles(""); } catch { createNotification({ text: "Failed to update group role", type: "error" }); @@ -261,7 +268,120 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember }; return ( -
+
+
+ {projectRoles + ?.filter( + ({ name, slug }) => + name.toLowerCase().includes(searchRoles.toLowerCase()) || + slug.toLowerCase().includes(searchRoles.toLowerCase()) + ) + ?.map(({ id, name, slug }) => { + const userProjectRoleDetails = userRolesGroupBySlug?.[slug]?.[0]; + + return ( +
+
+ ( + { + field.onChange(isChecked); + setValue(`${slug}.temporaryAccess`, false); + }} + > + {name} + + )} + /> +
+
+ ( + { + setValue(`${slug}.isChecked`, true, { shouldDirty: true }); + field.onChange({ isTemporary: true, ...data }); + }} + onRemoveTemporary={() => { + setValue(`${slug}.isChecked`, false, { shouldDirty: true }); + field.onChange(false); + }} + /> + )} + /> +
+
+ ); + })} +
+
+
+ setSearchRoles(el.target.value)} + leftIcon={} + placeholder="Search roles.." + /> +
+
+ +
+
+
+ ); +}; + +export const GroupRoles = ({ + roles = [], + disableEdit = false, + groupId, + className, + popperContentProps +}: TMemberRolesProp) => { + const { currentWorkspace } = useWorkspace(); + const { popUp, handlePopUpToggle } = usePopUp(["editRole"] as const); + + const { data: projectRoles, isPending: isRolesLoading } = useGetProjectRoles( + currentWorkspace?.id ?? "" + ); + + return ( +
{roles .slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE) .map(({ role, customRoleName, id, isTemporary, temporaryAccessEndTime }) => { @@ -325,119 +445,32 @@ export const GroupRoles = ({ roles = [], disableEdit = false, groupId }: TMember open={popUp.editRole.isOpen} onOpenChange={(isOpen) => { handlePopUpToggle("editRole", isOpen); - reset(); }} > {!disableEdit && ( - + e.stopPropagation()}> )} - + e.stopPropagation()} + hideCloseBtn + className="pt-4" + > {isRolesLoading ? (
) : ( -
-
- {projectRoles - ?.filter( - ({ name, slug }) => - name.toLowerCase().includes(searchRoles.toLowerCase()) || - slug.toLowerCase().includes(searchRoles.toLowerCase()) - ) - ?.map(({ id, name, slug }) => { - const userProjectRoleDetails = userRolesGroupBySlug?.[slug]?.[0]; - - return ( -
-
- ( - { - field.onChange(isChecked); - setValue(`${slug}.temporaryAccess`, false); - }} - > - {name} - - )} - /> -
-
- ( - { - setValue(`${slug}.isChecked`, true, { shouldDirty: true }); - field.onChange({ isTemporary: true, ...data }); - }} - onRemoveTemporary={() => { - setValue(`${slug}.isChecked`, false, { shouldDirty: true }); - field.onChange(false); - }} - /> - )} - /> -
-
- ); - })} -
-
-
- setSearchRoles(el.target.value)} - leftIcon={} - placeholder="Search roles.." - /> -
-
- -
-
-
+ handlePopUpToggle("editRole")} + /> )}
diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx index 8f06576e2..ff2c53aca 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx @@ -8,6 +8,7 @@ import { faUsers } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; import { format } from "date-fns"; import { ProjectPermissionCan } from "@app/components/permissions"; @@ -55,6 +56,7 @@ enum GroupsOrderBy { export const GroupTable = ({ handlePopUpOpen }: Props) => { const { currentWorkspace } = useWorkspace(); + const navigate = useNavigate(); const { search, @@ -143,7 +145,32 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { .slice(offset, perPage * page) .map(({ group: { id, name }, roles, createdAt }) => { return ( - + { + if (evt.key === "Enter") { + navigate({ + to: `/${currentWorkspace.type}/$projectId/groups/$groupId` as const, + params: { + projectId: currentWorkspace.id, + groupId: id + } + }); + } + }} + onClick={() => + navigate({ + to: `/${currentWorkspace.type}/$projectId/groups/$groupId` as const, + params: { + projectId: currentWorkspace.id, + groupId: id + } + }) + } + > {name} {
{ + onClick={(e) => { + e.stopPropagation(); handlePopUpOpen("deleteGroup", { id, name diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx new file mode 100644 index 000000000..a3dfabcd0 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx @@ -0,0 +1,70 @@ +import { Helmet } from "react-helmet"; +import { useTranslation } from "react-i18next"; +import { useParams } from "@tanstack/react-router"; + +import { ProjectPermissionCan } from "@app/components/permissions"; +import { EmptyState, PageHeader, Spinner } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { useGetWorkspaceGroupMembershipDetails } from "@app/hooks/api/workspace/queries"; + +import { GroupDetailsSection } from "./components/GroupDetailsSection"; +import { GroupMembersSection } from "./components/GroupMembersSection"; + +const Page = () => { + const groupId = useParams({ + strict: false, + select: (el) => el.groupId as string + }); + + const { currentWorkspace } = useWorkspace(); + + const { data: groupMembership, isPending } = useGetWorkspaceGroupMembershipDetails( + currentWorkspace.id, + groupId + ); + + if (isPending) + return ( +
+ +
+ ); + + return ( +
+ {groupMembership ? ( +
+ +
+
+ +
+ +
+
+ ) : ( + + )} +
+ ); +}; + +export const GroupDetailsByIDPage = () => { + const { t } = useTranslation(); + return ( + <> + + {t("common.head-title", { title: "Project Group" })} + + + + + + + ); +}; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx new file mode 100644 index 000000000..4f1dcb586 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx @@ -0,0 +1,152 @@ +import { faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; +import { format } from "date-fns"; + +import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; +import { + DeleteActionModal, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + IconButton +} from "@app/components/v2"; +import { CopyButton } from "@app/components/v2/CopyButton"; +import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { usePopUp } from "@app/hooks"; +import { useDeleteGroupFromWorkspace } from "@app/hooks/api"; +import { TGroupMembership } from "@app/hooks/api/groups/types"; +import { GroupRoles } from "@app/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles"; + +type Props = { + groupMembership: TGroupMembership; +}; + +export const GroupDetailsSection = ({ groupMembership }: Props) => { + const { handlePopUpToggle, popUp, handlePopUpClose, handlePopUpOpen } = usePopUp([ + "deleteGroup" + ] as const); + + const { mutateAsync: deleteMutateAsync } = useDeleteGroupFromWorkspace(); + const { currentWorkspace } = useWorkspace(); + const navigate = useNavigate(); + + const onRemoveGroupSubmit = async () => { + try { + await deleteMutateAsync({ + groupId: groupMembership.group.id, + projectId: currentWorkspace.id + }); + + createNotification({ + text: "Successfully removed group from project", + type: "success" + }); + + navigate({ + to: `/${currentWorkspace.type}/${currentWorkspace.id}/access-management?selectedTab=groups` + }); + + handlePopUpClose("deleteGroup"); + } catch (err) { + console.error(err); + const error = err as any; + const text = error?.response?.data?.message ?? "Failed to remove group from project"; + + createNotification({ + text, + type: "error" + }); + } + }; + + return ( +
+
+

Group Details

+ + + + + + + + + {(isAllowed) => { + return ( + } + onClick={() => handlePopUpOpen("deleteGroup")} + isDisabled={!isAllowed} + > + Remove Group From Project + + ); + }} + + + +
+
+
+

Group ID

+
+

{groupMembership.group.id}

+ +
+
+
+

Name

+

{groupMembership.group.name}

+
+
+

Slug

+
+

{groupMembership.group.slug}

+ +
+
+
+

Project Role

+ + {(isAllowed) => ( + + )} + +
+
+

Assigned to Project

+

+ {format(groupMembership.createdAt, "M/d/yyyy")} +

+
+
+ handlePopUpToggle("deleteGroup", isOpen)} + deleteKey="confirm" + buttonText="Remove" + onDeleteApproved={onRemoveGroupSubmit} + /> +
+ ); +}; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx new file mode 100644 index 000000000..0be3a9b01 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx @@ -0,0 +1,20 @@ +import { TGroupMembership } from "@app/hooks/api/groups/types"; + +import { GroupMembersTable } from "./GroupMembersTable"; + +type Props = { + groupMembership: TGroupMembership; +}; + +export const GroupMembersSection = ({ groupMembership }: Props) => { + return ( +
+
+

Group Members

+
+
+ +
+
+ ); +}; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx new file mode 100644 index 000000000..c0639260a --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -0,0 +1,235 @@ +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faFolder, + faMagnifyingGlass, + faSearch +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { + ConfirmActionModal, + EmptyState, + IconButton, + Input, + Pagination, + Table, + TableContainer, + TableSkeleton, + TBody, + Th, + THead, + Tr +} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; +import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; +import { useAssumeProjectPrivileges } from "@app/hooks/api"; +import { ActorType } from "@app/hooks/api/auditLogs/enums"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; +import { useListProjectGroupUsers } from "@app/hooks/api/groups/queries"; +import { EFilterReturnedUsers, TGroupMembership } from "@app/hooks/api/groups/types"; +import { ProjectType } from "@app/hooks/api/workspace/types"; + +import { GroupMembershipRow } from "./GroupMembershipRow"; + +type Props = { + groupMembership: TGroupMembership; +}; + +enum GroupMembersOrderBy { + Name = "name" +} + +export const GroupMembersTable = ({ groupMembership }: Props) => { + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + toggleOrderDirection + } = usePagination(GroupMembersOrderBy.Name, { + initPerPage: getUserTablePreference("projectGroupMembersTable", PreferenceKey.PerPage, 20) + }); + + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["assumePrivileges"] as const); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("projectGroupMembersTable", PreferenceKey.PerPage, newPerPage); + }; + + const { currentWorkspace } = useWorkspace(); + + const { data: groupMemberships, isPending } = useListProjectGroupUsers({ + id: groupMembership.group.id, + groupSlug: groupMembership.group.slug, + projectId: currentWorkspace.id, + offset, + limit: perPage, + search, + filter: EFilterReturnedUsers.EXISTING_MEMBERS + }); + + const filteredGroupMemberships = useMemo(() => { + return groupMemberships && groupMemberships?.users + ? groupMemberships?.users + ?.filter((membership) => { + const userSearchString = `${membership.firstName && membership.firstName} ${ + membership.lastName && membership.lastName + } ${membership.email && membership.email} ${ + membership.username && membership.username + }`; + return userSearchString.toLowerCase().includes(search.trim().toLowerCase()); + }) + .sort((a, b) => { + const [membershipOne, membershipTwo] = + orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + const membershipOneComparisonString = membershipOne.firstName + ? membershipOne.firstName + : membershipOne.email; + + const membershipTwoComparisonString = membershipTwo.firstName + ? membershipTwo.firstName + : membershipTwo.email; + + const comparison = membershipOneComparisonString + .toLowerCase() + .localeCompare(membershipTwoComparisonString.toLowerCase()); + + return comparison; + }) + : []; + }, [groupMemberships, orderDirection, search]); + + useResetPageHelper({ + totalCount: filteredGroupMemberships?.length, + offset, + setPage + }); + + const assumePrivileges = useAssumeProjectPrivileges(); + + const handleAssumePrivileges = async () => { + const { userId } = popUp?.assumePrivileges?.data as { userId: string }; + assumePrivileges.mutate( + { + actorId: userId, + actorType: ActorType.USER, + projectId: currentWorkspace.id + }, + { + onSuccess: () => { + createNotification({ + type: "success", + text: "User privilege assumption has started" + }); + + let overviewPage: string; + + switch (currentWorkspace.type) { + case ProjectType.SecretScanning: + overviewPage = "data-sources"; + break; + case ProjectType.CertificateManager: + overviewPage = "subscribers"; + break; + default: + overviewPage = "overview"; + } + + window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/${overviewPage}`; + } + } + ); + }; + + return ( +
+ setSearch(e.target.value)} + leftIcon={} + placeholder="Search users..." + /> + + + + + + + + + + + {isPending && } + {!isPending && + filteredGroupMemberships.slice(offset, perPage * page).map((userGroupMembership) => { + return ( + handlePopUpOpen("assumePrivileges", { userId })} + /> + ); + })} + +
+
+ Name + + + +
+
EmailAdded On +
+ {Boolean(filteredGroupMemberships.length) && ( + + )} + {!isPending && !filteredGroupMemberships?.length && ( + + )} +
+ handlePopUpToggle("assumePrivileges", isOpen)} + onConfirmed={handleAssumePrivileges} + buttonText="Confirm" + /> +
+ ); +}; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx new file mode 100644 index 000000000..55f566477 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx @@ -0,0 +1,76 @@ +import { faEllipsisV, faUser } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { ProjectPermissionCan } from "@app/components/permissions"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + IconButton, + Td, + Tooltip, + Tr +} from "@app/components/v2"; +import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/context"; +import { TGroupUser } from "@app/hooks/api/groups/types"; + +type Props = { + user: TGroupUser; + onAssumePrivileges: (userId: string) => void; +}; + +export const GroupMembershipRow = ({ + user: { firstName, lastName, joinedGroupAt, email, id }, + onAssumePrivileges +}: Props) => { + return ( + + +

{`${firstName ?? "-"} ${lastName ?? ""}`}

+ + +

{email}

+ + + +

{new Date(joinedGroupAt).toLocaleDateString()}

+
+ + + + + + + + + + + + {(isAllowed) => { + return ( + } + onClick={() => onAssumePrivileges(id)} + isDisabled={!isAllowed} + > + Assume Privileges + + ); + }} + + + + + + + ); +}; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/index.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/index.tsx new file mode 100644 index 000000000..70c696609 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/index.tsx @@ -0,0 +1 @@ +export { GroupMembersSection } from "./GroupMembersSection"; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/index.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/index.tsx new file mode 100644 index 000000000..003c47910 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/index.tsx @@ -0,0 +1 @@ +export { GroupDetailsSection } from "./GroupDetailsSection"; diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route-cert-manager.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route-cert-manager.tsx new file mode 100644 index 000000000..2663d75b1 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route-cert-manager.tsx @@ -0,0 +1,28 @@ +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId" +)({ + component: GroupDetailsByIDPage, + beforeLoad: ({ context, params }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/cert-manager/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Groups" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route-kms.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route-kms.tsx new file mode 100644 index 000000000..6f4b5a8a0 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route-kms.tsx @@ -0,0 +1,28 @@ +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId" +)({ + component: GroupDetailsByIDPage, + beforeLoad: ({ context, params }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/kms/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Groups" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx new file mode 100644 index 000000000..30fb82048 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx @@ -0,0 +1,28 @@ +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId" +)({ + component: GroupDetailsByIDPage, + beforeLoad: ({ context, params }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/secret-manager/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Groups" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-scanning.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-scanning.tsx new file mode 100644 index 000000000..0c3d7c998 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-scanning.tsx @@ -0,0 +1,28 @@ +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId" +)({ + component: GroupDetailsByIDPage, + beforeLoad: ({ context, params }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/secret-scanning/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Groups" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route-ssh.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route-ssh.tsx new file mode 100644 index 000000000..3aa9338fd --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route-ssh.tsx @@ -0,0 +1,28 @@ +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId" +)({ + component: GroupDetailsByIDPage, + beforeLoad: ({ context, params }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/ssh/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Groups" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route.tsx new file mode 100644 index 000000000..83ead0221 --- /dev/null +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route.tsx @@ -0,0 +1,27 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId" +)({ + component: GroupDetailsByIDPage, + context: () => ({ + breadcrumbs: [ + { + label: "Home", + icon: () => , + link: linkOptions({ to: "/organization/secret-manager/overview" }) + }, + { + label: "Access Control", + link: linkOptions({ to: "/organization/access-management" }) + }, + { + label: "groups" + } + ] + }) +}); diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 449ea28cb..9496230af 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -115,19 +115,24 @@ import { Route as certManagerAlertingPageRouteImport } from './pages/cert-manage import { Route as projectRoleDetailsBySlugPageRouteSshImport } from './pages/project/RoleDetailsBySlugPage/route-ssh' import { Route as projectMemberDetailsByIDPageRouteSshImport } from './pages/project/MemberDetailsByIDPage/route-ssh' import { Route as projectIdentityDetailsByIDPageRouteSshImport } from './pages/project/IdentityDetailsByIDPage/route-ssh' +import { Route as projectGroupDetailsByIDPageRouteSshImport } from './pages/project/GroupDetailsByIDPage/route-ssh' import { Route as projectRoleDetailsBySlugPageRouteSecretScanningImport } from './pages/project/RoleDetailsBySlugPage/route-secret-scanning' import { Route as projectMemberDetailsByIDPageRouteSecretScanningImport } from './pages/project/MemberDetailsByIDPage/route-secret-scanning' import { Route as projectIdentityDetailsByIDPageRouteSecretScanningImport } from './pages/project/IdentityDetailsByIDPage/route-secret-scanning' +import { Route as projectGroupDetailsByIDPageRouteSecretScanningImport } from './pages/project/GroupDetailsByIDPage/route-secret-scanning' import { Route as projectRoleDetailsBySlugPageRouteSecretManagerImport } from './pages/project/RoleDetailsBySlugPage/route-secret-manager' import { Route as projectMemberDetailsByIDPageRouteSecretManagerImport } from './pages/project/MemberDetailsByIDPage/route-secret-manager' import { Route as projectIdentityDetailsByIDPageRouteSecretManagerImport } from './pages/project/IdentityDetailsByIDPage/route-secret-manager' +import { Route as projectGroupDetailsByIDPageRouteSecretManagerImport } from './pages/project/GroupDetailsByIDPage/route-secret-manager' import { Route as projectRoleDetailsBySlugPageRouteKmsImport } from './pages/project/RoleDetailsBySlugPage/route-kms' import { Route as projectMemberDetailsByIDPageRouteKmsImport } from './pages/project/MemberDetailsByIDPage/route-kms' import { Route as projectIdentityDetailsByIDPageRouteKmsImport } from './pages/project/IdentityDetailsByIDPage/route-kms' +import { Route as projectGroupDetailsByIDPageRouteKmsImport } from './pages/project/GroupDetailsByIDPage/route-kms' import { Route as projectRoleDetailsBySlugPageRouteCertManagerImport } from './pages/project/RoleDetailsBySlugPage/route-cert-manager' import { Route as certManagerPkiCollectionDetailsByIDPageRoutesImport } from './pages/cert-manager/PkiCollectionDetailsByIDPage/routes' import { Route as projectMemberDetailsByIDPageRouteCertManagerImport } from './pages/project/MemberDetailsByIDPage/route-cert-manager' import { Route as projectIdentityDetailsByIDPageRouteCertManagerImport } from './pages/project/IdentityDetailsByIDPage/route-cert-manager' +import { Route as projectGroupDetailsByIDPageRouteCertManagerImport } from './pages/project/GroupDetailsByIDPage/route-cert-manager' import { Route as sshSshHostGroupDetailsByIDPageRouteImport } from './pages/ssh/SshHostGroupDetailsByIDPage/route' import { Route as sshSshCaByIDPageRouteImport } from './pages/ssh/SshCaByIDPage/route' import { Route as secretManagerSecretDashboardPageRouteImport } from './pages/secret-manager/SecretDashboardPage/route' @@ -1149,6 +1154,13 @@ const projectIdentityDetailsByIDPageRouteSshRoute = getParentRoute: () => sshLayoutRoute, } as any) +const projectGroupDetailsByIDPageRouteSshRoute = + projectGroupDetailsByIDPageRouteSshImport.update({ + id: '/groups/$groupId', + path: '/groups/$groupId', + getParentRoute: () => sshLayoutRoute, + } as any) + const projectRoleDetailsBySlugPageRouteSecretScanningRoute = projectRoleDetailsBySlugPageRouteSecretScanningImport.update({ id: '/roles/$roleSlug', @@ -1170,6 +1182,13 @@ const projectIdentityDetailsByIDPageRouteSecretScanningRoute = getParentRoute: () => secretScanningLayoutRoute, } as any) +const projectGroupDetailsByIDPageRouteSecretScanningRoute = + projectGroupDetailsByIDPageRouteSecretScanningImport.update({ + id: '/groups/$groupId', + path: '/groups/$groupId', + getParentRoute: () => secretScanningLayoutRoute, + } as any) + const projectRoleDetailsBySlugPageRouteSecretManagerRoute = projectRoleDetailsBySlugPageRouteSecretManagerImport.update({ id: '/roles/$roleSlug', @@ -1191,6 +1210,13 @@ const projectIdentityDetailsByIDPageRouteSecretManagerRoute = getParentRoute: () => secretManagerLayoutRoute, } as any) +const projectGroupDetailsByIDPageRouteSecretManagerRoute = + projectGroupDetailsByIDPageRouteSecretManagerImport.update({ + id: '/groups/$groupId', + path: '/groups/$groupId', + getParentRoute: () => secretManagerLayoutRoute, + } as any) + const projectRoleDetailsBySlugPageRouteKmsRoute = projectRoleDetailsBySlugPageRouteKmsImport.update({ id: '/roles/$roleSlug', @@ -1212,6 +1238,13 @@ const projectIdentityDetailsByIDPageRouteKmsRoute = getParentRoute: () => kmsLayoutRoute, } as any) +const projectGroupDetailsByIDPageRouteKmsRoute = + projectGroupDetailsByIDPageRouteKmsImport.update({ + id: '/groups/$groupId', + path: '/groups/$groupId', + getParentRoute: () => kmsLayoutRoute, + } as any) + const projectRoleDetailsBySlugPageRouteCertManagerRoute = projectRoleDetailsBySlugPageRouteCertManagerImport.update({ id: '/roles/$roleSlug', @@ -1240,6 +1273,13 @@ const projectIdentityDetailsByIDPageRouteCertManagerRoute = getParentRoute: () => certManagerLayoutRoute, } as any) +const projectGroupDetailsByIDPageRouteCertManagerRoute = + projectGroupDetailsByIDPageRouteCertManagerImport.update({ + id: '/groups/$groupId', + path: '/groups/$groupId', + getParentRoute: () => certManagerLayoutRoute, + } as any) + const sshSshHostGroupDetailsByIDPageRouteRoute = sshSshHostGroupDetailsByIDPageRouteImport.update({ id: '/ssh-host-groups/$sshHostGroupId', @@ -2861,6 +2901,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof sshSshHostGroupDetailsByIDPageRouteImport parentRoute: typeof sshLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId': { + id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId' + path: '/groups/$groupId' + fullPath: '/cert-manager/$projectId/groups/$groupId' + preLoaderRoute: typeof projectGroupDetailsByIDPageRouteCertManagerImport + parentRoute: typeof certManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': { id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId' path: '/identities/$identityId' @@ -2889,6 +2936,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteCertManagerImport parentRoute: typeof certManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId': { + id: '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId' + path: '/groups/$groupId' + fullPath: '/kms/$projectId/groups/$groupId' + preLoaderRoute: typeof projectGroupDetailsByIDPageRouteKmsImport + parentRoute: typeof kmsLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/identities/$identityId': { id: '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/identities/$identityId' path: '/identities/$identityId' @@ -2910,6 +2964,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteKmsImport parentRoute: typeof kmsLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId': { + id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId' + path: '/groups/$groupId' + fullPath: '/secret-manager/$projectId/groups/$groupId' + preLoaderRoute: typeof projectGroupDetailsByIDPageRouteSecretManagerImport + parentRoute: typeof secretManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/identities/$identityId': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/identities/$identityId' path: '/identities/$identityId' @@ -2931,6 +2992,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteSecretManagerImport parentRoute: typeof secretManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId': { + id: '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId' + path: '/groups/$groupId' + fullPath: '/secret-scanning/$projectId/groups/$groupId' + preLoaderRoute: typeof projectGroupDetailsByIDPageRouteSecretScanningImport + parentRoute: typeof secretScanningLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId': { id: '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId' path: '/identities/$identityId' @@ -2952,6 +3020,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectRoleDetailsBySlugPageRouteSecretScanningImport parentRoute: typeof secretScanningLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId': { + id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId' + path: '/groups/$groupId' + fullPath: '/ssh/$projectId/groups/$groupId' + preLoaderRoute: typeof projectGroupDetailsByIDPageRouteSshImport + parentRoute: typeof sshLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/identities/$identityId': { id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/identities/$identityId' path: '/identities/$identityId' @@ -3735,6 +3810,7 @@ interface certManagerLayoutRouteChildren { AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute + projectGroupDetailsByIDPageRouteCertManagerRoute: typeof projectGroupDetailsByIDPageRouteCertManagerRoute projectIdentityDetailsByIDPageRouteCertManagerRoute: typeof projectIdentityDetailsByIDPageRouteCertManagerRoute projectMemberDetailsByIDPageRouteCertManagerRoute: typeof projectMemberDetailsByIDPageRouteCertManagerRoute certManagerPkiCollectionDetailsByIDPageRoutesRoute: typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute @@ -3755,6 +3831,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren, certManagerCertAuthDetailsByIDPageRouteRoute: certManagerCertAuthDetailsByIDPageRouteRoute, + projectGroupDetailsByIDPageRouteCertManagerRoute: + projectGroupDetailsByIDPageRouteCertManagerRoute, projectIdentityDetailsByIDPageRouteCertManagerRoute: projectIdentityDetailsByIDPageRouteCertManagerRoute, projectMemberDetailsByIDPageRouteCertManagerRoute: @@ -3787,6 +3865,7 @@ interface kmsLayoutRouteChildren { kmsOverviewPageRouteRoute: typeof kmsOverviewPageRouteRoute kmsSettingsPageRouteRoute: typeof kmsSettingsPageRouteRoute projectAccessControlPageRouteKmsRoute: typeof projectAccessControlPageRouteKmsRoute + projectGroupDetailsByIDPageRouteKmsRoute: typeof projectGroupDetailsByIDPageRouteKmsRoute projectIdentityDetailsByIDPageRouteKmsRoute: typeof projectIdentityDetailsByIDPageRouteKmsRoute projectMemberDetailsByIDPageRouteKmsRoute: typeof projectMemberDetailsByIDPageRouteKmsRoute projectRoleDetailsBySlugPageRouteKmsRoute: typeof projectRoleDetailsBySlugPageRouteKmsRoute @@ -3797,6 +3876,8 @@ const kmsLayoutRouteChildren: kmsLayoutRouteChildren = { kmsOverviewPageRouteRoute: kmsOverviewPageRouteRoute, kmsSettingsPageRouteRoute: kmsSettingsPageRouteRoute, projectAccessControlPageRouteKmsRoute: projectAccessControlPageRouteKmsRoute, + projectGroupDetailsByIDPageRouteKmsRoute: + projectGroupDetailsByIDPageRouteKmsRoute, projectIdentityDetailsByIDPageRouteKmsRoute: projectIdentityDetailsByIDPageRouteKmsRoute, projectMemberDetailsByIDPageRouteKmsRoute: @@ -4078,6 +4159,7 @@ interface secretManagerLayoutRouteChildren { projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren secretManagerSecretDashboardPageRouteRoute: typeof secretManagerSecretDashboardPageRouteRoute + projectGroupDetailsByIDPageRouteSecretManagerRoute: typeof projectGroupDetailsByIDPageRouteSecretManagerRoute projectIdentityDetailsByIDPageRouteSecretManagerRoute: typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute projectMemberDetailsByIDPageRouteSecretManagerRoute: typeof projectMemberDetailsByIDPageRouteSecretManagerRoute projectRoleDetailsBySlugPageRouteSecretManagerRoute: typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute @@ -4098,6 +4180,8 @@ const secretManagerLayoutRouteChildren: secretManagerLayoutRouteChildren = { AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren, secretManagerSecretDashboardPageRouteRoute: secretManagerSecretDashboardPageRouteRoute, + projectGroupDetailsByIDPageRouteSecretManagerRoute: + projectGroupDetailsByIDPageRouteSecretManagerRoute, projectIdentityDetailsByIDPageRouteSecretManagerRoute: projectIdentityDetailsByIDPageRouteSecretManagerRoute, projectMemberDetailsByIDPageRouteSecretManagerRoute: @@ -4146,6 +4230,7 @@ interface secretScanningLayoutRouteChildren { secretScanningSettingsPageRouteRoute: typeof secretScanningSettingsPageRouteRoute projectAccessControlPageRouteSecretScanningRoute: typeof projectAccessControlPageRouteSecretScanningRoute AuthenticateInjectOrgDetailsOrgLayoutSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren + projectGroupDetailsByIDPageRouteSecretScanningRoute: typeof projectGroupDetailsByIDPageRouteSecretScanningRoute projectIdentityDetailsByIDPageRouteSecretScanningRoute: typeof projectIdentityDetailsByIDPageRouteSecretScanningRoute projectMemberDetailsByIDPageRouteSecretScanningRoute: typeof projectMemberDetailsByIDPageRouteSecretScanningRoute projectRoleDetailsBySlugPageRouteSecretScanningRoute: typeof projectRoleDetailsBySlugPageRouteSecretScanningRoute @@ -4159,6 +4244,8 @@ const secretScanningLayoutRouteChildren: secretScanningLayoutRouteChildren = { projectAccessControlPageRouteSecretScanningRoute, AuthenticateInjectOrgDetailsOrgLayoutSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: AuthenticateInjectOrgDetailsOrgLayoutSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren, + projectGroupDetailsByIDPageRouteSecretScanningRoute: + projectGroupDetailsByIDPageRouteSecretScanningRoute, projectIdentityDetailsByIDPageRouteSecretScanningRoute: projectIdentityDetailsByIDPageRouteSecretScanningRoute, projectMemberDetailsByIDPageRouteSecretScanningRoute: @@ -4192,6 +4279,7 @@ interface sshLayoutRouteChildren { projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute sshSshHostGroupDetailsByIDPageRouteRoute: typeof sshSshHostGroupDetailsByIDPageRouteRoute + projectGroupDetailsByIDPageRouteSshRoute: typeof projectGroupDetailsByIDPageRouteSshRoute projectIdentityDetailsByIDPageRouteSshRoute: typeof projectIdentityDetailsByIDPageRouteSshRoute projectMemberDetailsByIDPageRouteSshRoute: typeof projectMemberDetailsByIDPageRouteSshRoute projectRoleDetailsBySlugPageRouteSshRoute: typeof projectRoleDetailsBySlugPageRouteSshRoute @@ -4206,6 +4294,8 @@ const sshLayoutRouteChildren: sshLayoutRouteChildren = { sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute, sshSshHostGroupDetailsByIDPageRouteRoute: sshSshHostGroupDetailsByIDPageRouteRoute, + projectGroupDetailsByIDPageRouteSshRoute: + projectGroupDetailsByIDPageRouteSshRoute, projectIdentityDetailsByIDPageRouteSshRoute: projectIdentityDetailsByIDPageRouteSshRoute, projectMemberDetailsByIDPageRouteSshRoute: @@ -4561,19 +4651,24 @@ export interface FileRoutesByFullPath { '/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute '/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute '/ssh/$projectId/ssh-host-groups/$sshHostGroupId': typeof sshSshHostGroupDetailsByIDPageRouteRoute + '/cert-manager/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteCertManagerRoute '/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute '/cert-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute '/cert-manager/$projectId/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute '/cert-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteCertManagerRoute + '/kms/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteKmsRoute '/kms/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteKmsRoute '/kms/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteKmsRoute '/kms/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteKmsRoute + '/secret-manager/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSecretManagerRoute '/secret-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute '/secret-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute '/secret-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute + '/secret-scanning/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSecretScanningRoute '/secret-scanning/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretScanningRoute '/secret-scanning/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretScanningRoute '/secret-scanning/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretScanningRoute + '/ssh/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSshRoute '/ssh/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSshRoute '/ssh/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSshRoute '/ssh/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSshRoute @@ -4764,19 +4859,24 @@ export interface FileRoutesByTo { '/secret-manager/$projectId/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute '/ssh/$projectId/ca/$caId': typeof sshSshCaByIDPageRouteRoute '/ssh/$projectId/ssh-host-groups/$sshHostGroupId': typeof sshSshHostGroupDetailsByIDPageRouteRoute + '/cert-manager/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteCertManagerRoute '/cert-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute '/cert-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute '/cert-manager/$projectId/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute '/cert-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteCertManagerRoute + '/kms/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteKmsRoute '/kms/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteKmsRoute '/kms/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteKmsRoute '/kms/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteKmsRoute + '/secret-manager/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSecretManagerRoute '/secret-manager/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute '/secret-manager/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute '/secret-manager/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute + '/secret-scanning/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSecretScanningRoute '/secret-scanning/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretScanningRoute '/secret-scanning/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretScanningRoute '/secret-scanning/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretScanningRoute + '/ssh/$projectId/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSshRoute '/ssh/$projectId/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSshRoute '/ssh/$projectId/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSshRoute '/ssh/$projectId/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSshRoute @@ -4990,19 +5090,24 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug': typeof secretManagerSecretDashboardPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ca/$caId': typeof sshSshCaByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId': typeof sshSshHostGroupDetailsByIDPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId': typeof projectGroupDetailsByIDPageRouteCertManagerRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteCertManagerRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteCertManagerRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId': typeof certManagerPkiCollectionDetailsByIDPageRoutesRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteCertManagerRoute + '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId': typeof projectGroupDetailsByIDPageRouteKmsRoute '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteKmsRoute '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteKmsRoute '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteKmsRoute + '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretManagerRoute + '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSecretScanningRoute '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSecretScanningRoute '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSecretScanningRoute '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSecretScanningRoute + '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId': typeof projectGroupDetailsByIDPageRouteSshRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/identities/$identityId': typeof projectIdentityDetailsByIDPageRouteSshRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/members/$membershipId': typeof projectMemberDetailsByIDPageRouteSshRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/roles/$roleSlug': typeof projectRoleDetailsBySlugPageRouteSshRoute @@ -5207,19 +5312,24 @@ export interface FileRouteTypes { | '/secret-manager/$projectId/secrets/$envSlug' | '/ssh/$projectId/ca/$caId' | '/ssh/$projectId/ssh-host-groups/$sshHostGroupId' + | '/cert-manager/$projectId/groups/$groupId' | '/cert-manager/$projectId/identities/$identityId' | '/cert-manager/$projectId/members/$membershipId' | '/cert-manager/$projectId/pki-collections/$collectionId' | '/cert-manager/$projectId/roles/$roleSlug' + | '/kms/$projectId/groups/$groupId' | '/kms/$projectId/identities/$identityId' | '/kms/$projectId/members/$membershipId' | '/kms/$projectId/roles/$roleSlug' + | '/secret-manager/$projectId/groups/$groupId' | '/secret-manager/$projectId/identities/$identityId' | '/secret-manager/$projectId/members/$membershipId' | '/secret-manager/$projectId/roles/$roleSlug' + | '/secret-scanning/$projectId/groups/$groupId' | '/secret-scanning/$projectId/identities/$identityId' | '/secret-scanning/$projectId/members/$membershipId' | '/secret-scanning/$projectId/roles/$roleSlug' + | '/ssh/$projectId/groups/$groupId' | '/ssh/$projectId/identities/$identityId' | '/ssh/$projectId/members/$membershipId' | '/ssh/$projectId/roles/$roleSlug' @@ -5409,19 +5519,24 @@ export interface FileRouteTypes { | '/secret-manager/$projectId/secrets/$envSlug' | '/ssh/$projectId/ca/$caId' | '/ssh/$projectId/ssh-host-groups/$sshHostGroupId' + | '/cert-manager/$projectId/groups/$groupId' | '/cert-manager/$projectId/identities/$identityId' | '/cert-manager/$projectId/members/$membershipId' | '/cert-manager/$projectId/pki-collections/$collectionId' | '/cert-manager/$projectId/roles/$roleSlug' + | '/kms/$projectId/groups/$groupId' | '/kms/$projectId/identities/$identityId' | '/kms/$projectId/members/$membershipId' | '/kms/$projectId/roles/$roleSlug' + | '/secret-manager/$projectId/groups/$groupId' | '/secret-manager/$projectId/identities/$identityId' | '/secret-manager/$projectId/members/$membershipId' | '/secret-manager/$projectId/roles/$roleSlug' + | '/secret-scanning/$projectId/groups/$groupId' | '/secret-scanning/$projectId/identities/$identityId' | '/secret-scanning/$projectId/members/$membershipId' | '/secret-scanning/$projectId/roles/$roleSlug' + | '/ssh/$projectId/groups/$groupId' | '/ssh/$projectId/identities/$identityId' | '/ssh/$projectId/members/$membershipId' | '/ssh/$projectId/roles/$roleSlug' @@ -5633,19 +5748,24 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ca/$caId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId' + | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/members/$membershipId' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/roles/$roleSlug' + | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/identities/$identityId' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/members/$membershipId' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/roles/$roleSlug' + | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/identities/$identityId' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/members/$membershipId' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug' + | '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId' | '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/members/$membershipId' | '/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/roles/$roleSlug' + | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/identities/$identityId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/members/$membershipId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/roles/$roleSlug' @@ -6206,6 +6326,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName", + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/members/$membershipId", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId", @@ -6220,6 +6341,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview", "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/settings", "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/identities/$identityId", "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/members/$membershipId", "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/roles/$roleSlug" @@ -6237,6 +6359,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management", "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations", "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug", + "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/identities/$identityId", "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/members/$membershipId", "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug" @@ -6250,6 +6373,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/settings", "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/access-management", "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/data-sources", + "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId", "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/members/$membershipId", "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/roles/$roleSlug" @@ -6266,6 +6390,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management", "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ca/$caId", "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId", + "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/identities/$identityId", "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/members/$membershipId", "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/roles/$roleSlug" @@ -6558,6 +6683,10 @@ export const routeTree = rootRoute "filePath": "ssh/SshHostGroupDetailsByIDPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout" }, + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/groups/$groupId": { + "filePath": "project/GroupDetailsByIDPage/route-cert-manager.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout" + }, "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId": { "filePath": "project/IdentityDetailsByIDPage/route-cert-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout" @@ -6574,6 +6703,10 @@ export const routeTree = rootRoute "filePath": "project/RoleDetailsBySlugPage/route-cert-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/groups/$groupId": { + "filePath": "project/GroupDetailsByIDPage/route-kms.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout" + }, "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/identities/$identityId": { "filePath": "project/IdentityDetailsByIDPage/route-kms.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout" @@ -6586,6 +6719,10 @@ export const routeTree = rootRoute "filePath": "project/RoleDetailsBySlugPage/route-kms.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout" }, + "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/groups/$groupId": { + "filePath": "project/GroupDetailsByIDPage/route-secret-manager.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout" + }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/identities/$identityId": { "filePath": "project/IdentityDetailsByIDPage/route-secret-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout" @@ -6598,6 +6735,10 @@ export const routeTree = rootRoute "filePath": "project/RoleDetailsBySlugPage/route-secret-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId": { + "filePath": "project/GroupDetailsByIDPage/route-secret-scanning.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout" + }, "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId": { "filePath": "project/IdentityDetailsByIDPage/route-secret-scanning.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout" @@ -6610,6 +6751,10 @@ export const routeTree = rootRoute "filePath": "project/RoleDetailsBySlugPage/route-secret-scanning.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout" }, + "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/groups/$groupId": { + "filePath": "project/GroupDetailsByIDPage/route-ssh.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout" + }, "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/identities/$identityId": { "filePath": "project/IdentityDetailsByIDPage/route-ssh.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 0c14e27c9..2572f9ce2 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -270,7 +270,8 @@ const secretManagerRoutes = route("/secret-manager/$projectId", [ route("/access-management", "project/AccessControlPage/route-secret-manager.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-manager.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-manager.tsx"), - route("/members/$membershipId", "project/MemberDetailsByIDPage/route-secret-manager.tsx") + route("/members/$membershipId", "project/MemberDetailsByIDPage/route-secret-manager.tsx"), + route("/groups/$groupId", "project/GroupDetailsByIDPage/route-secret-manager.tsx") ]) ]); @@ -314,7 +315,8 @@ const certManagerRoutes = route("/cert-manager/$projectId", [ route("/access-management", "project/AccessControlPage/route-cert-manager.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-cert-manager.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-cert-manager.tsx"), - route("/members/$membershipId", "project/MemberDetailsByIDPage/route-cert-manager.tsx") + route("/members/$membershipId", "project/MemberDetailsByIDPage/route-cert-manager.tsx"), + route("/groups/$groupId", "project/GroupDetailsByIDPage/route-cert-manager.tsx") ]) ]); @@ -326,7 +328,8 @@ const kmsRoutes = route("/kms/$projectId", [ route("/access-management", "project/AccessControlPage/route-kms.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-kms.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-kms.tsx"), - route("/members/$membershipId", "project/MemberDetailsByIDPage/route-kms.tsx") + route("/members/$membershipId", "project/MemberDetailsByIDPage/route-kms.tsx"), + route("/groups/$groupId", "project/GroupDetailsByIDPage/route-kms.tsx") ]) ]); @@ -341,7 +344,8 @@ const sshRoutes = route("/ssh/$projectId", [ route("/access-management", "project/AccessControlPage/route-ssh.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-ssh.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-ssh.tsx"), - route("/members/$membershipId", "project/MemberDetailsByIDPage/route-ssh.tsx") + route("/members/$membershipId", "project/MemberDetailsByIDPage/route-ssh.tsx"), + route("/groups/$groupId", "project/GroupDetailsByIDPage/route-ssh.tsx") ]) ]); @@ -356,7 +360,8 @@ const secretScanningRoutes = route("/secret-scanning/$projectId", [ route("/access-management", "project/AccessControlPage/route-secret-scanning.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-scanning.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-scanning.tsx"), - route("/members/$membershipId", "project/MemberDetailsByIDPage/route-secret-scanning.tsx") + route("/members/$membershipId", "project/MemberDetailsByIDPage/route-secret-scanning.tsx"), + route("/groups/$groupId", "project/GroupDetailsByIDPage/route-secret-scanning.tsx") ]) ]);