1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: upgrade AdminAlert to PermissionGuard (#4074)

https://linear.app/unleash/issue/2-1165/improve-adminalert-usage-to-be-more-generic-accept-non-admin

Upgrades our `AdminAlert` to a new `PermissionGuard`.

**Question**: We don't **need** to, but **should** we be specific about
the `ADMIN` permission every time?
Technically `PermissionGuard` could have `permissions` as optional and
assume `[]` by default, which will add `ADMIN` anyways. However, I feel
like we may gain some readability if we're specific about it. WDYT?

Single permission:

![image](https://github.com/Unleash/unleash/assets/14320932/eab414ae-e798-4ab6-ba96-cde2977dc98b)

Multiple permissions:

![image](https://github.com/Unleash/unleash/assets/14320932/25302442-8fcc-4aa1-9525-d54f5f9350af)
This commit is contained in:
Nuno Góis 2023-06-23 13:25:35 +01:00 committed by GitHub
parent d2a98d0338
commit 95a0c7748f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 324 additions and 419 deletions

View File

@ -1,7 +1,4 @@
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { ApiTokenTable } from 'component/common/ApiTokenTable/ApiTokenTable';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
@ -24,7 +21,6 @@ import {
} from '@server/types/permissions';
export const ApiTokenPage = () => {
const { hasAccess } = useContext(AccessContext);
const { tokens, loading, refetch } = useApiTokens();
const { deleteToken } = useApiTokensApi();
@ -71,51 +67,49 @@ export const ApiTokenPage = () => {
});
return (
<ConditionallyRender
condition={hasAccess([
<PermissionGuard
permissions={[
READ_CLIENT_API_TOKEN,
READ_FRONTEND_API_TOKEN,
ADMIN,
])}
show={() => (
<PageContent
header={
<PageHeader
title={`API access (${rows.length})`}
actions={
<>
<Search
initialValue={globalFilter}
onChange={setGlobalFilter}
/>
<PageHeader.Divider />
<CreateApiTokenButton
permission={[
CREATE_FRONTEND_API_TOKEN,
CREATE_CLIENT_API_TOKEN,
ADMIN,
]}
path="/admin/api/create-token"
/>
</>
}
/>
}
>
<ApiTokenTable
loading={loading}
headerGroups={headerGroups}
setHiddenColumns={setHiddenColumns}
prepareRow={prepareRow}
getTableBodyProps={getTableBodyProps}
getTableProps={getTableProps}
rows={rows}
columns={columns}
globalFilter={globalFilter}
]}
>
<PageContent
header={
<PageHeader
title={`API access (${rows.length})`}
actions={
<>
<Search
initialValue={globalFilter}
onChange={setGlobalFilter}
/>
<PageHeader.Divider />
<CreateApiTokenButton
permission={[
CREATE_FRONTEND_API_TOKEN,
CREATE_CLIENT_API_TOKEN,
ADMIN,
]}
path="/admin/api/create-token"
/>
</>
}
/>
</PageContent>
)}
elseShow={() => <AdminAlert />}
/>
}
>
<ApiTokenTable
loading={loading}
headerGroups={headerGroups}
setHiddenColumns={setHiddenColumns}
prepareRow={prepareRow}
getTableBodyProps={getTableBodyProps}
getTableProps={getTableProps}
rows={rows}
columns={columns}
globalFilter={globalFilter}
/>
</PageContent>
</PermissionGuard>
);
};

View File

@ -1,4 +1,3 @@
import React from 'react';
import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -8,6 +7,8 @@ import { SamlAuth } from './SamlAuth/SamlAuth';
import { PasswordAuth } from './PasswordAuth/PasswordAuth';
import { GoogleAuth } from './GoogleAuth/GoogleAuth';
import { TabNav } from 'component/common/TabNav/TabNav/TabNav';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { ADMIN } from '@server/types/permissions';
export const AuthSettings = () => {
const { authenticationType } = useUiConfig().uiConfig;
@ -36,51 +37,54 @@ export const AuthSettings = () => {
return (
<div>
<PageContent header="Single Sign-On">
<ConditionallyRender
condition={authenticationType === 'enterprise'}
show={<TabNav tabData={tabs} />}
/>
<ConditionallyRender
condition={authenticationType === 'open-source'}
show={
<Alert severity="warning">
You are running the open-source version of Unleash.
You have to use the Enterprise edition in order
configure Single Sign-on.
</Alert>
}
/>
<ConditionallyRender
condition={authenticationType === 'demo'}
show={
<Alert severity="warning">
You are running Unleash in demo mode. You have to
use the Enterprise edition in order configure Single
Sign-on.
</Alert>
}
/>
<ConditionallyRender
condition={authenticationType === 'custom'}
show={
<Alert severity="warning">
You have decided to use custom authentication type.
You have to use the Enterprise edition in order
configure Single Sign-on from the user interface.
</Alert>
}
/>
<ConditionallyRender
condition={authenticationType === 'hosted'}
show={
<Alert severity="info">
Your Unleash instance is managed by the Unleash
team.
</Alert>
}
/>
</PageContent>
<PermissionGuard permissions={ADMIN}>
<PageContent header="Single Sign-On">
<ConditionallyRender
condition={authenticationType === 'enterprise'}
show={<TabNav tabData={tabs} />}
/>
<ConditionallyRender
condition={authenticationType === 'open-source'}
show={
<Alert severity="warning">
You are running the open-source version of
Unleash. You have to use the Enterprise edition
in order configure Single Sign-on.
</Alert>
}
/>
<ConditionallyRender
condition={authenticationType === 'demo'}
show={
<Alert severity="warning">
You are running Unleash in demo mode. You have
to use the Enterprise edition in order configure
Single Sign-on.
</Alert>
}
/>
<ConditionallyRender
condition={authenticationType === 'custom'}
show={
<Alert severity="warning">
You have decided to use custom authentication
type. You have to use the Enterprise edition in
order configure Single Sign-on from the user
interface.
</Alert>
}
/>
<ConditionallyRender
condition={authenticationType === 'hosted'}
show={
<Alert severity="info">
Your Unleash instance is managed by the Unleash
team.
</Alert>
}
/>
</PageContent>
</PermissionGuard>
</div>
);
};

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Box,
Button,
@ -9,8 +9,6 @@ import {
} from '@mui/material';
import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useAuthSettings from 'hooks/api/getters/useAuthSettings/useAuthSettings';
import useAuthSettingsApi from 'hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi';
@ -31,7 +29,6 @@ export const GoogleAuth = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const [data, setData] = useState(initialState);
const { hasAccess } = useContext(AccessContext);
const { config } = useAuthSettings('google');
const { updateSettings, errors, loading } = useAuthSettingsApi('google');
@ -41,10 +38,6 @@ export const GoogleAuth = () => {
}
}, [config]);
if (!hasAccess(ADMIN)) {
return <span>You need admin privileges to access this section.</span>;
}
const updateField = (event: React.ChangeEvent<HTMLInputElement>) => {
setData({
...data,

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Button,
FormControl,
@ -12,8 +12,6 @@ import {
} from '@mui/material';
import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AutoCreateForm } from '../AutoCreateForm/AutoCreateForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useAuthSettingsApi from 'hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi';
@ -41,7 +39,6 @@ export const OidcAuth = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const [data, setData] = useState(initialState);
const { hasAccess } = useContext(AccessContext);
const { config } = useAuthSettings('oidc');
const { updateSettings, errors, loading } = useAuthSettingsApi('oidc');
@ -51,14 +48,6 @@ export const OidcAuth = () => {
}
}, [config]);
if (!hasAccess(ADMIN)) {
return (
<Alert severity="error">
You need to be a root admin to access this section.
</Alert>
);
}
const updateField = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.name, event.target.value);
};

View File

@ -1,9 +1,7 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button, FormControlLabel, Grid, Switch } from '@mui/material';
import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import useAuthSettings from 'hooks/api/getters/useAuthSettings/useAuthSettings';
import useAuthSettingsApi, {
ISimpleAuthSettings,
@ -22,7 +20,6 @@ export const PasswordAuth = () => {
useState<boolean>(false);
const { updateSettings, errors, loading } =
useAuthSettingsApi<ISimpleAuthSettings>('simple');
const { hasAccess } = useContext(AccessContext);
const [confirmationOpen, setConfirmationOpen] = useState(false);
const { data: adminCount } = useAdminCount();
const { tokens } = useApiTokens();
@ -31,14 +28,6 @@ export const PasswordAuth = () => {
setDisablePasswordAuth(!!config.disabled);
}, [config.disabled]);
if (!hasAccess(ADMIN)) {
return (
<Alert severity="error">
You need to be a root admin to access this section.
</Alert>
);
}
const updateDisabled = () => {
setDisablePasswordAuth(!disablePasswordAuth);
};

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Button,
FormControlLabel,
@ -8,8 +8,6 @@ import {
} from '@mui/material';
import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AutoCreateForm } from '../AutoCreateForm/AutoCreateForm';
import useToast from 'hooks/useToast';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@ -36,7 +34,6 @@ export const SamlAuth = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const [data, setData] = useState(initialState);
const { hasAccess } = useContext(AccessContext);
const { config } = useAuthSettings('saml');
const { updateSettings, errors, loading } = useAuthSettingsApi('saml');
@ -46,14 +43,6 @@ export const SamlAuth = () => {
}
}, [config]);
if (!hasAccess(ADMIN)) {
return (
<Alert severity="error">
You need to be a root admin to access this section.
</Alert>
);
}
const updateField = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.name, event.target.value);
};

View File

@ -1,9 +1,8 @@
import { PageContent } from 'component/common/PageContent/PageContent';
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AccessContext from 'contexts/AccessContext';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
import { Alert } from '@mui/material';
import { BillingDashboard } from './BillingDashboard/BillingDashboard';
@ -19,7 +18,6 @@ export const Billing = () => {
loading,
} = useInstanceStatus();
const { invoices } = useInvoices();
const { hasAccess } = useContext(AccessContext);
useEffect(() => {
const hardRefresh = async () => {
@ -35,18 +33,14 @@ export const Billing = () => {
<ConditionallyRender
condition={isBilling}
show={
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={() => (
<>
<BillingDashboard
instanceStatus={instanceStatus!}
/>
<BillingHistory data={invoices} />
</>
)}
elseShow={() => <AdminAlert />}
/>
<PermissionGuard permissions={ADMIN}>
<>
<BillingDashboard
instanceStatus={instanceStatus!}
/>
<BillingHistory data={invoices} />
</>
</PermissionGuard>
}
elseShow={
<Alert severity="error">

View File

@ -1,6 +1,6 @@
import { Navigate } from 'react-router-dom';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import InvoiceAdminPage from 'component/admin/invoice/InvoiceAdminPage';
import { InvoiceAdminPage } from 'component/admin/invoice/InvoiceAdminPage';
const FlaggedBillingRedirect = () => {
const { uiConfig, loading } = useUiConfig();

View File

@ -1,8 +1,5 @@
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import AccessContext from 'contexts/AccessContext';
import React, { useContext } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { Box } from '@mui/material';
@ -10,19 +7,13 @@ import { CorsHelpAlert } from 'component/admin/cors/CorsHelpAlert';
import { CorsForm } from 'component/admin/cors/CorsForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
export const CorsAdmin = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<CorsPage />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export const CorsAdmin = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<CorsPage />
</PermissionGuard>
</div>
);
const CorsPage = () => {
const { uiConfig, loading } = useUiConfig();

View File

@ -1,20 +1,11 @@
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { GroupsList } from './GroupsList/GroupsList';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from '@server/types/permissions';
export const GroupsAdmin = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<GroupsList />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export const GroupsAdmin = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<GroupsList />
</PermissionGuard>
</div>
);

View File

@ -1,25 +1,11 @@
import { useContext } from 'react';
import InvoiceList from './InvoiceList';
import AccessContext from 'contexts/AccessContext';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Alert } from '@mui/material';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
const InvoiceAdminPage = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<InvoiceList />}
elseShow={
<Alert severity="error">
You need to be instance admin to access this section.
</Alert>
}
/>
</div>
);
};
export default InvoiceAdminPage;
export const InvoiceAdminPage = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<InvoiceList />
</PermissionGuard>
</div>
);

View File

@ -1,8 +1,5 @@
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import AccessContext from 'contexts/AccessContext';
import React, { useContext } from 'react';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { Box, styled } from '@mui/material';
@ -10,19 +7,13 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { MaintenanceTooltip } from './MaintenanceTooltip';
import { MaintenanceToggle } from './MaintenanceToggle';
export const MaintenanceAdmin = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<MaintenancePage />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export const MaintenanceAdmin = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<MaintenancePage />
</PermissionGuard>
</div>
);
const StyledBox = styled(Box)(({ theme }) => ({
display: 'grid',

View File

@ -1,9 +1,8 @@
import { useContext, useState } from 'react';
import AccessContext from 'contexts/AccessContext';
import { useState } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { RolesTable } from './RolesTable/RolesTable';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { PageContent } from 'component/common/PageContent/PageContent';
import { Tab, Tabs, styled, useMediaQuery } from '@mui/material';
import { Route, Routes, useLocation } from 'react-router-dom';
@ -45,7 +44,6 @@ const StyledActions = styled('div')({
export const Roles = () => {
const { uiConfig } = useUiConfig();
const { hasAccess } = useContext(AccessContext);
const { pathname } = useLocation();
const { roles, projectRoles, loading } = useRoles();
@ -84,122 +82,109 @@ export const Roles = () => {
return (
<div>
<ConditionallyRender
condition={hasAccess(READ_ROLE)}
show={
<StyledPageContent
headerClass="page-header"
bodyClass="page-body"
isLoading={loading}
header={
<>
<StyledHeader>
<StyledTabsContainer>
<Tabs
value={pathname}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
allowScrollButtonsMobile
>
{tabs.map(
({ label, path, total }) => (
<Tab
key={label}
value={path}
label={
<CenteredNavLink
to={path}
>
<span>
{label} (
{total})
</span>
</CenteredNavLink>
}
/>
)
)}
</Tabs>
</StyledTabsContainer>
<StyledActions>
<ConditionallyRender
condition={!isSmallScreen}
show={
<>
<Search
initialValue={
searchValue
}
onChange={
setSearchValue
}
/>
<PageHeader.Divider />
</>
}
/>
<ResponsiveButton
onClick={() => {
setSelectedRole(undefined);
setModalOpen(true);
}}
maxWidth={`${theme.breakpoints.values['sm']}px`}
Icon={Add}
permission={ADMIN}
>
New {type} role
</ResponsiveButton>
</StyledActions>
</StyledHeader>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
}
/>
</>
}
>
<Routes>
<Route
path="project-roles"
element={
<RolesTable
type={PROJECT_ROLE_TYPE}
searchValue={searchValue}
modalOpen={modalOpen}
setModalOpen={setModalOpen}
selectedRole={selectedRole}
setSelectedRole={setSelectedRole}
/>
}
/>
<Route
path="*"
element={
<RolesTable
type={
uiConfig.flags.customRootRoles
? ROOT_ROLE_TYPE
: PROJECT_ROLE_TYPE
<PermissionGuard permissions={[READ_ROLE, ADMIN]}>
<StyledPageContent
headerClass="page-header"
bodyClass="page-body"
isLoading={loading}
header={
<>
<StyledHeader>
<StyledTabsContainer>
<Tabs
value={pathname}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
allowScrollButtonsMobile
>
{tabs.map(({ label, path, total }) => (
<Tab
key={label}
value={path}
label={
<CenteredNavLink to={path}>
<span>
{label} ({total})
</span>
</CenteredNavLink>
}
/>
))}
</Tabs>
</StyledTabsContainer>
<StyledActions>
<ConditionallyRender
condition={!isSmallScreen}
show={
<>
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
<PageHeader.Divider />
</>
}
searchValue={searchValue}
modalOpen={modalOpen}
setModalOpen={setModalOpen}
selectedRole={selectedRole}
setSelectedRole={setSelectedRole}
/>
<ResponsiveButton
onClick={() => {
setSelectedRole(undefined);
setModalOpen(true);
}}
maxWidth={`${theme.breakpoints.values['sm']}px`}
Icon={Add}
permission={ADMIN}
>
New {type} role
</ResponsiveButton>
</StyledActions>
</StyledHeader>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
}
/>
</Routes>
</StyledPageContent>
}
elseShow={<AdminAlert />}
/>
</>
}
>
<Routes>
<Route
path="project-roles"
element={
<RolesTable
type={PROJECT_ROLE_TYPE}
searchValue={searchValue}
modalOpen={modalOpen}
setModalOpen={setModalOpen}
selectedRole={selectedRole}
setSelectedRole={setSelectedRole}
/>
}
/>
<Route
path="*"
element={
<RolesTable
type={
uiConfig.flags.customRootRoles
? ROOT_ROLE_TYPE
: PROJECT_ROLE_TYPE
}
searchValue={searchValue}
modalOpen={modalOpen}
setModalOpen={setModalOpen}
selectedRole={selectedRole}
setSelectedRole={setSelectedRole}
/>
}
/>
</Routes>
</StyledPageContent>
</PermissionGuard>
</div>
);
};

View File

@ -1,20 +1,11 @@
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { ServiceAccountsTable } from './ServiceAccountsTable/ServiceAccountsTable';
export const ServiceAccounts = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<ServiceAccountsTable />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export const ServiceAccounts = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<ServiceAccountsTable />
</PermissionGuard>
</div>
);

View File

@ -1,24 +1,15 @@
import { useContext } from 'react';
import UsersList from './UsersList/UsersList';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { InviteLinkBar } from './InviteLinkBar/InviteLinkBar';
export const UsersAdmin = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<InviteLinkBar />
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<UsersList />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export const UsersAdmin = () => (
<div>
<InviteLinkBar />
<PermissionGuard permissions={ADMIN}>
<UsersList />
</PermissionGuard>
</div>
);
export default UsersAdmin;

View File

@ -1,9 +0,0 @@
import { Alert } from '@mui/material';
export const AdminAlert = () => {
return (
<Alert severity="error">
You need instance admin to access this section.
</Alert>
);
};

View File

@ -0,0 +1,54 @@
import { useContext } from 'react';
import { Alert, styled } from '@mui/material';
import { ADMIN } from '@server/types/permissions';
import AccessContext from 'contexts/AccessContext';
const StyledList = styled('ul')(({ theme }) => ({
paddingInlineStart: theme.spacing(2),
}));
interface IPermissionGuardProps {
permissions: string | string[];
children: JSX.Element;
}
export const PermissionGuard = ({
permissions,
children,
}: IPermissionGuardProps) => {
const { hasAccess } = useContext(AccessContext);
const permissionsArray = Array.isArray(permissions)
? permissions
: [permissions];
if (!permissionsArray.includes(ADMIN)) {
permissionsArray.push(ADMIN);
}
if (hasAccess(permissionsArray)) {
return children;
}
if (permissionsArray.length === 1) {
return (
<Alert severity="error">
You need the <strong>{permissionsArray[0]}</strong> permission
to access this section.
</Alert>
);
}
return (
<Alert severity="error">
You need one of the following permissions to access this section:
<StyledList>
{permissionsArray.sort().map(permission => (
<li key={permission}>
<strong>{permission}</strong>
</li>
))}
</StyledList>
</Alert>
);
};

View File

@ -1,18 +1,9 @@
import React, { useContext } from 'react';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AccessContext from 'contexts/AccessContext';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { EventLog } from 'component/events/EventLog/EventLog';
export const EventPage = () => {
const { hasAccess } = useContext(AccessContext);
return (
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={() => <EventLog title="Event log" />}
elseShow={<AdminAlert />}
/>
);
};
export const EventPage = () => (
<PermissionGuard permissions={ADMIN}>
<EventLog title="Event log" />
</PermissionGuard>
);

View File

@ -1,20 +1,11 @@
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { LoginHistoryTable } from './LoginHistoryTable/LoginHistoryTable';
export const LoginHistory = () => {
const { hasAccess } = useContext(AccessContext);
return (
<div>
<ConditionallyRender
condition={hasAccess(ADMIN)}
show={<LoginHistoryTable />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export const LoginHistory = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<LoginHistoryTable />
</PermissionGuard>
</div>
);