Skip to content

Commit

Permalink
Redirect Director Metrics if Not Admin
Browse files Browse the repository at this point in the history
- Redirect director metrics if not admin
- Hide navigation options if not admin
- Update AuthenicatedContent to consume static lists to enable easier SSR
  • Loading branch information
CannonLock committed Dec 4, 2024
1 parent 1f190a1 commit 870871e
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 105 deletions.
19 changes: 12 additions & 7 deletions web_ui/frontend/app/director/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { Box } from '@mui/material';
import { ButtonLink, Sidebar } from '@/components/layout/Sidebar';
import BuildIcon from '@mui/icons-material/Build';
import Main from '@/components/layout/Main';
import { Dashboard, Equalizer, MapOutlined } from '@mui/icons-material';
import { Block, Dashboard, Equalizer, MapOutlined } from '@mui/icons-material';
import AuthenticatedContent from '@/components/layout/AuthenticatedContent';

export const metadata = {
title: 'Pelican Director',
Expand All @@ -38,15 +39,19 @@ export default function RootLayout({
<ButtonLink title={'Dashboard'} href={'/director/'}>
<Dashboard />
</ButtonLink>
<ButtonLink title={'Metrics'} href={'/director/metrics/'}>
<Equalizer />
</ButtonLink>
<ButtonLink title={'Map'} href={'/director/map/'}>
<MapOutlined />
</ButtonLink>
<ButtonLink title={'Config'} href={'/config/'}>
<BuildIcon />
</ButtonLink>
<AuthenticatedContent allowedRoles={['admin']}>
<ButtonLink title={'Metrics'} href={'/director/metrics/'}>
<Equalizer />
</ButtonLink>
</AuthenticatedContent>
<AuthenticatedContent allowedRoles={['admin']}>
<ButtonLink title={'Config'} href={'/config/'}>
<BuildIcon />
</ButtonLink>
</AuthenticatedContent>
</Sidebar>
<Main>{children}</Main>
</Box>
Expand Down
167 changes: 87 additions & 80 deletions web_ui/frontend/app/director/metrics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,96 +9,103 @@ import {
} from '@/app/director/metrics/components/MetricBoxPlot';
import { StorageTable } from '@/app/director/metrics/components/StorageTable';
import { TransferBarGraph } from '@/app/director/metrics/components/TransferBarGraph';
import AuthenticatedContent from '@/components/layout/AuthenticatedContent';

const Page = () => {
return (
<Grid container spacing={1} direction={'row'}>
<Grid item xs={5} display={'flex'}>
<Grid container spacing={1}>
{[
<ProjectTable key={'project-table'} />,
<StorageTable key={'storage-table'} />,
].map((component, index) => (
<Grid key={index} item xs={12} display={'flex'} height={'45vh'}>
<Paper sx={{ width: '100%' }}>{component}</Paper>
</Grid>
))}
</Grid>
</Grid>
<Grid item xs={7}>
<Grid container spacing={1} flexGrow={1}>
<Grid item xs={12} display={'flex'} height={'35vh'}>
<Grid container spacing={1}>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<BytesMetricBoxPlot
metric={'go_memstats_alloc_bytes'}
title={'Server Memory Usage'}
/>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<MetricBoxPlot
metric={
'avg by (server_name) (irate(process_cpu_seconds_total[${range}]))'
}
title={'CPU Usage by Core'}
/>
</Paper>
<AuthenticatedContent
allowedRoles={['admin']}
trustThenValidate={true}
redirect={true}
>
<Grid container spacing={1} direction={'row'}>
<Grid item xs={5} display={'flex'}>
<Grid container spacing={1}>
{[
<ProjectTable key={'project-table'} />,
<StorageTable key={'storage-table'} />,
].map((component, index) => (
<Grid key={index} item xs={12} display={'flex'} height={'45vh'}>
<Paper sx={{ width: '100%' }}>{component}</Paper>
</Grid>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<MetricBoxPlot
metric={
'sum by (server_name) (sum_over_time(xrootd_server_connection_count[${range}])) / sum by (server_name) (count_over_time(xrootd_server_connection_count[${range}]))'
}
title={'XRootD Server Connections'}
/>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<MetricBoxPlot
metric={
'sum by (server_name) (sum_over_time(xrootd_sched_thread_count[${range}])) / sum by (server_name) (count_over_time(xrootd_sched_thread_count[${range}]))'
}
title={'XRootD Scheduler Threads'}
/>
</Paper>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} display={'flex'} height={'32vh'}>
<Paper sx={{ width: '100%' }}>
<TransferBarGraph />
</Paper>
))}
</Grid>
<Grid item xs={12} display={'flex'} height={'23vh'}>
<Grid container>
{[
<BigBytesMetric
key={'rx'}
metric={'xrootd_server_bytes{direction="rx"}'}
title={'Bytes Received'}
color={green[300]}
/>,
<BigBytesMetric
key={'tx'}
metric={'xrootd_server_bytes{direction="tx"}'}
title={'Bytes Transferred'}
color={green[300]}
/>,
].map((component, index) => (
<Grid key={index} item xs={6} display={'flex'}>
<Paper sx={{ width: '100%' }}>{component}</Paper>
</Grid>
<Grid item xs={7}>
<Grid container spacing={1} flexGrow={1}>
<Grid item xs={12} display={'flex'} height={'35vh'}>
<Grid container spacing={1}>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<BytesMetricBoxPlot
metric={'go_memstats_alloc_bytes'}
title={'Server Memory Usage'}
/>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<MetricBoxPlot
metric={
'avg by (server_name) (irate(process_cpu_seconds_total[${range}]))'
}
title={'CPU Usage by Core'}
/>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<MetricBoxPlot
metric={
'sum by (server_name) (sum_over_time(xrootd_server_connection_count[${range}])) / sum by (server_name) (count_over_time(xrootd_server_connection_count[${range}]))'
}
title={'XRootD Server Connections'}
/>
</Paper>
</Grid>
<Grid item xs={3}>
<Paper sx={{ flexGrow: 1, height: '100%' }}>
<MetricBoxPlot
metric={
'sum by (server_name) (sum_over_time(xrootd_sched_thread_count[${range}])) / sum by (server_name) (count_over_time(xrootd_sched_thread_count[${range}]))'
}
title={'XRootD Scheduler Threads'}
/>
</Paper>
</Grid>
))}
</Grid>
</Grid>
<Grid item xs={12} display={'flex'} height={'32vh'}>
<Paper sx={{ width: '100%' }}>
<TransferBarGraph />
</Paper>
</Grid>
<Grid item xs={12} display={'flex'} height={'23vh'}>
<Grid container>
{[
<BigBytesMetric
key={'rx'}
metric={'xrootd_server_bytes{direction="rx"}'}
title={'Bytes Received'}
color={green[300]}
/>,
<BigBytesMetric
key={'tx'}
metric={'xrootd_server_bytes{direction="tx"}'}
title={'Bytes Transferred'}
color={green[300]}
/>,
].map((component, index) => (
<Grid key={index} item xs={6} display={'flex'}>
<Paper sx={{ width: '100%' }}>{component}</Paper>
</Grid>
))}
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</AuthenticatedContent>
);
};

Expand Down
2 changes: 1 addition & 1 deletion web_ui/frontend/app/origin/globus/callback/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default function Home() {
<AuthenticatedContent
boxProps={{ width: '100%' }}
redirect={true}
checkAuthentication={(u: User) => u?.role == 'admin'}
allowedRoles={['admin']}
>
<Box
pt={10}
Expand Down
5 changes: 1 addition & 4 deletions web_ui/frontend/app/origin/globus/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ import { GlobusExportTable } from '@/components/GlobusExportTable';

export default function Home() {
return (
<AuthenticatedContent
redirect={true}
checkAuthentication={(u: User) => u?.role == 'admin'}
>
<AuthenticatedContent redirect={true} allowedRoles={['admin']}>
<Box width={'100%'}>
<Typography variant='h4' mb={2}>
Globus Exports
Expand Down
5 changes: 1 addition & 4 deletions web_ui/frontend/app/origin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ export default function Home() {
};

return (
<AuthenticatedContent
redirect={true}
checkAuthentication={(u: User) => u?.role == 'admin'}
>
<AuthenticatedContent redirect={true} allowedRoles={['admin']}>
<Box width={'100%'}>
<Grid container spacing={2}>
<Grid item xs={12} lg={6}>
Expand Down
9 changes: 6 additions & 3 deletions web_ui/frontend/app/registry/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import SpeedDial, {
} from '@/components/layout/SidebarSpeedDial';
import AuthenticatedContent from '@/components/layout/AuthenticatedContent';
import { PaddedContent } from '@/components/layout';
import BuildIcon from '@mui/icons-material/Build';

export const metadata = {
title: 'Pelican Registry',
Expand Down Expand Up @@ -81,9 +82,11 @@ export default function RootLayout({
<Block />
</ButtonLink>
</AuthenticatedContent>
<ButtonLink title={'Config'} href={'/config/'}>
<Build />
</ButtonLink>
<AuthenticatedContent allowedRoles={['admin']}>
<ButtonLink title={'Config'} href={'/config/'}>
<BuildIcon />
</ButtonLink>
</AuthenticatedContent>
</Sidebar>
<Main>
<PaddedContent>{children}</PaddedContent>
Expand Down
24 changes: 18 additions & 6 deletions web_ui/frontend/components/layout/AuthenticatedContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,30 @@ interface AuthenticatedContentProps {
promptLogin?: boolean;
redirect?: boolean;
trustThenValidate?: boolean;
children: React.ReactNode;
boxProps?: BoxProps;
checkAuthentication?: (user: User) => boolean;
allowedRoles?: User['role'][];
replace?: boolean;
children: React.ReactNode;
}

/**
* AuthenticatedContent is a component that will show the children if the user is authenticated.
* @param promptLogin If true then the user will be prompted to login if they are not authenticated
* @param redirect If true then the user will be redirected to the login page if they are not authenticated
* @param trustThenValidate If true then the user will be shown the content if they are not authenticated but will be validated after
* @param boxProps The props to pass to the Box component
* @param allowedRoles The roles that are allowed to see the content
* @param replace If true then the
* @param children The content to show if the user is authenticated
* @constructor
*/
const AuthenticatedContent = ({
promptLogin = false,
redirect = false,
trustThenValidate = false,
children,
boxProps,
checkAuthentication,
allowedRoles,
}: AuthenticatedContentProps) => {
if (redirect && promptLogin) {
throw new Error('redirect XOR promptLogin must be true');
Expand All @@ -66,12 +78,12 @@ const AuthenticatedContent = ({
const [pageUrl, setPageUrl] = useState<string>('');

const authenticated = useMemo(() => {
if (data && checkAuthentication) {
return checkAuthentication(data);
if (data && allowedRoles) {
return data?.role && allowedRoles.includes(data?.role);
} else {
return !!data?.authenticated;
}
}, [data, checkAuthentication]);
}, [data, allowedRoles]);

useEffect(() => {
// Keep pathname as is since backend handles the redirect after logging in and needs the full path
Expand Down

0 comments on commit 870871e

Please sign in to comment.