1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00
Nuno Góis 2025-03-17 08:46:09 +00:00 committed by GitHub
parent 76b3e06fe5
commit 3a59886206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 105 additions and 50 deletions

View File

@ -3,7 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo'; import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo';
import { styled, useMediaQuery } from '@mui/material'; 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 { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import theme from 'themes/theme'; import theme from 'themes/theme';
@ -18,16 +18,21 @@ import {
} from 'utils/permissions'; } from 'utils/permissions';
import type { IAccessOverviewPermissionCategory } from './AccessOverviewAccordion/AccessOverviewList'; import type { IAccessOverviewPermissionCategory } from './AccessOverviewAccordion/AccessOverviewList';
import { createProjectPermissionsStructure } from 'component/admin/roles/RoleForm/RolePermissionCategories/createProjectPermissionsStructure'; 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 }) => ({ const StyledActionsContainer = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
flex: 1, flex: 1,
gap: theme.spacing(1), gap: theme.spacing(1),
maxWidth: 600, maxWidth: 800,
[theme.breakpoints.down('md')]: { [theme.breakpoints.down('md')]: {
flexDirection: 'column', flexDirection: 'column',
maxWidth: '100%', maxWidth: '100%',
}, },
'& > div': {
width: '100%',
},
})); }));
const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({ const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({
@ -36,6 +41,20 @@ const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({
gap: theme.spacing(2), 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 = () => { export const AccessOverview = () => {
const id = useRequiredPathParam('id'); const id = useRequiredPathParam('id');
const [query, setQuery] = useQueryParams({ const [query, setQuery] = useQueryParams({
@ -48,6 +67,7 @@ export const AccessOverview = () => {
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [searchValue, setSearchValue] = useState('');
const [project, setProject] = useState(query.project ?? ''); const [project, setProject] = useState(query.project ?? '');
const [environment, setEnvironment] = useState( const [environment, setEnvironment] = useState(
query.environment ?? undefined, query.environment ?? undefined,
@ -71,6 +91,7 @@ export const AccessOverview = () => {
const AccessActions = ( const AccessActions = (
<StyledActionsContainer> <StyledActionsContainer>
<Search initialValue={searchValue} onChange={setSearchValue} />
<AccessOverviewSelect <AccessOverviewSelect
label='Project' label='Project'
options={projects} options={projects}
@ -92,20 +113,40 @@ export const AccessOverview = () => {
</StyledActionsContainer> </StyledActionsContainer>
); );
const rootCategories = getCategorizedRootPermissions( const rootCategories = useMemo(() => {
overview?.root ?? [], const categories = getCategorizedRootPermissions(
) as IAccessOverviewPermissionCategory[]; overview?.root ?? [],
) as IAccessOverviewPermissionCategory[];
const projectCategories = createProjectPermissionsStructure( if (!searchValue) return categories;
overview?.project ?? [],
).map(({ label, permissions }) => ({
label,
permissions: permissions.map(([permission]) => permission),
})) as IAccessOverviewPermissionCategory[];
const environmentCategories = getCategorizedProjectPermissions( return categories
overview?.environment ?? [], .map((category) => filterCategory(category, searchValue))
) as IAccessOverviewPermissionCategory[]; .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 ( return (
<PageContent <PageContent
@ -128,20 +169,24 @@ export const AccessOverview = () => {
} }
> >
<StyledAccessOverviewContainer> <StyledAccessOverviewContainer>
<AccessOverviewAccordion categories={rootCategories}> <SearchHighlightProvider value={searchValue}>
Root permissions for role {rootRole?.name} <AccessOverviewAccordion categories={rootCategories}>
</AccessOverviewAccordion> Root permissions for role {rootRole?.name}
<AccessOverviewAccordion categories={projectCategories}>
Project permissions
{project
? ` for project ${project}${projectRoles?.length ? ` with project role${projectRoles.length !== 1 ? 's' : ''} ${projectRoles?.map((role: any) => role.name).join(', ')}` : ''}`
: ''}
</AccessOverviewAccordion>
{environment && (
<AccessOverviewAccordion categories={environmentCategories}>
Environment permissions for {environment}
</AccessOverviewAccordion> </AccessOverviewAccordion>
)} <AccessOverviewAccordion categories={projectCategories}>
Project permissions
{project
? ` for project ${project}${projectRoles?.length ? ` with project role${projectRoles.length !== 1 ? 's' : ''} ${projectRoles?.map((role: any) => role.name).join(', ')}` : ''}`
: ''}
</AccessOverviewAccordion>
{environment && (
<AccessOverviewAccordion
categories={environmentCategories}
>
Environment permissions for {environment}
</AccessOverviewAccordion>
)}
</SearchHighlightProvider>
</StyledAccessOverviewContainer> </StyledAccessOverviewContainer>
</PageContent> </PageContent>
); );

View File

@ -1,6 +1,8 @@
import Check from '@mui/icons-material/Check'; import Check from '@mui/icons-material/Check';
import Close from '@mui/icons-material/Close'; import Close from '@mui/icons-material/Close';
import { Box, styled } from '@mui/material'; import { Box, styled } from '@mui/material';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import type { import type {
IAccessOverviewPermission, IAccessOverviewPermission,
IPermissionCategory, IPermissionCategory,
@ -53,29 +55,37 @@ export const AccessOverviewList = ({
categories, categories,
}: { }: {
categories: IAccessOverviewPermissionCategory[]; categories: IAccessOverviewPermissionCategory[];
}) => ( }) => {
<Box sx={{ maxHeight: 500, overflow: 'auto' }}> const { searchQuery } = useSearchHighlightContext();
<StyledList>
{categories.map((category) => ( return (
<> <Box sx={{ maxHeight: 500, overflow: 'auto' }}>
<li key={category.label}> <StyledList>
<strong>{category.label}</strong> {categories.map((category) => (
</li> <>
<StyledList> <li key={category.label}>
{category.permissions.map((permission) => ( <strong>{category.label}</strong>
<li key={permission.name}> </li>
<div>{permission.displayName}</div> <StyledList>
<PermissionStatus {category.permissions.map((permission) => (
hasPermission={permission.hasPermission} <li key={permission.name}>
/> <div>
</li> <Highlighter search={searchQuery}>
))} {permission.displayName}
</StyledList> </Highlighter>
</> </div>
))} <PermissionStatus
</StyledList> hasPermission={permission.hasPermission}
</Box> />
); </li>
))}
</StyledList>
</>
))}
</StyledList>
</Box>
);
};
const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => ( const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => (
<StyledPermissionStatus hasPermission={hasPermission}> <StyledPermissionStatus hasPermission={hasPermission}>