1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-15 17:50:48 +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 { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { ApiTokenTable } from 'component/common/ApiTokenTable/ApiTokenTable'; import { ApiTokenTable } from 'component/common/ApiTokenTable/ApiTokenTable';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
@ -24,7 +21,6 @@ import {
} from '@server/types/permissions'; } from '@server/types/permissions';
export const ApiTokenPage = () => { export const ApiTokenPage = () => {
const { hasAccess } = useContext(AccessContext);
const { tokens, loading, refetch } = useApiTokens(); const { tokens, loading, refetch } = useApiTokens();
const { deleteToken } = useApiTokensApi(); const { deleteToken } = useApiTokensApi();
@ -71,51 +67,49 @@ export const ApiTokenPage = () => {
}); });
return ( return (
<ConditionallyRender <PermissionGuard
condition={hasAccess([ permissions={[
READ_CLIENT_API_TOKEN, READ_CLIENT_API_TOKEN,
READ_FRONTEND_API_TOKEN, READ_FRONTEND_API_TOKEN,
ADMIN, ADMIN,
])} ]}
show={() => ( >
<PageContent <PageContent
header={ header={
<PageHeader <PageHeader
title={`API access (${rows.length})`} title={`API access (${rows.length})`}
actions={ actions={
<> <>
<Search <Search
initialValue={globalFilter} initialValue={globalFilter}
onChange={setGlobalFilter} onChange={setGlobalFilter}
/> />
<PageHeader.Divider /> <PageHeader.Divider />
<CreateApiTokenButton <CreateApiTokenButton
permission={[ permission={[
CREATE_FRONTEND_API_TOKEN, CREATE_FRONTEND_API_TOKEN,
CREATE_CLIENT_API_TOKEN, CREATE_CLIENT_API_TOKEN,
ADMIN, ADMIN,
]} ]}
path="/admin/api/create-token" 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> }
)} >
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 { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -8,6 +7,8 @@ import { SamlAuth } from './SamlAuth/SamlAuth';
import { PasswordAuth } from './PasswordAuth/PasswordAuth'; import { PasswordAuth } from './PasswordAuth/PasswordAuth';
import { GoogleAuth } from './GoogleAuth/GoogleAuth'; import { GoogleAuth } from './GoogleAuth/GoogleAuth';
import { TabNav } from 'component/common/TabNav/TabNav/TabNav'; import { TabNav } from 'component/common/TabNav/TabNav/TabNav';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { ADMIN } from '@server/types/permissions';
export const AuthSettings = () => { export const AuthSettings = () => {
const { authenticationType } = useUiConfig().uiConfig; const { authenticationType } = useUiConfig().uiConfig;
@ -36,51 +37,54 @@ export const AuthSettings = () => {
return ( return (
<div> <div>
<PageContent header="Single Sign-On"> <PermissionGuard permissions={ADMIN}>
<ConditionallyRender <PageContent header="Single Sign-On">
condition={authenticationType === 'enterprise'} <ConditionallyRender
show={<TabNav tabData={tabs} />} condition={authenticationType === 'enterprise'}
/> show={<TabNav tabData={tabs} />}
<ConditionallyRender />
condition={authenticationType === 'open-source'} <ConditionallyRender
show={ condition={authenticationType === 'open-source'}
<Alert severity="warning"> show={
You are running the open-source version of Unleash. <Alert severity="warning">
You have to use the Enterprise edition in order You are running the open-source version of
configure Single Sign-on. Unleash. You have to use the Enterprise edition
</Alert> in order configure Single Sign-on.
} </Alert>
/> }
<ConditionallyRender />
condition={authenticationType === 'demo'} <ConditionallyRender
show={ condition={authenticationType === 'demo'}
<Alert severity="warning"> show={
You are running Unleash in demo mode. You have to <Alert severity="warning">
use the Enterprise edition in order configure Single You are running Unleash in demo mode. You have
Sign-on. to use the Enterprise edition in order configure
</Alert> Single Sign-on.
} </Alert>
/> }
<ConditionallyRender />
condition={authenticationType === 'custom'} <ConditionallyRender
show={ condition={authenticationType === 'custom'}
<Alert severity="warning"> show={
You have decided to use custom authentication type. <Alert severity="warning">
You have to use the Enterprise edition in order You have decided to use custom authentication
configure Single Sign-on from the user interface. type. You have to use the Enterprise edition in
</Alert> order configure Single Sign-on from the user
} interface.
/> </Alert>
<ConditionallyRender }
condition={authenticationType === 'hosted'} />
show={ <ConditionallyRender
<Alert severity="info"> condition={authenticationType === 'hosted'}
Your Unleash instance is managed by the Unleash show={
team. <Alert severity="info">
</Alert> Your Unleash instance is managed by the Unleash
} team.
/> </Alert>
</PageContent> }
/>
</PageContent>
</PermissionGuard>
</div> </div>
); );
}; };

View File

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

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Button, Button,
FormControl, FormControl,
@ -12,8 +12,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import { Alert } from '@mui/material'; import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent'; 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 { AutoCreateForm } from '../AutoCreateForm/AutoCreateForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useAuthSettingsApi from 'hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi'; import useAuthSettingsApi from 'hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi';
@ -41,7 +39,6 @@ export const OidcAuth = () => {
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const [data, setData] = useState(initialState); const [data, setData] = useState(initialState);
const { hasAccess } = useContext(AccessContext);
const { config } = useAuthSettings('oidc'); const { config } = useAuthSettings('oidc');
const { updateSettings, errors, loading } = useAuthSettingsApi('oidc'); const { updateSettings, errors, loading } = useAuthSettingsApi('oidc');
@ -51,14 +48,6 @@ export const OidcAuth = () => {
} }
}, [config]); }, [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>) => { const updateField = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.name, event.target.value); 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 { Button, FormControlLabel, Grid, Switch } from '@mui/material';
import { Alert } from '@mui/material'; import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent'; 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 useAuthSettings from 'hooks/api/getters/useAuthSettings/useAuthSettings';
import useAuthSettingsApi, { import useAuthSettingsApi, {
ISimpleAuthSettings, ISimpleAuthSettings,
@ -22,7 +20,6 @@ export const PasswordAuth = () => {
useState<boolean>(false); useState<boolean>(false);
const { updateSettings, errors, loading } = const { updateSettings, errors, loading } =
useAuthSettingsApi<ISimpleAuthSettings>('simple'); useAuthSettingsApi<ISimpleAuthSettings>('simple');
const { hasAccess } = useContext(AccessContext);
const [confirmationOpen, setConfirmationOpen] = useState(false); const [confirmationOpen, setConfirmationOpen] = useState(false);
const { data: adminCount } = useAdminCount(); const { data: adminCount } = useAdminCount();
const { tokens } = useApiTokens(); const { tokens } = useApiTokens();
@ -31,14 +28,6 @@ export const PasswordAuth = () => {
setDisablePasswordAuth(!!config.disabled); setDisablePasswordAuth(!!config.disabled);
}, [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 = () => { const updateDisabled = () => {
setDisablePasswordAuth(!disablePasswordAuth); setDisablePasswordAuth(!disablePasswordAuth);
}; };

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Button, Button,
FormControlLabel, FormControlLabel,
@ -8,8 +8,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import { Alert } from '@mui/material'; import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent'; 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 { AutoCreateForm } from '../AutoCreateForm/AutoCreateForm';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@ -36,7 +34,6 @@ export const SamlAuth = () => {
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const [data, setData] = useState(initialState); const [data, setData] = useState(initialState);
const { hasAccess } = useContext(AccessContext);
const { config } = useAuthSettings('saml'); const { config } = useAuthSettings('saml');
const { updateSettings, errors, loading } = useAuthSettingsApi('saml'); const { updateSettings, errors, loading } = useAuthSettingsApi('saml');
@ -46,14 +43,6 @@ export const SamlAuth = () => {
} }
}, [config]); }, [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>) => { const updateField = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.name, event.target.value); setValue(event.target.name, event.target.value);
}; };

View File

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

View File

@ -1,6 +1,6 @@
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; 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 FlaggedBillingRedirect = () => {
const { uiConfig, loading } = useUiConfig(); const { uiConfig, loading } = useUiConfig();

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
import { useContext, useState } from 'react'; import { useState } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { RolesTable } from './RolesTable/RolesTable'; 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 { PageContent } from 'component/common/PageContent/PageContent';
import { Tab, Tabs, styled, useMediaQuery } from '@mui/material'; import { Tab, Tabs, styled, useMediaQuery } from '@mui/material';
import { Route, Routes, useLocation } from 'react-router-dom'; import { Route, Routes, useLocation } from 'react-router-dom';
@ -45,7 +44,6 @@ const StyledActions = styled('div')({
export const Roles = () => { export const Roles = () => {
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const { hasAccess } = useContext(AccessContext);
const { pathname } = useLocation(); const { pathname } = useLocation();
const { roles, projectRoles, loading } = useRoles(); const { roles, projectRoles, loading } = useRoles();
@ -84,122 +82,109 @@ export const Roles = () => {
return ( return (
<div> <div>
<ConditionallyRender <PermissionGuard permissions={[READ_ROLE, ADMIN]}>
condition={hasAccess(READ_ROLE)} <StyledPageContent
show={ headerClass="page-header"
<StyledPageContent bodyClass="page-body"
headerClass="page-header" isLoading={loading}
bodyClass="page-body" header={
isLoading={loading} <>
header={ <StyledHeader>
<> <StyledTabsContainer>
<StyledHeader> <Tabs
<StyledTabsContainer> value={pathname}
<Tabs indicatorColor="primary"
value={pathname} textColor="primary"
indicatorColor="primary" variant="scrollable"
textColor="primary" allowScrollButtonsMobile
variant="scrollable" >
allowScrollButtonsMobile {tabs.map(({ label, path, total }) => (
> <Tab
{tabs.map( key={label}
({ label, path, total }) => ( value={path}
<Tab label={
key={label} <CenteredNavLink to={path}>
value={path} <span>
label={ {label} ({total})
<CenteredNavLink </span>
to={path} </CenteredNavLink>
> }
<span> />
{label} ( ))}
{total}) </Tabs>
</span> </StyledTabsContainer>
</CenteredNavLink> <StyledActions>
} <ConditionallyRender
/> condition={!isSmallScreen}
) show={
)} <>
</Tabs> <Search
</StyledTabsContainer> initialValue={searchValue}
<StyledActions> onChange={setSearchValue}
<ConditionallyRender />
condition={!isSmallScreen} <PageHeader.Divider />
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
} }
searchValue={searchValue} />
modalOpen={modalOpen} <ResponsiveButton
setModalOpen={setModalOpen} onClick={() => {
selectedRole={selectedRole} setSelectedRole(undefined);
setSelectedRole={setSelectedRole} 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> </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 { 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'; import { ServiceAccountsTable } from './ServiceAccountsTable/ServiceAccountsTable';
export const ServiceAccounts = () => { export const ServiceAccounts = () => (
const { hasAccess } = useContext(AccessContext); <div>
<PermissionGuard permissions={ADMIN}>
return ( <ServiceAccountsTable />
<div> </PermissionGuard>
<ConditionallyRender </div>
condition={hasAccess(ADMIN)} );
show={<ServiceAccountsTable />}
elseShow={<AdminAlert />}
/>
</div>
);
};

View File

@ -1,24 +1,15 @@
import { useContext } from 'react';
import UsersList from './UsersList/UsersList'; 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 { 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'; import { InviteLinkBar } from './InviteLinkBar/InviteLinkBar';
export const UsersAdmin = () => { export const UsersAdmin = () => (
const { hasAccess } = useContext(AccessContext); <div>
<InviteLinkBar />
return ( <PermissionGuard permissions={ADMIN}>
<div> <UsersList />
<InviteLinkBar /> </PermissionGuard>
<ConditionallyRender </div>
condition={hasAccess(ADMIN)} );
show={<UsersList />}
elseShow={<AdminAlert />}
/>
</div>
);
};
export default UsersAdmin; 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 { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import AccessContext from 'contexts/AccessContext';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { EventLog } from 'component/events/EventLog/EventLog'; import { EventLog } from 'component/events/EventLog/EventLog';
export const EventPage = () => { export const EventPage = () => (
const { hasAccess } = useContext(AccessContext); <PermissionGuard permissions={ADMIN}>
<EventLog title="Event log" />
return ( </PermissionGuard>
<ConditionallyRender );
condition={hasAccess(ADMIN)}
show={() => <EventLog title="Event log" />}
elseShow={<AdminAlert />}
/>
);
};

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 { 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'; import { LoginHistoryTable } from './LoginHistoryTable/LoginHistoryTable';
export const LoginHistory = () => { export const LoginHistory = () => (
const { hasAccess } = useContext(AccessContext); <div>
<PermissionGuard permissions={ADMIN}>
return ( <LoginHistoryTable />
<div> </PermissionGuard>
<ConditionallyRender </div>
condition={hasAccess(ADMIN)} );
show={<LoginHistoryTable />}
elseShow={<AdminAlert />}
/>
</div>
);
};