From 069550ead117834f8dff4771306752546cadcc7d Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Fri, 27 Oct 2023 13:21:46 +0100 Subject: [PATCH] ISPN-14716 flush cache --- cypress/e2e/1_acess_management.cy.js | 14 +- src/app/AccessManagement/AccessManager.tsx | 83 +++++++++-- .../AccessManagement/FlushRoleCacheModal.tsx | 51 +++++++ src/app/CacheManagers/CacheManagers.tsx | 37 +++-- src/app/Caches/DetailCache.tsx | 140 +++++++++--------- src/app/Caches/Entries/CacheEntries.tsx | 10 +- src/app/ClusterStatus/ClusterStatus.tsx | 48 +++--- src/app/ConnectedClients/ConnectedClients.tsx | 17 ++- src/app/GlobalStats/GlobalStats.tsx | 34 +++-- src/app/IndexManagement/IndexManagement.tsx | 18 ++- src/app/Rebalancing/RebalancingCache.tsx | 10 +- .../Rebalancing/RebalancingCacheManager.tsx | 60 ++++---- src/app/XSite/XSiteCache.tsx | 72 ++++----- src/app/assets/languages/en.json | 30 +++- src/app/services/rolesHook.ts | 19 +++ src/services/securityService.ts | 13 ++ 16 files changed, 439 insertions(+), 217 deletions(-) create mode 100644 src/app/AccessManagement/FlushRoleCacheModal.tsx diff --git a/cypress/e2e/1_acess_management.cy.js b/cypress/e2e/1_acess_management.cy.js index 454faf3db..d85688493 100644 --- a/cypress/e2e/1_acess_management.cy.js +++ b/cypress/e2e/1_acess_management.cy.js @@ -9,6 +9,19 @@ describe('Global stats', () => { cy.contains('Superuser'); }); + it('successfully flushes the ACL cache', () => { + cy.get('[data-cy=aclActions]').click(); + cy.get('[data-cy="flushCacheAction"]').click(); + cy.get('[aria-label="Cancel"]').click(); + cy.contains('flushed').should('not.exist'); + + cy.get('[data-cy=aclActions]').click(); + cy.get('[data-cy="flushCacheAction"]').click(); + cy.get('[aria-label="Flush"]').click(); + cy.contains('Access control list cache successfully flushed across the cluster'); + + }); + it('successfully creates and removes a role', () => { // create cy.get('button[data-cy="createRoleButton"]').click(); @@ -28,5 +41,4 @@ describe('Global stats', () => { cy.contains('aRole description').should('not.exist'); }); - }); diff --git a/src/app/AccessManagement/AccessManager.tsx b/src/app/AccessManagement/AccessManager.tsx index 2a5173fe7..9f1474f62 100644 --- a/src/app/AccessManagement/AccessManager.tsx +++ b/src/app/AccessManagement/AccessManager.tsx @@ -3,6 +3,11 @@ import { useState } from 'react'; import { Card, CardBody, + Dropdown, + DropdownItem, + DropdownList, + MenuToggle, + MenuToggleElement, Nav, NavItem, NavList, @@ -10,10 +15,15 @@ import { PageSectionVariants, Text, TextContent, - TextVariants + TextVariants, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; import { RoleTableDisplay } from '@app/AccessManagement/RoleTableDisplay'; +import { FlushRoleCacheModal } from '@app/AccessManagement/FlushRoleCacheModal'; const AccessManager = () => { const { t } = useTranslation(); @@ -21,12 +31,23 @@ const AccessManager = () => { const [activeTabKey, setActiveTabKey] = useState('0'); const [showRoles, setShowRoles] = useState(true); const [showAccessControl, setShowAccessControl] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [isFlushCache, setIsFlushCache] = useState(false); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { + setIsOpen(false); + }; interface AccessTab { key: string; name: string; } - const handleTabClick = (nav) => { + + const handleTabClick = (ev, nav) => { const tabIndex = nav.itemId; setActiveTabKey(tabIndex); setShowRoles(tabIndex == '0'); @@ -62,17 +83,61 @@ const AccessManager = () => { ); + const displayActions = ( + + + setIsOpen(isOpen)} + toggle={(toggleRef: React.Ref) => ( + + {t('common.actions.actions')} + + )} + ouiaId="accessManagementDropdown" + shouldFocusToggleOnSelect + > + + setIsFlushCache(true)}> + {t('access-management.flush-cache-action')} + + + + + + ); + return ( - <> + - - {t('access-management.title')} - {t('access-management.description', { brandname: brandname })} - + + + + + + {t('access-management.title')} + {t('access-management.description', { brandname: brandname })} + + + + {displayActions} + + {buildTabs()} - {buildSelectedContent} - + + {buildSelectedContent} + setIsFlushCache(false)} + submitModal={() => { + setIsFlushCache(false); + }}/> + + ); }; diff --git a/src/app/AccessManagement/FlushRoleCacheModal.tsx b/src/app/AccessManagement/FlushRoleCacheModal.tsx new file mode 100644 index 000000000..68d81d7e4 --- /dev/null +++ b/src/app/AccessManagement/FlushRoleCacheModal.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Button, ButtonVariant, Modal, Text, TextContent } from '@patternfly/react-core'; +import { useTranslation } from 'react-i18next'; +import { useFlushCache } from '@app/services/rolesHook'; + +const FlushRoleCacheModal = (props: { isModalOpen: boolean; submitModal: () => void; closeModal: () => void }) => { + const { t } = useTranslation(); + const { onFlushCache } = useFlushCache(props.submitModal); + + return ( + { + onFlushCache(); + }} + > + {t('access-management.flush-cache-action')} + , + + ]} + > + + + {t('access-management.modal-flush-cache-description')} + + + + ); +}; + +export { FlushRoleCacheModal }; diff --git a/src/app/CacheManagers/CacheManagers.tsx b/src/app/CacheManagers/CacheManagers.tsx index 5d7fd6ce5..c9c7b3cf0 100644 --- a/src/app/CacheManagers/CacheManagers.tsx +++ b/src/app/CacheManagers/CacheManagers.tsx @@ -14,7 +14,11 @@ import { Spinner, Text, TextContent, - TextVariants + TextVariants, + Title, + Toolbar, + ToolbarContent, + ToolbarItem } from '@patternfly/react-core'; import displayUtils from '@services/displayUtils'; import { CacheTableDisplay } from '@app/CacheManagers/CacheTableDisplay'; @@ -169,22 +173,23 @@ const CacheManagers = () => { title = displayUtils.capitalize(cm.name); return ( - - - - - {title} - - - - - + + + + {title} + + + + + + - - {buildSiteDisplay(cm.local_site)} - - - + + + + + + {buildTabs()} ); diff --git a/src/app/Caches/DetailCache.tsx b/src/app/Caches/DetailCache.tsx index 71ff568e2..82c0ffbf8 100644 --- a/src/app/Caches/DetailCache.tsx +++ b/src/app/Caches/DetailCache.tsx @@ -248,10 +248,8 @@ const DetailCache = (props: { cacheName: string }) => { } return ( - - - - + <> + @@ -265,7 +263,7 @@ const DetailCache = (props: { cacheName: string }) => { - + ); }; @@ -275,74 +273,68 @@ const DetailCache = (props: { cacheName: string }) => { } return ( - - - - - - - {`Rebuilding the index for ${cacheName}`} - - - + + + + {`Rebuilding the index for ${cacheName}`} + + + ); }; const buildIndexManage = () => { if (!cache?.features.indexed) return; return ( - - - - {buildDisplayReindexing()} - - - - - - - + <> + + {buildDisplayReindexing()} + + + + + + ); }; const buildRefreshButton = () => { return ( - - - - - - - - + + + ); }; const buildFeaturesChip = () => { if (!cache?.features) return; return ( - - - - + <> + + {displayUtils.createFeaturesChipGroup(cache.features).map((feature) => ( @@ -350,8 +342,8 @@ const DetailCache = (props: { cacheName: string }) => { ))} - - + + ); }; @@ -377,12 +369,16 @@ const DetailCache = (props: { cacheName: string }) => { } return ( - - - {buildFeaturesChip()} - {buildBackupsManage()} - {buildIndexManage()} - + + + + + {buildFeaturesChip()} + {buildBackupsManage()} + {buildIndexManage()} + + + ); }; @@ -432,7 +428,7 @@ const DetailCache = (props: { cacheName: string }) => { return ( - + {t('caches.info.loading', { cacheName: cacheName })} @@ -448,7 +444,7 @@ const DetailCache = (props: { cacheName: string }) => { return ( - + {t('caches.info.error', { cacheName: cacheName })} @@ -463,8 +459,8 @@ const DetailCache = (props: { cacheName: string }) => { return ( - - + + {cache.name} @@ -474,11 +470,11 @@ const DetailCache = (props: { cacheName: string }) => { {buildShowMoreHeader()} - {buildRefreshButton()} - - - {buildShowMorePanel()} + + {buildRefreshButton()} + + {buildShowMorePanel()} { if (cache.encoding.key == EncodingType.Protobuf) { setSelectSearchOption(ContentType.string); setKeyContentTypeToEdit(ContentType.string); - } else if (cache.encoding.key == EncodingType.Java || - cache.encoding.key == EncodingType.JBoss || - cache.encoding.key == EncodingType.JavaSerialized) { + } else if ( + cache.encoding.key == EncodingType.Java || + cache.encoding.key == EncodingType.JBoss || + cache.encoding.key == EncodingType.JavaSerialized + ) { setSelectSearchOption(ContentType.StringContentType); setKeyContentTypeToEdit(ContentType.StringContentType); - } else if (cache.encoding.key == EncodingType.XML){ + } else if (cache.encoding.key == EncodingType.XML) { setSelectSearchOption(ContentType.XML); setKeyContentTypeToEdit(ContentType.XML); } else if (cache.encoding.key == EncodingType.JSON) { diff --git a/src/app/ClusterStatus/ClusterStatus.tsx b/src/app/ClusterStatus/ClusterStatus.tsx index 44cbec140..dfbc47833 100644 --- a/src/app/ClusterStatus/ClusterStatus.tsx +++ b/src/app/ClusterStatus/ClusterStatus.tsx @@ -23,7 +23,9 @@ import { Toolbar, ToolbarContent, ToolbarItem, - EmptyStateHeader + EmptyStateHeader, + ToolbarGroup, + Title } from '@patternfly/react-core'; import { CubesIcon, SearchIcon, DownloadIcon } from '@patternfly/react-icons'; import { Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; @@ -91,7 +93,7 @@ const ClusterStatus = (props) => { if (!cacheManager) { return ( - Cluster + - ); } @@ -100,26 +102,28 @@ const ClusterStatus = (props) => { const sizeLabel = cacheManager.cluster_size + member + 'in use'; return ( - - - - - {t('cluster-membership.title')} - - - - - - - - - - - {sizeLabel} - - - - + + + + + {t('cluster-membership.title')} + + + + + + + + + + + + {sizeLabel} + + + + + ); }; diff --git a/src/app/ConnectedClients/ConnectedClients.tsx b/src/app/ConnectedClients/ConnectedClients.tsx index 9e3c16b66..52a33025b 100644 --- a/src/app/ConnectedClients/ConnectedClients.tsx +++ b/src/app/ConnectedClients/ConnectedClients.tsx @@ -29,7 +29,8 @@ import { ToolbarItem, ToolbarItemVariant, ToolbarGroup, - EmptyStateHeader + EmptyStateHeader, + Title } from '@patternfly/react-core'; import { CubesIcon, SearchIcon, InfoCircleIcon } from '@patternfly/react-icons'; import { Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; @@ -350,10 +351,16 @@ const ConnectedClients = () => { return ( - - {t('connected-clients.title')} - {t('connected-clients.connected-clients-description')} - + + + + + {t('connected-clients.title')} + {t('connected-clients.connected-clients-description')} + + + + {buildConnectedClients()} diff --git a/src/app/GlobalStats/GlobalStats.tsx b/src/app/GlobalStats/GlobalStats.tsx index 131d5b0de..9040401cb 100644 --- a/src/app/GlobalStats/GlobalStats.tsx +++ b/src/app/GlobalStats/GlobalStats.tsx @@ -26,7 +26,11 @@ import { TextListItemVariants, TextListVariants, TextVariants, - EmptyStateHeader + EmptyStateHeader, + ToolbarGroup, + ToolbarContent, + Toolbar, + ToolbarItem } from '@patternfly/react-core'; import { ArrowIcon, CubesIcon, RedoIcon } from '@patternfly/react-icons'; import { Link } from 'react-router-dom'; @@ -332,28 +336,34 @@ const GlobalStats = () => { ); return ( - - - - {t('global-stats.title')} - {descriptionText()} - - - {buildRefreshButton} - + + + + + + {t('global-stats.title')} + {descriptionText()} + + + + + {buildRefreshButton} + + + {buildStats()} diff --git a/src/app/IndexManagement/IndexManagement.tsx b/src/app/IndexManagement/IndexManagement.tsx index b93c5f206..312633cde 100644 --- a/src/app/IndexManagement/IndexManagement.tsx +++ b/src/app/IndexManagement/IndexManagement.tsx @@ -147,11 +147,17 @@ const IndexManagement = (props) => { - - - {t('caches.index.indexing-status')} - - + + + + + + {t('caches.index.indexing-status')} + + + + + @@ -169,7 +175,7 @@ const IndexManagement = (props) => { }} > diff --git a/src/app/Rebalancing/RebalancingCache.tsx b/src/app/Rebalancing/RebalancingCache.tsx index 93e8bcadd..1790bb2ff 100644 --- a/src/app/Rebalancing/RebalancingCache.tsx +++ b/src/app/Rebalancing/RebalancingCache.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Label, Spinner, Switch, ToolbarItem } from '@patternfly/react-core'; +import { Label, Spinner, Switch, TextContent, ToolbarItem, Text } from '@patternfly/react-core'; import { useCacheDetail } from '@app/services/cachesHook'; import { useConnectedUser } from '@app/services/userManagementHook'; import { ConsoleServices } from '@services/ConsoleServices'; @@ -17,7 +17,7 @@ const RebalancingCache = () => { // If rebalancing is not activated at cluster level, don't display anything if (!cacheManager.rebalancing_enabled || cache.rebalancing_enabled == undefined) { - return ; + return <>; } if (loading || !cache) { @@ -34,7 +34,11 @@ const RebalancingCache = () => { if (cache?.rehash_in_progress) { return ( - {t('caches.rebalancing.rebalancing')} + + + {t('caches.rebalancing.rebalancing')} + + ); } diff --git a/src/app/Rebalancing/RebalancingCacheManager.tsx b/src/app/Rebalancing/RebalancingCacheManager.tsx index 86e7751a4..9c309f515 100644 --- a/src/app/Rebalancing/RebalancingCacheManager.tsx +++ b/src/app/Rebalancing/RebalancingCacheManager.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Divider, FlexItem, Spinner, Switch } from '@patternfly/react-core'; +import { Divider, Flex, FlexItem, Spinner, Switch } from '@patternfly/react-core'; import { useConnectedUser } from '@app/services/userManagementHook'; import { ConsoleServices } from '@services/ConsoleServices'; import { ConsoleACL } from '@services/securityService'; @@ -28,38 +28,40 @@ const RebalancingCacheManager = () => { cm.rebalancing_enabled != undefined ) { return ( - - - - setConfirmationModalOpened(true)} - /> - - ConsoleServices.dataContainer() - .rebalancing(cm.name, !cm.rebalancing_enabled) - .then((r) => { - addAlert(r); - reload(); - }) - .finally(() => setConfirmationModalOpened(false)) - } - closeModal={() => setConfirmationModalOpened(false)} - enabled={cm.rebalancing_enabled} - /> - - + <> + + + + setConfirmationModalOpened(true)} + /> + + ConsoleServices.dataContainer() + .rebalancing(cm.name, !cm.rebalancing_enabled) + .then((r) => { + addAlert(r); + reload(); + }) + .finally(() => setConfirmationModalOpened(false)) + } + closeModal={() => setConfirmationModalOpened(false)} + enabled={cm.rebalancing_enabled} + /> + + + ); } // Return nothing if the connected user is not ADMIN - return ; + return <>; }; export { RebalancingCacheManager }; diff --git a/src/app/XSite/XSiteCache.tsx b/src/app/XSite/XSiteCache.tsx index c88656d74..9b5f8655d 100644 --- a/src/app/XSite/XSiteCache.tsx +++ b/src/app/XSite/XSiteCache.tsx @@ -7,8 +7,6 @@ import { ButtonVariant, Card, CardBody, - Level, - LevelItem, PageSection, PageSectionVariants, Switch, @@ -21,7 +19,7 @@ import { ToolbarItemVariant } from '@patternfly/react-core'; import { Link } from 'react-router-dom'; -import { global_spacer_md, global_spacer_xs } from '@patternfly/react-tokens'; +import { global_spacer_xs } from '@patternfly/react-tokens'; import { useApiAlert } from '@app/utils/useApiAlert'; import { DataContainerBreadcrumb } from '@app/Common/DataContainerBreadcrumb'; import { cellWidth, IRow, TableVariant, textCenter } from '@patternfly/react-table'; @@ -115,19 +113,19 @@ const XSiteCache = (props) => { }, [backups, backupsStatus, stateTransferStatus, loading, error]); const columns = [ - { title: 'Site', transforms: [cellWidth(30)] }, + { title: t('caches.backups.column-site'), transforms: [cellWidth(30)] }, { - title: 'Status', + title: t('caches.backups.column-status'), transforms: [cellWidth(30), textCenter], cellTransforms: [textCenter] }, { - title: 'Transfer status / Result', + title: t('caches.backups.column-transfer'), transforms: [cellWidth(40), textCenter], cellTransforms: [textCenter] }, { - title: 'Action', + title: t('caches.backups.column-action'), transforms: [cellWidth(20), textCenter], cellTransforms: [textCenter] } @@ -174,18 +172,18 @@ const XSiteCache = (props) => { marginTop: global_spacer_xs.value }} /> - Mixed + {t('caches.backups.mixed')} @@ -196,8 +194,8 @@ const XSiteCache = (props) => { return ( bringOnlineTakeOffLine(site, status)} /> @@ -232,7 +230,7 @@ const XSiteCache = (props) => { }) } > - Cancel + {t('common.actions.cancel')} ); } @@ -240,7 +238,7 @@ const XSiteCache = (props) => { if (stStatus == ST_SEND_OK || stStatus == ST_SEND_FAILED || stStatus == ST_SEND_CANCELED) { return ( ); } @@ -322,28 +320,32 @@ const XSiteCache = (props) => { return ( - - - - - - Backups management + + + + + + + {t('caches.backups.title')} + + + + + + + + - - - - - - - - - - + + + diff --git a/src/app/assets/languages/en.json b/src/app/assets/languages/en.json index 22c0aa2fb..932d69d34 100644 --- a/src/app/assets/languages/en.json +++ b/src/app/assets/languages/en.json @@ -7,6 +7,14 @@ "configuration-docs-link": "https://infinispan.org/docs/stable/titles/configuring/configuring.html", "default-roles-docs-link": "https://infinispan.org/docs/stable/titles/server/server.html#default-user-roles_server-getting-started" }, + "common": { + "actions": { + "actions": "Actions", + "refresh": "Refresh", + "back": "Back", + "cancel": "Cancel" + } + }, "layout": { "console-name": "Server Management Console", "skip-to-content": "Skip to Content", @@ -534,7 +542,6 @@ "action-see-less": "See fewer cache details", "action-see-more": "See more cache details", "action-manage-indexes": "Manage indexes", - "refresh": "Refresh", "back": "Back" }, "entries": { @@ -642,8 +649,20 @@ "size": "Index size in bytes", "indexing-status": "Indexing", "button-clear": "Clear index", - "button-rebuild": "Rebuild index", - "button-back-to-cache-detail": "Back" + "button-rebuild": "Rebuild index" + }, + "backups": { + "title": "Backups management", + "clear-state-action": "Clear state", + "take-offline-action": "Take offline", + "bring-online-action": "Bring online", + "take-all-offline": "Take all offline", + "bring-all-online": "Bring all online", + "mixed": "Mixed", + "column-site": "Site", + "column-status": "Status", + "column-transfer": "Transfer status / Result", + "column-action": "Action" }, "rebalancing": { "rebalancing": "Rebalancing", @@ -842,6 +861,11 @@ "description": "Access management allows you to control {{brandname}} Server administration.", "tab-roles": "Roles", "tab-access-control": "Access control", + "flush-cache-action": "Flush cache", + "flush-cache-success": "Access control list cache successfully flushed across the cluster", + "flush-cache-error": "Unexpected error flushing the access-control cache across the cluster", + "modal-flush-cache-title": "Flushing the ACL cache", + "modal-flush-cache-description": "Flush the access-control list cache across the cluster.", "roles": { "role-name": "Role name", "role-name-tooltip": "Role name prefixed with a lock indicates that the role is predefined and cannot be edited.", diff --git a/src/app/services/rolesHook.ts b/src/app/services/rolesHook.ts index 750fb099b..7425c386b 100644 --- a/src/app/services/rolesHook.ts +++ b/src/app/services/rolesHook.ts @@ -77,3 +77,22 @@ export function useDeleteRole(roleName: string, call: () => void) { onDeleteRole }; } + +export function useFlushCache(call: () => void) { + const { addAlert } = useApiAlert(); + const { t } = useTranslation(); + const onFlushCache = () => { + ConsoleServices.security() + .flushCache( + t('access-management.flush-cache-success'), + t('access-management.flush-cache-error') + ) + .then((actionResponse) => { + addAlert(actionResponse); + }) + .finally(() => call()); + }; + return { + onFlushCache + }; +} diff --git a/src/services/securityService.ts b/src/services/securityService.ts index 9a62a4b72..2064eb204 100644 --- a/src/services/securityService.ts +++ b/src/services/securityService.ts @@ -209,4 +209,17 @@ export class SecurityService { errorMessage: messageError }); } + + /** + * Flush security cache + * @param messageOk + * @param messageError + */ + public async flushCache(messageOk: string, messageError: string) { + return this.fetchCaller.post({ + url: this.endpoint + '/cache?action=flush', + successMessage: messageOk, + errorMessage: messageError + }); + } }