From dd4086a4c81bf07cdffff1daf8b1cd5fa561a6bc Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Fri, 10 Nov 2023 11:56:32 +0100 Subject: [PATCH] ISPN-15305 caches list role detail --- cypress/e2e/1_acess_management.cy.js | 5 +- src/app/AccessManagement/RoleDetail.tsx | 25 +-- .../RoleDetailContent/RoleCaches.tsx | 189 +++++++++++++++++- src/app/AccessManagement/RoleTableDisplay.tsx | 15 +- src/app/assets/languages/en.json | 8 + src/app/services/rolesHook.ts | 32 +++ src/services/cacheService.ts | 12 ++ 7 files changed, 263 insertions(+), 23 deletions(-) diff --git a/cypress/e2e/1_acess_management.cy.js b/cypress/e2e/1_acess_management.cy.js index 31357a383..84f0cf79d 100644 --- a/cypress/e2e/1_acess_management.cy.js +++ b/cypress/e2e/1_acess_management.cy.js @@ -46,7 +46,7 @@ describe('Global stats', () => { cy.get('[aria-label=role-description-input]') .should('have.value', 'aRole description with update'); - cy.get('[aria-label=nav-item-Permissions').click(); + cy.get('[aria-label=nav-item-permissions').click(); cy.contains('ALL'); cy.get('[data-cy=addPermissionButton').click(); cy.get('[data-cy=menu-toogle-permissions]').click(); @@ -58,6 +58,9 @@ describe('Global stats', () => { cy.get("[aria-label=Remove]").click(); cy.contains('Role aRole has been updated'); + cy.get('[aria-label=nav-item-caches').click(); + cy.contains('default'); + // remove cy.login(Cypress.env('username'), Cypress.env('password'), '/access-management'); cy.get("[aria-label=aRole-menu]").click(); diff --git a/src/app/AccessManagement/RoleDetail.tsx b/src/app/AccessManagement/RoleDetail.tsx index ccfe55e1c..f817c7d8d 100644 --- a/src/app/AccessManagement/RoleDetail.tsx +++ b/src/app/AccessManagement/RoleDetail.tsx @@ -24,20 +24,21 @@ import { useTranslation } from 'react-i18next'; import { DataContainerBreadcrumb } from '@app/Common/DataContainerBreadcrumb'; import { RoleGeneral } from '@app/AccessManagement/RoleDetailContent/RoleGeneral'; import { RolePermissions } from '@app/AccessManagement/RoleDetailContent/RolePermissions'; +import { RoleCaches } from '@app/AccessManagement/RoleDetailContent/RoleCaches'; import { DeleteRole } from '@app/AccessManagement/DeleteRole'; -import { useNavigate } from 'react-router'; import { useDescribeRole } from '@app/services/rolesHook'; +import { useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; const RoleDetail = () => { const navigate = useNavigate(); const roleName = useParams()['roleName'] as string; const { t } = useTranslation(); - const { role } = useDescribeRole(roleName); - const [activeTabKey, setActiveTabKey] = useState('0'); + const { role, loading } = useDescribeRole(roleName); + const [activeTabKey, setActiveTabKey] = useState<'general' | 'permissions' | 'caches'>('general'); const [showGeneralDescription, setShowGeneralDescription] = useState(true); const [showPermissions, setShowPermissions] = useState(false); - // const [showCaches, setShowCaches] = useState(false); + const [showCaches, setShowCaches] = useState(false); const [isOpen, setIsOpen] = useState(false); const [isDeleteRole, setIsDeleteRole] = useState(false); @@ -50,9 +51,9 @@ const RoleDetail = () => { }; useEffect(() => { - setShowGeneralDescription(activeTabKey === '0'); - setShowPermissions(activeTabKey === '1'); - // setShowCaches(activeTabKey === '2'); + setShowGeneralDescription(activeTabKey === 'general'); + setShowPermissions(activeTabKey === 'permissions'); + setShowCaches(activeTabKey === 'caches'); }, [activeTabKey]); interface AccessTab { @@ -65,9 +66,9 @@ const RoleDetail = () => { }; const tabs: AccessTab[] = [ - { name: t('access-management.role.tab-general'), key: '0' }, - { name: t('access-management.role.tab-permissions'), key: '1' } - // { name: t('access-management.role.tab-caches'), key: '2' } + { name: t('access-management.role.tab-general'), key: 'general' }, + { name: t('access-management.role.tab-permissions'), key: 'permissions' }, + { name: t('access-management.role.tab-caches'), key: 'caches' } ]; const buildTabs = () => { @@ -76,7 +77,7 @@ const RoleDetail = () => { {tabs.map((tab) => ( { {showGeneralDescription && } {showPermissions && } - {/*{showCaches && }*/} + {showCaches && } diff --git a/src/app/AccessManagement/RoleDetailContent/RoleCaches.tsx b/src/app/AccessManagement/RoleDetailContent/RoleCaches.tsx index 96ac352c2..e10acb43b 100644 --- a/src/app/AccessManagement/RoleDetailContent/RoleCaches.tsx +++ b/src/app/AccessManagement/RoleDetailContent/RoleCaches.tsx @@ -1,10 +1,193 @@ import { useTranslation } from 'react-i18next'; +import { + Bullseye, + Button, + ButtonVariant, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + EmptyStateVariant, + Pagination, + SearchInput, + Title, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, + ToolbarItemVariant +} from '@patternfly/react-core'; +import React, { useEffect, useState } from 'react'; +import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { useCachesForRole } from '@app/services/rolesHook'; +import { TableErrorState } from '@app/Common/TableErrorState'; +import { TableLoadingState } from '@app/Common/TableLoadingState'; +import { KeyIcon, SearchIcon } from '@patternfly/react-icons'; +import { Link } from 'react-router-dom'; -const RoleCaches = () => { +const RoleCaches = (props: { name: string }) => { const { t } = useTranslation(); - const brandname = t('brandname.brandname'); + const { secured, nonSecured, loading, error } = useCachesForRole(props.name); + const [pagination, setPagination] = useState({ + page: 1, + perPage: 5 + }); + const [searchValue, setSearchValue] = useState(''); + const [cachesRows, setCachesRows] = useState([]); + const [filteredCaches, setFilteredCaches] = useState([]); - return 'Caches'; + useEffect(() => { + if (loading) { + return; + } + + const caches = secured.concat(nonSecured); + if (searchValue.trim() !== '') { + setFilteredCaches( + caches.filter((perm) => perm.toLowerCase().includes(searchValue.toLowerCase())).sort() + ); + } else { + setFilteredCaches(caches); + } + + setPagination({ + ...pagination, + page: 1 + }); + }, [secured, nonSecured, searchValue, loading]); + + useEffect(() => { + if (loading) { + return; + } + const initSlice = (pagination.page - 1) * pagination.perPage; + setCachesRows(filteredCaches.slice(initSlice, initSlice + pagination.perPage)); + }, [filteredCaches, pagination]); + + const columnNames = { + name: t('access-management.role.caches-name') + }; + + const onSetPage = (_event, pageNumber) => { + setPagination({ + ...pagination, + page: pageNumber + }); + }; + + const onPerPageSelect = (_event, perPage) => { + setPagination({ + page: 1, + perPage: perPage + }); + }; + + const onSearchChange = (value: string) => { + setSearchValue(value); + }; + + const paginationComponent = ( + + ); + + const displayRows = () => { + if (!loading && cachesRows.length == 0) { + return ( + + + + + + + {t('access-management.role.no-caches-found')} + + {t('access-management.role.no-filtered-caches-body')} + + + + + ); + } + + return ( + + {cachesRows.map((row) => ( + + + {secured.includes(row) && } + + + + + + ))} + + ); + }; + + if (loading) { + return ; + } + + if (error) { + return ; + } + + return ( + + + + + + onSearchChange(value)} + onSearch={(_event, value) => onSearchChange(value)} + onClear={() => setSearchValue('')} + /> + + + {paginationComponent} + + + + + + + + + {displayRows()} +
{t('access-management.role.caches-name-tooltip')}, + ariaLabel: 'Cache name more information', + popoverProps: { + headerContent: columnNames.name, + } + }}>{columnNames.name}
+ + {paginationComponent} + +
+ ); }; export { RoleCaches }; diff --git a/src/app/AccessManagement/RoleTableDisplay.tsx b/src/app/AccessManagement/RoleTableDisplay.tsx index f44347cfc..c63635815 100644 --- a/src/app/AccessManagement/RoleTableDisplay.tsx +++ b/src/app/AccessManagement/RoleTableDisplay.tsx @@ -118,17 +118,18 @@ const RoleTableDisplay = () => { {rolesRows.map((row) => ( - {row.implicit && ( - - - - )} + {row.implicit && ()} - {row.name} + diff --git a/src/app/assets/languages/en.json b/src/app/assets/languages/en.json index 89cb5c0ed..df6af78f1 100644 --- a/src/app/assets/languages/en.json +++ b/src/app/assets/languages/en.json @@ -929,13 +929,21 @@ "tab-caches": "Accessible caches", "loading": "Loading role {{roleName}}", "error": "An error occurred while retrieving the role {{roleName}}", + "loading-caches": "Loading caches for role {{roleName}}", + "error-caches": "An error occurred while retrieving the available caches for role {{roleName}}", "implicit-warning": "The general settings for predefined roles are not editable.", "permission-name": "Permission name", "permission-description": "Description", "add-permission-button": "Add permission", "permissions-search-placeholder": "Filter by name", + "caches-name": "Cache name", + "caches-name-tooltip": "Cache name prefixed with a key indicates that the cache has authorization enabled and can be accessed by this role.", + "caches-secured": "Secured", + "caches-search-placeholder": "Filter by cache name", "no-permissions-found": "No permission found", "no-filtered-permissions-body": "No permission match your search criteria.", + "no-caches-found": "No caches found", + "no-filtered-caches-body": "No caches match your search criteria.", "permissions-implicit-warning": "The permissions for predefined roles are not editable.", "update-success": "Role {{name}} has been updated", "update-error": "Unexpected error updating the role {{name}}", diff --git a/src/app/services/rolesHook.ts b/src/app/services/rolesHook.ts index c78ce3a76..8dbdbd9c4 100644 --- a/src/app/services/rolesHook.ts +++ b/src/app/services/rolesHook.ts @@ -200,3 +200,35 @@ export function useFlushCache(call: () => void) { onFlushCache }; } + +export function useCachesForRole(roleName: string) { + const [secured, setSecured] = useState([]); + const [nonSecured, setNonSecured] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + useEffect(() => { + if (loading) { + ConsoleServices.caches() + .getCachesForRole(roleName) + .then((either) => { + if (either.isRight()) { + const securedCaches = either.value.get('secured') as string[]; + const nonSecuredCaches = either.value.get('non-secured') as string[]; + setSecured(securedCaches.sort()); + setNonSecured(nonSecuredCaches.sort()); + } else { + setError(either.value.message); + } + }) + .finally(() => setLoading(false)); + } + }, [loading]); + + return { + secured, + nonSecured, + loading, + error + }; +} diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts index 249f4defe..fa24712f1 100644 --- a/src/services/cacheService.ts +++ b/src/services/cacheService.ts @@ -598,4 +598,16 @@ export class CacheService { public async getClusterDistribution(): Promise> { return this.fetchCaller.get(this.endpoint + '/cluster?action=distribution', (text) => text); } + + /** + * Retrieve caches for a role + * + */ + public async getCachesForRole(role: string): Promise>> { + return this.fetchCaller.get(this.endpoint + '/caches?action=role-accessible&role=' + role, + (data) => new Map() + .set('secured', data['secured']) + .set('non-secured', data['non-secured']) + ); + } }