Skip to content

Commit

Permalink
ISPN-14695 Role list display
Browse files Browse the repository at this point in the history
  • Loading branch information
dpanshug authored and karesti committed Sep 29, 2023
1 parent 5dc4e2d commit d3c96fb
Showing 13 changed files with 438 additions and 23 deletions.
81 changes: 81 additions & 0 deletions src/app/AccessManagement/AccessManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { useState } from 'react';
import {
Card,
CardBody,
Nav,
NavItem,
NavList,
PageSection,
PageSectionVariants,
Text,
TextContent,
TextVariants
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { RoleTableDisplay } from '@app/AccessManagement/RoleTableDisplay';

const AccessManager = () => {
const { t } = useTranslation();
const brandname = t('brandname.brandname');
const [activeTabKey, setActiveTabKey] = useState('0');
const [showRoles, setShowRoles] = useState(true);
const [showAccessControl, setShowAccessControl] = useState(false);

interface AccessTab {
key: string;
name: string;
}
const handleTabClick = (nav) => {
const tabIndex = nav.itemId;
setActiveTabKey(tabIndex);
setShowRoles(tabIndex == '0');
setShowAccessControl(tabIndex == '1');
};
const buildTabs = () => {
const tabs: AccessTab[] = [
{ name: t('access-management.tab-roles'), key: '0' },
];

return (
<Nav data-cy="navigationTabs" onSelect={handleTabClick} variant={'tertiary'}>
<NavList>
{tabs.map((tab) => (
<NavItem
aria-label={'nav-item-' + tab.name}
key={'nav-item-' + tab.key}
itemId={tab.key}
isActive={activeTabKey === tab.key}
>
{tab.name}
</NavItem>
))}
</NavList>
</Nav>
);
};

const buildSelectedContent = (
<Card>
<CardBody>
{showRoles && <RoleTableDisplay />}
{showAccessControl && <div>Access Control</div>}
</CardBody>
</Card>
);

return (
<>
<PageSection variant={PageSectionVariants.light} style={{ paddingBottom: 0 }}>
<TextContent style={{ marginBottom: '1rem' }}>
<Text component={TextVariants.h1}>{t('access-management.title')}</Text>
<Text component={TextVariants.p}>{t('access-management.description', { brandname: brandname })}</Text>
</TextContent>
{buildTabs()}
</PageSection>
<PageSection variant={PageSectionVariants.default}>{buildSelectedContent}</PageSection>
</>
);
};

export { AccessManager };
186 changes: 186 additions & 0 deletions src/app/AccessManagement/RoleTableDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React, { useEffect, useState } from 'react';
import {
Bullseye,
Chip,
ChipGroup,
EmptyState,
EmptyStateBody,
EmptyStateIcon,
EmptyStateVariant,
Icon,
Pagination,
SearchInput,
Title,
Toolbar,
ToolbarContent,
ToolbarFilter,
ToolbarGroup,
ToolbarItem,
ToolbarItemVariant
} from '@patternfly/react-core';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { useTranslation } from 'react-i18next';
import { LockIcon, SearchIcon } from '@patternfly/react-icons';
import { useFetchAvailableRoles } from '@app/services/rolesHook';
import { filterRoles } from '@app/utils/searchFilter';
import { RoleFilterOption } from '@services/infinispanRefData';

const RoleTableDisplay = () => {
const { t } = useTranslation();
const brandname = t('brandname.brandname');
const { roles, loading, error } = useFetchAvailableRoles();
const [selectSearchOption, setSelectSearchOption] = useState<string>(RoleFilterOption.name);
const [searchValue, setSearchValue] = useState<string>('');
const [rolesPagination, setRolesPagination] = useState({
page: 1,
perPage: 10
});

const [filteredRoles, setFilteredRoles] = useState<Role[]>([]);

useEffect(() => {
if (searchValue !== '') {
setFilteredRoles(filterRoles(searchValue, roles, selectSearchOption));
} else {
setFilteredRoles(roles);
}
}, [searchValue, roles]);

const columnNames = {
name: t('access-management.roles.role-name'),
permissions: t('access-management.roles.permissions'),
description: t('access-management.roles.role-description')
};

const onSetPage = (_event, pageNumber) => {
setRolesPagination({
...rolesPagination,
page: pageNumber
});
};

const onPerPageSelect = (_event, perPage) => {
setRolesPagination({
page: 1,
perPage: perPage
});
};

const onSearchChange = (value: string) => {
setSearchValue(value);
};

const pagination = (
<Pagination
itemCount={roles.length}
perPage={rolesPagination.perPage}
page={rolesPagination.page}
onSetPage={onSetPage}
widgetId="pagination-roles"
onPerPageSelect={onPerPageSelect}
isCompact
/>
);

const buildSearch = (
<ToolbarGroup variant="filter-group">
<ToolbarFilter categoryName={selectSearchOption}>
<SearchInput
placeholder={`Find by ${selectSearchOption}`}
value={searchValue}
onChange={(_event, value) => onSearchChange(value)}
onSearch={(_event, value) => onSearchChange(value)}
onClear={() => setSearchValue('')}
/>
</ToolbarFilter>
</ToolbarGroup>
);

return (
<React.Fragment>
<Toolbar id="role-table-toolbar" className={'role-table-display'}>
<ToolbarContent>
{buildSearch}
<ToolbarItem variant={ToolbarItemVariant.pagination}>{pagination}</ToolbarItem>
</ToolbarContent>
</Toolbar>
<Table className={'roles-table'} aria-label={'roles-table-label'} variant={'compact'}>
<Thead noWrap>
<Tr>
<Th info={{
popover: (
<div>
{t("access-management.roles.role-name-tooltip")}
</div>
),
ariaLabel: 'Role name more information',
popoverProps: {
headerContent: columnNames.name,
footerContent: (
<a target="_blank" rel='noreferrer' href={t("brandname.default-roles-docs-link")}>
{t("access-management.roles.roles-hint-link", { brandname: brandname })}
</a>
)
}
}}>
{columnNames.name}
</Th>
<Th>{columnNames.permissions}</Th>
<Th>{columnNames.description}</Th>
</Tr>
</Thead>
<Tbody>
{filteredRoles.length == 0 ? (
<Tr>
<Td colSpan={3}>
<Bullseye>
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h2" size="lg">
{t('access-management.roles.no-roles-found')}
</Title>
<EmptyStateBody>
{roles.length == 0
? t('access-management.roles.no-roles-body')
: t('access-management.roles.no-filtered-roles-body')}
</EmptyStateBody>
</EmptyState>
</Bullseye>
</Td>
</Tr>
) : (
filteredRoles.map((row) => {
return (
<Tr key={row.name}>
<Td dataLabel={columnNames.name} width={15}>
{ row.implicit &&
<Icon size="sm" isInline>
<LockIcon className="role-icon" />
</Icon>
}
{row.name}
</Td>
<Td dataLabel={columnNames.permissions} width={30}>{
<ChipGroup>
{row.permissions.map(currentChip => (
<Chip key={currentChip} isReadOnly={true}>
{currentChip}
</Chip>
))}
</ChipGroup>
}</Td>
<Td dataLabel={columnNames.description}>{row.description}</Td>
</Tr>
);
})
)}
</Tbody>
</Table>
<Toolbar id="role-table-toolbar" className={'role-table-display'}>
<ToolbarItem variant={ToolbarItemVariant.pagination}>{pagination}</ToolbarItem>
</Toolbar>
</React.Fragment>
);
};

export { RoleTableDisplay };
23 changes: 16 additions & 7 deletions src/app/CacheManagers/CounterTableDisplay.tsx
Original file line number Diff line number Diff line change
@@ -269,6 +269,19 @@ const CounterTableDisplay = (props: { setCountersCount: (number) => void; isVisi
);
};

const buildSeparator = () => {
if (!ConsoleServices.security().hasConsoleACL(ConsoleACL.CREATE, connectedUser) ||
filteredCounters.length !== 0) {
return;
}
return (
<ToolbarItem
variant={ToolbarItemVariant.separator}
style={{ marginInline: global_spacer_sm.value }}
></ToolbarItem>
)
}

const buildCreateCounterButton = () => {
if (!ConsoleServices.security().hasConsoleACL(ConsoleACL.CREATE, connectedUser)) {
return;
@@ -375,17 +388,16 @@ const CounterTableDisplay = (props: { setCountersCount: (number) => void; isVisi
setSelectedCounterType('');
setSelectedCounterStorage('');
}}
style={{ marginBottom: '1rem' }}
>
<ToolbarContent>
<ToolbarToggleGroup data-cy="counterFilterSelect" toggleIcon={<FilterIcon />} breakpoint="xl">
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="xl">
<ToolbarFilter
data-cy="counterFilterSelect"
chips={selectedCounterType !== '' ? [selectedCounterType] : ([] as string[])}
deleteChip={() => setSelectedCounterType('')}
deleteChipGroup={() => setSelectedCounterType('')}
categoryName={t('cache-managers.counters.counter-type')}
>
<div />
</ToolbarFilter>
<ToolbarFilter
chips={selectedCounterStorage !== '' ? [selectedCounterStorage] : ([] as string[])}
@@ -398,10 +410,7 @@ const CounterTableDisplay = (props: { setCountersCount: (number) => void; isVisi
</ToolbarFilter>
</ToolbarToggleGroup>
<ToolbarItem variant="search-filter">{searchInput}</ToolbarItem>
<ToolbarItem
variant={ToolbarItemVariant.separator}
style={{ marginInline: global_spacer_sm.value }}
></ToolbarItem>
{buildSeparator()}
{buildCreateCounterButton()}
{filteredCounters.length !== 0 && (
<ToolbarItem variant={ToolbarItemVariant.pagination}>{pagination('down')}</ToolbarItem>
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ const SecuredCacheConfigurator = (props: { isEnabled: boolean }) => {
useEffect(() => {
if (loading) {
ConsoleServices.security()
.getSecurityRoles()
.getSecurityRolesNames()
.then((r) => {
if (r.isRight()) {
setAvailableRoles(r.value);
8 changes: 8 additions & 0 deletions src/app/app.css
Original file line number Diff line number Diff line change
@@ -47,3 +47,11 @@ body,
.pf-v5-c-table__action {
text-align: right;
}

.role-table-display{
margin-bottom: var(--pf-v5-global--spacer--md);
}

.role-icon{
margin-right: var(--pf-v5-global--spacer--xs);
}
20 changes: 19 additions & 1 deletion src/app/assets/languages/en.json
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
"brandname": "Infinispan",
"encoding-docs-link": "https://infinispan.org/docs/stable/titles/encoding/encoding.html",
"persistence-docs-link": "https://infinispan.org/docs/stable/titles/configuring/configuring.html#persistence",
"configuration-docs-link": "https://infinispan.org/docs/stable/titles/configuring/configuring.html"
"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"
},
"layout": {
"console-name": "Server Management Console",
@@ -814,5 +815,22 @@
"local-address": "Local address",
"null": "N/A",
"more-info": "More info"
},
"access-management": {
"title": "Access management",
"description": "Access management allows you to control {{brandname}} Server administration.",
"tab-roles": "Roles",
"tab-access-control": "Access control",
"roles": {
"role-name": "Role name",
"role-name-tooltip": "Role name prefixed with a lock indicates that the role is predefined and cannot be edited.",
"permissions": "Permissions",
"role-description": "Description",
"search-placeholder": "Find by {{searchOption}}",
"no-roles-found": "No roles found",
"no-roles-body": "You can create a role to manage access to {{brandname}} Server administration.",
"no-filtered-roles-body": "No roles match your search criteria.",
"roles-hint-link": "Learn more in the {{brandname}} documentation"
}
}
}
7 changes: 4 additions & 3 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -12,11 +12,12 @@ const App = () => {
const [init, setInit] = useState<
'SERVER_ERROR' | 'READY' | 'NOT_READY' | 'PENDING' | 'DONE' | 'LOGIN' | 'HTTP_LOGIN'
>('PENDING');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
let searchParams = new URL(window.location).searchParams;
const searchParams = new URL(window.location).searchParams;
// local dev mode basic
let user = searchParams.get('user');
let password = searchParams.get('password');
const user = searchParams.get('user');
const password = searchParams.get('password');

if (user != null && password != null) {
ConsoleServices.init(user, password);
Loading

0 comments on commit d3c96fb

Please sign in to comment.