diff --git a/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx b/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx index ffc4689e75..e04cdd8a23 100644 --- a/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx +++ b/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx @@ -3,7 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo'; import { styled, useMediaQuery } from '@mui/material'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import theme from 'themes/theme'; @@ -18,16 +18,21 @@ import { } from 'utils/permissions'; import type { IAccessOverviewPermissionCategory } from './AccessOverviewAccordion/AccessOverviewList'; import { createProjectPermissionsStructure } from 'component/admin/roles/RoleForm/RolePermissionCategories/createProjectPermissionsStructure'; +import { Search } from 'component/common/Search/Search'; +import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; const StyledActionsContainer = styled('div')(({ theme }) => ({ display: 'flex', flex: 1, gap: theme.spacing(1), - maxWidth: 600, + maxWidth: 800, [theme.breakpoints.down('md')]: { flexDirection: 'column', maxWidth: '100%', }, + '& > div': { + width: '100%', + }, })); const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({ @@ -36,6 +41,20 @@ const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({ gap: theme.spacing(2), })); +const filterCategory = ( + category: IAccessOverviewPermissionCategory, + search: string, +): IAccessOverviewPermissionCategory | undefined => { + const searchLower = search.toLowerCase(); + const filteredPermissions = category.permissions.filter(({ displayName }) => + displayName.toLowerCase().includes(searchLower), + ); + + if (filteredPermissions.length) { + return { ...category, permissions: filteredPermissions }; + } +}; + export const AccessOverview = () => { const id = useRequiredPathParam('id'); const [query, setQuery] = useQueryParams({ @@ -48,6 +67,7 @@ export const AccessOverview = () => { const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + const [searchValue, setSearchValue] = useState(''); const [project, setProject] = useState(query.project ?? ''); const [environment, setEnvironment] = useState( query.environment ?? undefined, @@ -71,6 +91,7 @@ export const AccessOverview = () => { const AccessActions = ( + { ); - const rootCategories = getCategorizedRootPermissions( - overview?.root ?? [], - ) as IAccessOverviewPermissionCategory[]; + const rootCategories = useMemo(() => { + const categories = getCategorizedRootPermissions( + overview?.root ?? [], + ) as IAccessOverviewPermissionCategory[]; - const projectCategories = createProjectPermissionsStructure( - overview?.project ?? [], - ).map(({ label, permissions }) => ({ - label, - permissions: permissions.map(([permission]) => permission), - })) as IAccessOverviewPermissionCategory[]; + if (!searchValue) return categories; - const environmentCategories = getCategorizedProjectPermissions( - overview?.environment ?? [], - ) as IAccessOverviewPermissionCategory[]; + return categories + .map((category) => filterCategory(category, searchValue)) + .filter(Boolean) as IAccessOverviewPermissionCategory[]; + }, [overview?.root, searchValue]); + + const projectCategories = useMemo(() => { + const categories = createProjectPermissionsStructure( + overview?.project ?? [], + ).map(({ label, permissions }) => ({ + label, + permissions: permissions.map(([permission]) => permission), + })) as IAccessOverviewPermissionCategory[]; + + return categories + .map((category) => filterCategory(category, searchValue)) + .filter(Boolean) as IAccessOverviewPermissionCategory[]; + }, [overview?.project, searchValue]); + + const environmentCategories = useMemo(() => { + const categories = getCategorizedProjectPermissions( + overview?.environment ?? [], + ) as IAccessOverviewPermissionCategory[]; + + return categories + .map((category) => filterCategory(category, searchValue)) + .filter(Boolean) as IAccessOverviewPermissionCategory[]; + }, [overview?.environment, searchValue]); return ( { } > - - Root permissions for role {rootRole?.name} - - - Project permissions - {project - ? ` for project ${project}${projectRoles?.length ? ` with project role${projectRoles.length !== 1 ? 's' : ''} ${projectRoles?.map((role: any) => role.name).join(', ')}` : ''}` - : ''} - - {environment && ( - - Environment permissions for {environment} + + + Root permissions for role {rootRole?.name} - )} + + Project permissions + {project + ? ` for project ${project}${projectRoles?.length ? ` with project role${projectRoles.length !== 1 ? 's' : ''} ${projectRoles?.map((role: any) => role.name).join(', ')}` : ''}` + : ''} + + {environment && ( + + Environment permissions for {environment} + + )} + ); diff --git a/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx b/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx index a361fa610f..48de04cae6 100644 --- a/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx +++ b/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx @@ -1,6 +1,8 @@ import Check from '@mui/icons-material/Check'; import Close from '@mui/icons-material/Close'; import { Box, styled } from '@mui/material'; +import { Highlighter } from 'component/common/Highlighter/Highlighter'; +import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import type { IAccessOverviewPermission, IPermissionCategory, @@ -53,29 +55,37 @@ export const AccessOverviewList = ({ categories, }: { categories: IAccessOverviewPermissionCategory[]; -}) => ( - - - {categories.map((category) => ( - <> -
  • - {category.label} -
  • - - {category.permissions.map((permission) => ( -
  • -
    {permission.displayName}
    - -
  • - ))} -
    - - ))} -
    -
    -); +}) => { + const { searchQuery } = useSearchHighlightContext(); + + return ( + + + {categories.map((category) => ( + <> +
  • + {category.label} +
  • + + {category.permissions.map((permission) => ( +
  • +
    + + {permission.displayName} + +
    + +
  • + ))} +
    + + ))} +
    +
    + ); +}; const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => (