From 0ae9ead8f59ed809927d5dd6a91f41e37fb3b5fa Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Mon, 9 Dec 2024 11:18:59 -0600 Subject: [PATCH 01/10] Update Existing Navigation - Added global config - Created Navigation Component to consume config Features: - Configuration and future shared pages will use server navigation if only one server is active - Canonicalized showing navigation based on authorization level and export type. Easy to add future updates. --- web_ui/frontend/app/cache/layout.tsx | 20 +- web_ui/frontend/app/config/Config.tsx | 198 +++++++----------- web_ui/frontend/app/config/layout.tsx | 7 +- web_ui/frontend/app/director/layout.tsx | 20 +- web_ui/frontend/app/layout.tsx | 1 - web_ui/frontend/app/navigation.tsx | 87 ++++++++ web_ui/frontend/app/origin/layout.tsx | 5 +- web_ui/frontend/app/registry/layout.tsx | 60 +----- .../frontend/components/DataExportTable.tsx | 2 +- .../layout/Navigation/AppBar/AppBar.tsx | 166 +++++++++++++++ .../layout/Navigation/Navigation.tsx | 50 +++++ .../{ => Navigation}/Sidebar/AboutMenu.tsx | 5 +- .../layout/Navigation/Sidebar/Menu.tsx | 86 ++++++++ .../Navigation/Sidebar/NavigationItem.tsx | 76 +++++++ .../{ => Navigation}/Sidebar/Sidebar.tsx | 17 +- .../{ => Navigation}/Sidebar/UserMenu.tsx | 0 .../layout/Navigation/Sidebar/index.tsx | 1 + .../components/layout/Navigation/index.ts | 40 ++++ .../components/layout/OriginSidebar.tsx | 57 ----- .../components/layout/Sidebar/ButtonLink.tsx | 23 -- .../components/layout/Sidebar/index.tsx | 2 - web_ui/frontend/components/layout/index.ts | 1 - 22 files changed, 621 insertions(+), 303 deletions(-) create mode 100644 web_ui/frontend/app/navigation.tsx create mode 100644 web_ui/frontend/components/layout/Navigation/AppBar/AppBar.tsx create mode 100644 web_ui/frontend/components/layout/Navigation/Navigation.tsx rename web_ui/frontend/components/layout/{ => Navigation}/Sidebar/AboutMenu.tsx (95%) create mode 100644 web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx create mode 100644 web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx rename web_ui/frontend/components/layout/{ => Navigation}/Sidebar/Sidebar.tsx (80%) rename web_ui/frontend/components/layout/{ => Navigation}/Sidebar/UserMenu.tsx (100%) create mode 100644 web_ui/frontend/components/layout/Navigation/Sidebar/index.tsx create mode 100644 web_ui/frontend/components/layout/Navigation/index.ts delete mode 100644 web_ui/frontend/components/layout/OriginSidebar.tsx delete mode 100644 web_ui/frontend/components/layout/Sidebar/ButtonLink.tsx delete mode 100644 web_ui/frontend/components/layout/Sidebar/index.tsx diff --git a/web_ui/frontend/app/cache/layout.tsx b/web_ui/frontend/app/cache/layout.tsx index 197b4f6e9..801f029fa 100644 --- a/web_ui/frontend/app/cache/layout.tsx +++ b/web_ui/frontend/app/cache/layout.tsx @@ -16,17 +16,12 @@ * ***************************************************************/ -import { Box, Tooltip } from '@mui/material'; +import { Box } from '@mui/material'; -import { ButtonLink, Sidebar } from '@/components/layout/Sidebar'; -import Link from 'next/link'; -import Image from 'next/image'; -import PelicanLogo from '@/public/static/images/PelicanPlatformLogo_Icon.png'; -import IconButton from '@mui/material/IconButton'; -import BuildIcon from '@mui/icons-material/Build'; import Main from '@/components/layout/Main'; import { PaddedContent } from '@/components/layout'; -import { Dashboard, MapOutlined } from '@mui/icons-material'; +import { Navigation } from '@/components/layout/Navigation'; +import NavigationConfiguration from '@/app/navigation'; export const metadata = { title: 'Pelican Cache', @@ -40,14 +35,7 @@ export default function RootLayout({ }) { return ( - - - - - - - - +
{children}
diff --git a/web_ui/frontend/app/config/Config.tsx b/web_ui/frontend/app/config/Config.tsx index bfd42fbde..06bca21fb 100644 --- a/web_ui/frontend/app/config/Config.tsx +++ b/web_ui/frontend/app/config/Config.tsx @@ -45,7 +45,6 @@ import { import useSWR from 'swr'; import { merge, isMatch, isEqual } from 'lodash'; import * as yaml from 'js-yaml'; -import { ButtonLink, Sidebar } from '@/components/layout/Sidebar'; import { Main } from '@/components/layout/Main'; import { submitConfigChange } from '@/components/configuration/util'; import { @@ -79,13 +78,6 @@ function Config({ metadata }: { metadata: ParameterMetadataRecord }) { async () => await alertOnError(getConfigJson, 'Could not get config', dispatch) ); - const { data: enabledServers } = useSWR( - 'getEnabledServers', - getEnabledServers, - { - fallbackData: ['origin', 'registry', 'director', 'cache'], - } - ); const serverConfig = useMemo(() => { return flattenObject(data || {}); @@ -107,121 +99,85 @@ function Config({ metadata }: { metadata: ParameterMetadataRecord }) { }, [serverConfig, patch]); return ( - <> - - {enabledServers && enabledServers.includes('origin') && ( - - - - )} - {enabledServers && enabledServers.includes('director') && ( - - - - )} - {enabledServers && enabledServers.includes('registry') && ( - - - - )} - {enabledServers && enabledServers.includes('cache') && ( - - - - )} - -
- - - - - Configuration - {serverConfig && ( - - - - - - )} - + + + + Configuration + {serverConfig && ( + + + + - - - - {error && ( - - {(error as Error).message} - - )} - - - - - - - } - /> - {status && } - - - - - - - - - -
- + // Refresh the page after 3 seconds + setTimeout(() => { + mutate(); + setStatus(undefined); + _setPatch({}); + }, 3000); + } catch (e) { + setStatus({ + severity: 'error', + message: (e as string).toString(), + }); + } + }} + > + Save + + +
+ } + /> + {status && } + + + + + + + + ); } diff --git a/web_ui/frontend/app/config/layout.tsx b/web_ui/frontend/app/config/layout.tsx index ef2330f35..d0ff4161b 100644 --- a/web_ui/frontend/app/config/layout.tsx +++ b/web_ui/frontend/app/config/layout.tsx @@ -17,6 +17,8 @@ ***************************************************************/ import { Box } from '@mui/material'; +import { PaddedContent, Main } from '@/components/layout'; +import { Navigation } from '@/components/layout/Navigation'; export const metadata = { title: 'Pelican Configuration', @@ -30,7 +32,10 @@ export default function RootLayout({ }) { return ( - {children} + +
+ {children} +
); } diff --git a/web_ui/frontend/app/director/layout.tsx b/web_ui/frontend/app/director/layout.tsx index 5d9325d30..00cd8b64b 100644 --- a/web_ui/frontend/app/director/layout.tsx +++ b/web_ui/frontend/app/director/layout.tsx @@ -17,10 +17,9 @@ ***************************************************************/ 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 { Navigation } from '@/components/layout/Navigation'; +import NavigationConfiguration from '@/app/navigation'; export const metadata = { title: 'Pelican Director', @@ -34,20 +33,7 @@ export default function RootLayout({ }) { return ( - - - - - - - - - - - - - - +
{children}
); diff --git a/web_ui/frontend/app/layout.tsx b/web_ui/frontend/app/layout.tsx index 10f5ca8b3..43cd1ec30 100644 --- a/web_ui/frontend/app/layout.tsx +++ b/web_ui/frontend/app/layout.tsx @@ -20,7 +20,6 @@ import { LocalizationProvider } from '@/clientComponents'; import { ThemeProviderClient } from '@/components/ThemeProvider'; import { AlertProvider } from '@/components/AlertProvider'; import './globals.css'; - export const metadata = { title: 'Pelican Platform', description: 'Software designed to make data distribution easy', diff --git a/web_ui/frontend/app/navigation.tsx b/web_ui/frontend/app/navigation.tsx new file mode 100644 index 000000000..9812ab018 --- /dev/null +++ b/web_ui/frontend/app/navigation.tsx @@ -0,0 +1,87 @@ +import { + Add, + Block, + Build, + Dashboard, + Equalizer, + FolderOpen, + Lock, + MapOutlined, + Public, + Storage, + TripOrigin, + AssistantDirection, + AppRegistration, + Cached, +} from '@mui/icons-material'; +import { NavigationConfiguration } from '@/components/layout/Navigation'; + +const NavigationConfig: NavigationConfiguration = { + registry: [ + { title: 'Dashboard', href: '/registry/', icon: }, + { + title: 'Denied Namespaces', + href: '/registry/denied/', + icon: , + allowedRoles: ['admin'], + }, + { + title: 'Add', + icon: , + allowedRoles: ['admin'], + children: [ + { + title: 'Namespace', + href: '/registry/namespace/register/', + icon: , + }, + { + title: 'Origin', + href: '/registry/origin/register/', + icon: , + }, + { + title: 'Cache', + href: '/registry/cache/register/', + icon: , + }, + ], + }, + { + title: 'Config', + href: '/config/', + icon: , + allowedRoles: ['admin'], + }, + ], + origin: [ + { title: 'Dashboard', href: '/origin/', icon: }, + { title: 'Metrics', href: '/origin/metrics/', icon: }, + { + title: 'Globus Configurations', + href: '/origin/globus/', + icon: , + allowedExportTypes: ['globus'], + }, + { title: 'Issuer', href: '/origin/issuer', icon: }, + { title: 'Config', href: '/config/', icon: }, + ], + director: [ + { title: 'Dashboard', href: '/director/', icon: }, + { title: 'Metrics', href: '/director/metrics/', icon: }, + { title: 'Map', href: '/director/map/', icon: }, + { title: 'Config', href: '/config/', icon: }, + ], + cache: [ + { title: 'Dashboard', href: '/cache/', icon: }, + { title: 'Config', href: '/config/', icon: }, + ], + shared: [ + { title: 'Origin', href: '/origin/', icon: }, + { title: 'Director', href: '/director/', icon: }, + { title: 'Registry', href: '/registry/', icon: }, + { title: 'Cache', href: '/cache/', icon: }, + ], +}; + +export default NavigationConfig; diff --git a/web_ui/frontend/app/origin/layout.tsx b/web_ui/frontend/app/origin/layout.tsx index 6ce8e929f..6ef48f0eb 100644 --- a/web_ui/frontend/app/origin/layout.tsx +++ b/web_ui/frontend/app/origin/layout.tsx @@ -18,8 +18,9 @@ import { Box } from '@mui/material'; import Main from '@/components/layout/Main'; -import { OriginSidebar } from '@/components/layout/OriginSidebar'; import { PaddedContent } from '@/components/layout'; +import { Navigation } from '@/components/layout/Navigation'; +import NavigationConfiguration from '@/app/navigation'; export const metadata = { title: 'Pelican Origin', @@ -33,7 +34,7 @@ export default function RootLayout({ }) { return ( - +
{children}
diff --git a/web_ui/frontend/app/registry/layout.tsx b/web_ui/frontend/app/registry/layout.tsx index 36e2b69cd..bb11417e1 100644 --- a/web_ui/frontend/app/registry/layout.tsx +++ b/web_ui/frontend/app/registry/layout.tsx @@ -16,29 +16,15 @@ * ***************************************************************/ -import { Box, Tooltip } from '@mui/material'; -import Link from 'next/link'; -import { - Build, - FolderOpen, - TripOrigin, - Storage, - Block, - Dashboard, -} from '@mui/icons-material'; - -import { ButtonLink, Sidebar } from '@/components/layout/Sidebar'; -import IconButton from '@mui/material/IconButton'; +import { Box } from '@mui/material'; import { Main } from '@/components/layout/Main'; -import SpeedDial, { - SpeedButtonControlledProps, -} from '@/components/layout/SidebarSpeedDial'; -import AuthenticatedContent from '@/components/layout/AuthenticatedContent'; import { PaddedContent } from '@/components/layout'; +import { Navigation } from '@/components/layout/Navigation'; +import NavigationConfiguration from '@/app/navigation'; export const metadata = { title: 'Pelican Registry', - description: 'Software designed to make data distribution easy', + description: 'Register your service with this Federation', }; export default function RootLayout({ @@ -46,45 +32,9 @@ export default function RootLayout({ }: { children: React.ReactNode; }) { - const actions: SpeedButtonControlledProps[] = [ - { - href: '/registry/namespace/register/', - icon: , - text: 'Namespace', - title: 'Register a new Namespace', - }, - { - href: '/registry/origin/register/', - icon: , - text: 'Origin', - title: 'Register a new Origin', - }, - { - href: '/registry/cache/register/', - icon: , - text: 'Cache', - title: 'Register a new Cache', - }, - ]; - return ( - - - - - - - - - - - - - - - - +
{children}
diff --git a/web_ui/frontend/components/DataExportTable.tsx b/web_ui/frontend/components/DataExportTable.tsx index a9a46748c..672480816 100644 --- a/web_ui/frontend/components/DataExportTable.tsx +++ b/web_ui/frontend/components/DataExportTable.tsx @@ -35,7 +35,7 @@ type ExportResCommon = { editUrl: string; }; -type ExportRes = ExportResCommon & +export type ExportRes = ExportResCommon & ( | { type: 's3'; exports: S3ExportEntry[] } | { type: 'posix'; exports: PosixExportEntry[] } diff --git a/web_ui/frontend/components/layout/Navigation/AppBar/AppBar.tsx b/web_ui/frontend/components/layout/Navigation/AppBar/AppBar.tsx new file mode 100644 index 000000000..676bd8af5 --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/AppBar/AppBar.tsx @@ -0,0 +1,166 @@ +import * as React from 'react'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import Menu from '@mui/material/Menu'; +import MenuIcon from '@mui/icons-material/Menu'; +import Container from '@mui/material/Container'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import Tooltip from '@mui/material/Tooltip'; +import MenuItem from '@mui/material/MenuItem'; +import AdbIcon from '@mui/icons-material/Adb'; +import { NavigationProps } from '@/components/layout/Navigation'; + +const pages = ['Products', 'Pricing', 'Blog']; +const settings = ['Profile', 'Account', 'Dashboard', 'Logout']; + +function ResponsiveAppBar({ config, exportType, role }: NavigationProps) { + const [anchorElNav, setAnchorElNav] = React.useState( + null + ); + const [anchorElUser, setAnchorElUser] = React.useState( + null + ); + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(null); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( + + + + + + LOGO + + + + + + + + {pages.map((page) => ( + + {page} + + ))} + + + + + LOGO + + + {pages.map((page) => ( + + ))} + + + + + + + + + {settings.map((setting) => ( + + + {setting} + + + ))} + + + + + + ); +} +export default ResponsiveAppBar; diff --git a/web_ui/frontend/components/layout/Navigation/Navigation.tsx b/web_ui/frontend/components/layout/Navigation/Navigation.tsx new file mode 100644 index 000000000..5211e30cb --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/Navigation.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { StaticNavigationItemProps } from '@/components/layout/Navigation/index'; +import useSWR from 'swr'; +import { getExportData } from '@/components/DataExportTable'; +import { getUser } from '@/helpers/login'; +import { Sidebar } from '@/components/layout/Navigation/Sidebar'; +import NavigationConfig from '@/app/navigation'; +import { getEnabledServers } from '@/helpers/util'; + +const Navigation = ({ + config, + sharedPage, +}: { + config?: StaticNavigationItemProps[]; + sharedPage?: boolean; +}) => { + // Check either config or sharedPage is defined but not both + if ((config && sharedPage) || (!config && !sharedPage)) { + throw new Error('Either config xor sharedPage must be defined'); + } + + const { data: exports } = useSWR('getDataExport', getExportData); + const { data: user } = useSWR('getUser', getUser); + const { data: servers } = useSWR('getServers', getEnabledServers); + + // Handle navigation for shared pages + // Best we can do is sending them to root if there are many running servers + // If there is just one then we can render that navigation + if (sharedPage) { + const multipleServersActive = servers && servers.length > 1; + if (multipleServersActive) { + config = NavigationConfig['shared']; + } else { + config = NavigationConfig[servers ? servers[0] : 'shared']; + } + } + + return ( + <> + + + ); +}; + +export { Navigation }; diff --git a/web_ui/frontend/components/layout/Sidebar/AboutMenu.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/AboutMenu.tsx similarity index 95% rename from web_ui/frontend/components/layout/Sidebar/AboutMenu.tsx rename to web_ui/frontend/components/layout/Navigation/Sidebar/AboutMenu.tsx index cdaf1d20e..4d2b21ab1 100644 --- a/web_ui/frontend/components/layout/Sidebar/AboutMenu.tsx +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/AboutMenu.tsx @@ -21,8 +21,7 @@ import { } from '@mui/icons-material'; import useSWR from 'swr'; -import { evaluateOrReturn, getEnabledServers } from '@/helpers/util'; -import { ServerType } from '@/index'; +import { evaluateOrReturn } from '@/helpers/util'; const AboutMenu = () => { const [open, setOpen] = useState(false); @@ -177,7 +176,7 @@ const actions: MenuItemProps[] = [ ]; export const getVersionNumber = () => { - const { version } = require('../../../package.json'); + const { version } = require('../../../../package.json'); return version; }; diff --git a/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx new file mode 100644 index 000000000..3c3b50ef0 --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx @@ -0,0 +1,86 @@ +'use client'; + +import React, { ReactNode, useMemo, useRef, useState } from 'react'; +import { + BoxProps, + IconButton, + Menu, + ListItemIcon, + MenuItem, + ListItemText, +} from '@mui/material'; + +import { + StaticNavigationItemProps, + StaticNavigationParentItemProps, +} from '@/components/layout/Navigation'; +import Link from 'next/link'; + +const NavigationMenu = ({ + config, +}: { + config: StaticNavigationParentItemProps; +}) => { + const [open, setOpen] = useState(false); + const menuRef = useRef(null); + + const buttonId = `${config.title}-menu-button`; + const menuId = `${config.title}-menu`; + + return ( + <> + setOpen(!open)} + sx={{ mt: 1 }} + > + {config.icon} + + setOpen(false)} + onClick={() => setOpen(false)} + anchorOrigin={{ + vertical: 'center', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'center', + horizontal: 'left', + }} + > + {config.children.map((config) => ( + + ))} + + + ); +}; + +const NavigationMenuItem = ({ + config, +}: { + config: StaticNavigationItemProps; +}) => { + // If this item has children, render a menu + if ('children' in config) { + return ; + } + + // Otherwise, render the navigation item + return ( + + + {config.icon} + + + + ); +}; + +export default NavigationMenu; diff --git a/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx new file mode 100644 index 000000000..6d1f3329f --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx @@ -0,0 +1,76 @@ +/** + * Navigation Component for Pelican Sidebar + */ +import { Box, Button, Skeleton, Tooltip } from '@mui/material'; +import Link from 'next/link'; +import IconButton from '@mui/material/IconButton'; +import { Air } from '@mui/icons-material'; +import { + NavigationItemProps, + StaticNavigationChildItemProps, + StaticNavigationParentItemProps, +} from '@/components/layout/Navigation'; +import { ExportRes } from '@/components/DataExportTable'; +import NavigationMenu from '@/components/layout/Navigation/Sidebar/Menu'; + +export const NavigationItem = ({ + exportType, + role, + config, +}: NavigationItemProps) => { + // If the role or export has yet to propogate, show a skeleton + if ( + (config?.allowedRoles && role === undefined) || + (config?.allowedExportTypes && exportType === undefined) + ) { + return ; + } + + // If the role or export is not allowed, return null + if ( + (config?.allowedRoles && !config.allowedRoles.includes(role)) || + (config?.allowedExportTypes && + !config.allowedExportTypes.includes(exportType as ExportRes['type'])) + ) { + return null; + } + + // If the item has children, render a menu + if ('children' in config) { + return ; + } + + // Otherwise, render the navigation item + return ; +}; + +const NavigationChildItem = ({ + title, + href, + icon, + showTitle, +}: StaticNavigationChildItemProps) => { + return ( + + + + {showTitle ? ( + + ) : ( + {icon} + )} + + + + ); +}; + +const NavigationItemSkeleton = () => { + return ( + + + + + + ); +}; diff --git a/web_ui/frontend/components/layout/Sidebar/Sidebar.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx similarity index 80% rename from web_ui/frontend/components/layout/Sidebar/Sidebar.tsx rename to web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx index 136ae53f9..01eab2ef0 100644 --- a/web_ui/frontend/components/layout/Sidebar/Sidebar.tsx +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx @@ -18,7 +18,7 @@ import { Box } from '@mui/material'; -import styles from '../../../app/page.module.css'; +import styles from '../../../../app/page.module.css'; import React, { ReactNode } from 'react'; import UserMenu from './UserMenu'; @@ -26,8 +26,10 @@ import { default as NextLink } from 'next/link'; import Image from 'next/image'; import PelicanLogo from '@/public/static/images/PelicanPlatformLogo_Icon.png'; import AboutMenu from './AboutMenu'; +import { NavigationItem } from '@/components/layout/Navigation/Sidebar/NavigationItem'; +import { NavigationProps } from '@/components/layout/Navigation'; -export const Sidebar = ({ children }: { children: ReactNode }) => { +export const Sidebar = ({ config, exportType, role }: NavigationProps) => { return ( { loading={'eager'} /> - {children} + {config.map((navItem) => { + return ( + + ); + })} { - const { data, error } = useSWR('getDataExport', getExportData); - - if (error) { - console.log('Error fetching data exports: ' + error); - } - - return ( - - - - - - - - {data?.type === 'globus' && ( - - - - )} - - - - - - - - ); -}; diff --git a/web_ui/frontend/components/layout/Sidebar/ButtonLink.tsx b/web_ui/frontend/components/layout/Sidebar/ButtonLink.tsx deleted file mode 100644 index f2af13fdc..000000000 --- a/web_ui/frontend/components/layout/Sidebar/ButtonLink.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Box, Tooltip } from '@mui/material'; -import Link from 'next/link'; -import IconButton from '@mui/material/IconButton'; -import { Dashboard } from '@mui/icons-material'; -import { ReactNode } from 'react'; - -interface ButtonLinkProps { - title: string; - href: string; - children: ReactNode; -} - -export const ButtonLink = ({ title, href, children }: ButtonLinkProps) => { - return ( - - - - {children} - - - - ); -}; diff --git a/web_ui/frontend/components/layout/Sidebar/index.tsx b/web_ui/frontend/components/layout/Sidebar/index.tsx deleted file mode 100644 index 64f44763e..000000000 --- a/web_ui/frontend/components/layout/Sidebar/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ButtonLink'; -export * from './Sidebar'; diff --git a/web_ui/frontend/components/layout/index.ts b/web_ui/frontend/components/layout/index.ts index 42f48d2b8..d6c7de324 100644 --- a/web_ui/frontend/components/layout/index.ts +++ b/web_ui/frontend/components/layout/index.ts @@ -2,6 +2,5 @@ export * from './AuthenticatedContent'; export * from './Drawer'; export * from './Header'; export * from './Main'; -export * from './OriginSidebar'; export * from './SidebarSpeedDial'; export * from './PaddedContent'; From a64d41482fc37e4c8f96663bec67d8310c2ba614 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Wed, 11 Dec 2024 15:25:51 -0600 Subject: [PATCH 02/10] Add AppBar Navigation - Add AppBar for mobile navigation --- web_ui/frontend/app/cache/layout.tsx | 5 +- web_ui/frontend/app/config/layout.tsx | 5 +- web_ui/frontend/app/director/layout.tsx | 5 +- web_ui/frontend/app/origin/layout.tsx | 5 +- web_ui/frontend/app/registry/layout.tsx | 5 +- web_ui/frontend/components/layout/Main.tsx | 2 +- .../layout/Navigation/AppBar/AppBar.tsx | 224 +++++++----------- .../layout/Navigation/AppBar/BurgerMenu.tsx | 154 ++++++++++++ .../Navigation/AppBar/NavigationItem.tsx | 135 +++++++++++ .../layout/Navigation/AppBar/index.ts | 1 + .../layout/Navigation/Navigation.tsx | 42 +++- .../layout/Navigation/Sidebar/Menu.tsx | 10 +- .../Navigation/Sidebar/NavigationItem.tsx | 7 +- .../layout/Navigation/Sidebar/Sidebar.tsx | 3 +- .../layout/Navigation/Sidebar/UserMenu.tsx | 7 +- .../components/layout/Navigation/index.ts | 4 +- web_ui/frontend/helpers/login.ts | 4 +- 17 files changed, 447 insertions(+), 171 deletions(-) create mode 100644 web_ui/frontend/components/layout/Navigation/AppBar/BurgerMenu.tsx create mode 100644 web_ui/frontend/components/layout/Navigation/AppBar/NavigationItem.tsx create mode 100644 web_ui/frontend/components/layout/Navigation/AppBar/index.ts diff --git a/web_ui/frontend/app/cache/layout.tsx b/web_ui/frontend/app/cache/layout.tsx index 801f029fa..6981bb4f8 100644 --- a/web_ui/frontend/app/cache/layout.tsx +++ b/web_ui/frontend/app/cache/layout.tsx @@ -34,11 +34,10 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - +
{children}
-
+
); } diff --git a/web_ui/frontend/app/config/layout.tsx b/web_ui/frontend/app/config/layout.tsx index d0ff4161b..8342507e6 100644 --- a/web_ui/frontend/app/config/layout.tsx +++ b/web_ui/frontend/app/config/layout.tsx @@ -31,11 +31,10 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - +
{children}
-
+
); } diff --git a/web_ui/frontend/app/director/layout.tsx b/web_ui/frontend/app/director/layout.tsx index 00cd8b64b..f66b6cb26 100644 --- a/web_ui/frontend/app/director/layout.tsx +++ b/web_ui/frontend/app/director/layout.tsx @@ -32,9 +32,8 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - +
{children}
-
+ ); } diff --git a/web_ui/frontend/app/origin/layout.tsx b/web_ui/frontend/app/origin/layout.tsx index 6ef48f0eb..dffdb4483 100644 --- a/web_ui/frontend/app/origin/layout.tsx +++ b/web_ui/frontend/app/origin/layout.tsx @@ -33,11 +33,10 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - +
{children}
-
+ ); } diff --git a/web_ui/frontend/app/registry/layout.tsx b/web_ui/frontend/app/registry/layout.tsx index bb11417e1..46b18ec97 100644 --- a/web_ui/frontend/app/registry/layout.tsx +++ b/web_ui/frontend/app/registry/layout.tsx @@ -33,11 +33,10 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - +
{children}
-
+ ); } diff --git a/web_ui/frontend/components/layout/Main.tsx b/web_ui/frontend/components/layout/Main.tsx index 44b528807..c172c8835 100644 --- a/web_ui/frontend/components/layout/Main.tsx +++ b/web_ui/frontend/components/layout/Main.tsx @@ -6,7 +6,7 @@ export const Main = ({ children }: { children: ReactNode }) => { ( - null - ); - const [anchorElUser, setAnchorElUser] = React.useState( - null - ); - - const handleOpenNavMenu = (event: React.MouseEvent) => { - setAnchorElNav(event.currentTarget); - }; - const handleOpenUserMenu = (event: React.MouseEvent) => { - setAnchorElUser(event.currentTarget); - }; - - const handleCloseNavMenu = () => { - setAnchorElNav(null); - }; - - const handleCloseUserMenu = () => { - setAnchorElUser(null); - }; + const [navOpen, setNavOpen] = React.useState(false); return ( - - - - - - LOGO - - - - - - - - {pages.map((page) => ( - - {page} - - ))} - - - - - LOGO - - - {pages.map((page) => ( - - ))} - - - - - + - - + {'Pelican + - {settings.map((setting) => ( - - - {setting} - - - ))} - - - - - + Pelican +
+ Platform + + + + + + + + + + setNavOpen(false)} + /> + + + ); } export default ResponsiveAppBar; diff --git a/web_ui/frontend/components/layout/Navigation/AppBar/BurgerMenu.tsx b/web_ui/frontend/components/layout/Navigation/AppBar/BurgerMenu.tsx new file mode 100644 index 000000000..5bc0f9a4b --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/AppBar/BurgerMenu.tsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react'; +import { + List, + ListItem, + ListItemText, + Collapse, + IconButton, + Box, +} from '@mui/material'; +import { + Api, + BugReport, + Description, + GitHub, + HelpOutline, +} from '@mui/icons-material'; +import { + NavigationItemProps, + NavigationProps, + StaticNavigationBaseItemProps, + StaticNavigationItemProps, +} from '@/components/layout/Navigation/index'; +import Container from '@mui/material/Container'; +import Toolbar from '@mui/material/Toolbar'; +import Image from 'next/image'; +import PelicanLogo from '@/public/static/images/PelicanPlatformLogo_Icon.png'; +import Typography from '@mui/material/Typography'; +import UserMenu from '@/components/layout/Navigation/Sidebar/UserMenu'; +import AppBar from '@mui/material/AppBar'; +import { Close } from '@mui/icons-material'; +import { NavigationItem } from '@/components/layout/Navigation/AppBar/NavigationItem'; +import { getVersionNumber } from '@/components/layout/Navigation/Sidebar/AboutMenu'; + +const helpMenu: StaticNavigationItemProps[] = [ + { + icon: , + title: 'Help', + children: [ + { + icon: , + title: 'Documentation', + href: 'https://docs.pelicanplatform.org', + }, + { + icon: , + title: 'Pelican Server API', + href: '/api/v1.0/docs', + }, + { + icon: , + title: () => `Release ${getVersionNumber()}`, + href: () => + `https://github.com/PelicanPlatform/pelican/releases/tag/v${getVersionNumber()}`, + }, + { + icon: , + title: 'Report Bug', + href: 'https://github.com/PelicanPlatform/pelican/issues/new', + }, + ], + }, +]; + +type BurgerMenuProps = { onClose: () => void } & NavigationProps; + +const BurgerMenu: React.FC = ({ + config, + exportType, + role, + onClose, +}) => { + return ( + + + + + + + + + + {'Pelican + + Pelican +
+ Platform +
+ + + +
+
+
+ + {[...config, ...helpMenu].map((item, index) => ( + + ))} + +
+ ); +}; + +export default BurgerMenu; diff --git a/web_ui/frontend/components/layout/Navigation/AppBar/NavigationItem.tsx b/web_ui/frontend/components/layout/Navigation/AppBar/NavigationItem.tsx new file mode 100644 index 000000000..774a10389 --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/AppBar/NavigationItem.tsx @@ -0,0 +1,135 @@ +/** + * AppBar equivalent for the NavigationItem component + * Uses List component to render the navigation items + */ + +import { useState } from 'react'; +import { + Box, + Collapse, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Skeleton, +} from '@mui/material'; +import Link from 'next/link'; +import { + NavigationItemProps, + NavigationProps, + StaticNavigationChildItemProps, + StaticNavigationItemProps, + StaticNavigationParentItemProps, +} from '@/components/layout/Navigation'; +import { ExportRes } from '@/components/DataExportTable'; +import { evaluateOrReturn } from '@/helpers/util'; + +export const NavigationItem = ({ + exportType, + role, + config, + onClose, +}: { onClose: () => void } & NavigationItemProps) => { + // If the role or export has yet to propogate, show a skeleton + if ( + (config?.allowedRoles && role === undefined) || + (config?.allowedExportTypes && exportType === undefined) + ) { + return ; + } + + // If the role or export is not allowed, return null + if ( + (config?.allowedRoles && !config.allowedRoles.includes(role)) || + (config?.allowedExportTypes && + !config.allowedExportTypes.includes(exportType as ExportRes['type'])) + ) { + return null; + } + + // If the item has children, render a menu + if ('children' in config) { + return ; + } + + // Otherwise, render the navigation item + return ; +}; + +const NavigationChildItem = ({ + title, + href, + icon, + onClose, +}: { onClose: () => void } & StaticNavigationChildItemProps) => { + return ( + + + {icon} + + + + ); +}; + +const NavigationItemSkeleton = () => { + return ( + + + + + + ); +}; + +const NavigationMenu = ({ + onClose, + config, +}: { + onClose: () => void; + config: StaticNavigationParentItemProps; +}) => { + const [open, setOpen] = useState(false); + + return ( + <> + setOpen(!open)} + style={{ backgroundColor: open ? '#d1f4ff' : 'inherit' }} + > + {config.icon} + + + + + + {config.children.map((config) => ( + + ))} + + + + + ); +}; + +const NavigationMenuItem = ({ + onClose, + config, +}: { + onClose: () => void; + config: StaticNavigationItemProps; +}) => { + // If this item has children, render a nested menu + if ('children' in config) { + return ; + } + + // Otherwise, render the navigation item + return ; +}; diff --git a/web_ui/frontend/components/layout/Navigation/AppBar/index.ts b/web_ui/frontend/components/layout/Navigation/AppBar/index.ts new file mode 100644 index 000000000..7a331d484 --- /dev/null +++ b/web_ui/frontend/components/layout/Navigation/AppBar/index.ts @@ -0,0 +1 @@ +export { default as AppBar } from './AppBar'; diff --git a/web_ui/frontend/components/layout/Navigation/Navigation.tsx b/web_ui/frontend/components/layout/Navigation/Navigation.tsx index 5211e30cb..8254912d4 100644 --- a/web_ui/frontend/components/layout/Navigation/Navigation.tsx +++ b/web_ui/frontend/components/layout/Navigation/Navigation.tsx @@ -7,11 +7,16 @@ import { getUser } from '@/helpers/login'; import { Sidebar } from '@/components/layout/Navigation/Sidebar'; import NavigationConfig from '@/app/navigation'; import { getEnabledServers } from '@/helpers/util'; +import { Box } from '@mui/material'; +import { AppBar } from '@/components/layout/Navigation/AppBar'; +import { ReactNode } from 'react'; const Navigation = ({ + children, config, sharedPage, }: { + children: ReactNode; config?: StaticNavigationItemProps[]; sharedPage?: boolean; }) => { @@ -36,13 +41,40 @@ const Navigation = ({ } } + console.log(user); + return ( <> - + + + + + + + + {children} + ); }; diff --git a/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx index 3c3b50ef0..683a67225 100644 --- a/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/Menu.tsx @@ -15,6 +15,7 @@ import { StaticNavigationParentItemProps, } from '@/components/layout/Navigation'; import Link from 'next/link'; +import { evaluateOrReturn } from '@/helpers/util'; const NavigationMenu = ({ config, @@ -55,7 +56,10 @@ const NavigationMenu = ({ }} > {config.children.map((config) => ( - + ))} @@ -74,10 +78,10 @@ const NavigationMenuItem = ({ // Otherwise, render the navigation item return ( - + {config.icon} - + ); diff --git a/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx index 6d1f3329f..3c1eda6ea 100644 --- a/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/NavigationItem.tsx @@ -12,6 +12,7 @@ import { } from '@/components/layout/Navigation'; import { ExportRes } from '@/components/DataExportTable'; import NavigationMenu from '@/components/layout/Navigation/Sidebar/Menu'; +import { evaluateOrReturn } from '@/helpers/util'; export const NavigationItem = ({ exportType, @@ -52,10 +53,10 @@ const NavigationChildItem = ({ }: StaticNavigationChildItemProps) => { return ( - - + + {showTitle ? ( - + ) : ( {icon} )} diff --git a/web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx b/web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx index 01eab2ef0..401cc4576 100644 --- a/web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx +++ b/web_ui/frontend/components/layout/Navigation/Sidebar/Sidebar.tsx @@ -28,6 +28,7 @@ import PelicanLogo from '@/public/static/images/PelicanPlatformLogo_Icon.png'; import AboutMenu from './AboutMenu'; import { NavigationItem } from '@/components/layout/Navigation/Sidebar/NavigationItem'; import { NavigationProps } from '@/components/layout/Navigation'; +import { evaluateOrReturn } from '@/helpers/util'; export const Sidebar = ({ config, exportType, role }: NavigationProps) => { return ( @@ -66,7 +67,7 @@ export const Sidebar = ({ config, exportType, role }: NavigationProps) => { {config.map((navItem) => { return ( { +const UserMenu = ({ menuOptions }: { menuOptions?: Partial }) => { const userMenuRef = React.useRef(null); const { @@ -100,11 +100,14 @@ const UserMenu = () => { anchorOrigin={{ vertical: 'center', horizontal: 'right', + ...menuOptions?.anchorOrigin, }} transformOrigin={{ vertical: 'center', horizontal: 'left', + ...menuOptions?.transformOrigin, }} + {...menuOptions} > {user.role === 'admin' ? ( Admin User diff --git a/web_ui/frontend/components/layout/Navigation/index.ts b/web_ui/frontend/components/layout/Navigation/index.ts index c2b96a64a..798a8fc37 100644 --- a/web_ui/frontend/components/layout/Navigation/index.ts +++ b/web_ui/frontend/components/layout/Navigation/index.ts @@ -12,7 +12,7 @@ export type StaticNavigationItemProps = | StaticNavigationChildItemProps; export type StaticNavigationBaseItemProps = { - title: string; + title: string | (() => string); icon: ReactNode; showTitle?: boolean; allowedRoles?: User['role'][]; @@ -20,7 +20,7 @@ export type StaticNavigationBaseItemProps = { }; export type StaticNavigationChildItemProps = StaticNavigationBaseItemProps & { - href: string; + href: string | (() => string); }; export type StaticNavigationParentItemProps = StaticNavigationBaseItemProps & { diff --git a/web_ui/frontend/helpers/login.ts b/web_ui/frontend/helpers/login.ts index 1f3d7a50c..69ea707e2 100644 --- a/web_ui/frontend/helpers/login.ts +++ b/web_ui/frontend/helpers/login.ts @@ -96,8 +96,8 @@ export const getUser = async (): Promise => { // If authenticated, store status and csrf token const user = { authenticated: json['authenticated'], - user: json['user'] == '' ? undefined : json['user'], - role: json['role'] == '' ? undefined : json['role'], + user: json['user'] == '' ? null : json['user'], + role: json['role'] == '' ? null : json['role'], csrf_token: response.headers.get('X-CSRF-Token'), }; From 65abb820d26f977a87b06963d92337d5f923f175 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Thu, 12 Dec 2024 08:54:33 -0600 Subject: [PATCH 03/10] Make Config and Login Mobile Friendly --- web_ui/frontend/app/(landing)/page.tsx | 46 ++---------- .../app/(login)/components/PasswordInput.tsx | 4 +- web_ui/frontend/app/(login)/layout.tsx | 9 ++- web_ui/frontend/app/(login)/login/page.tsx | 72 +++++++++---------- web_ui/frontend/app/config/Config.tsx | 2 +- .../components/NamespaceCapabilitiesTable.tsx | 8 +-- .../components/ServerCapabilitiesTable.tsx | 8 +-- .../components/layout/PaddedContent.tsx | 2 +- 8 files changed, 57 insertions(+), 94 deletions(-) diff --git a/web_ui/frontend/app/(landing)/page.tsx b/web_ui/frontend/app/(landing)/page.tsx index 014bd919d..004bb5b4c 100644 --- a/web_ui/frontend/app/(landing)/page.tsx +++ b/web_ui/frontend/app/(landing)/page.tsx @@ -19,32 +19,12 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import { Box, Container, Grid, Skeleton, Typography } from '@mui/material'; +import { Box, Container, Grid, List, ListItemButton, ListItemText, Skeleton, Typography } from '@mui/material'; import Link from 'next/link'; import useSWR from 'swr'; import { getEnabledServers } from '@/helpers/util'; import { ServerType } from '@/index'; -function TextCenteredBox({ text }: { text: string }) { - return ( - - - - {text} - - - - ); -} export default function Home() { const { data: enabledServers, isLoading } = useSWR( @@ -56,30 +36,18 @@ export default function Home() { - Pelican Services + Active Pelican Services - - {isLoading && ( - - - - )} + {enabledServers && enabledServers.map((service) => { return ( - - - - - + + + ); })} - + ); diff --git a/web_ui/frontend/app/(login)/components/PasswordInput.tsx b/web_ui/frontend/app/(login)/components/PasswordInput.tsx index 43ecf76c1..8d3d593cc 100644 --- a/web_ui/frontend/app/(login)/components/PasswordInput.tsx +++ b/web_ui/frontend/app/(login)/components/PasswordInput.tsx @@ -52,12 +52,12 @@ export default function PasswordInput({ return (
- - {children} - +
+ + {children} + +
); } diff --git a/web_ui/frontend/app/(login)/login/page.tsx b/web_ui/frontend/app/(login)/login/page.tsx index a597ca523..570942951 100644 --- a/web_ui/frontend/app/(login)/login/page.tsx +++ b/web_ui/frontend/app/(login)/login/page.tsx @@ -108,12 +108,8 @@ const AdminLogin = () => {
{ setPassword(e.target.value); }, @@ -143,15 +139,13 @@ const AdminLogin = () => { ) { return ( - - - + {LoginComponent} ); @@ -210,34 +204,32 @@ export default function Home() { - - {serverIntersect && - (serverIntersect.includes('registry') || - serverIntersect.includes('origin') || - serverIntersect.includes('cache') || - serverIntersect.includes('director')) && ( - <> - - - - - )} - {serverIntersect && } - {!serverIntersect && ( - + {serverIntersect && + (serverIntersect.includes('registry') || + serverIntersect.includes('origin') || + serverIntersect.includes('cache') || + serverIntersect.includes('director')) && ( + <> + + + + )} - + {serverIntersect && } + {!serverIntersect && ( + + )} ); diff --git a/web_ui/frontend/app/config/Config.tsx b/web_ui/frontend/app/config/Config.tsx index 06bca21fb..12819c4b6 100644 --- a/web_ui/frontend/app/config/Config.tsx +++ b/web_ui/frontend/app/config/Config.tsx @@ -116,7 +116,7 @@ function Config({ metadata }: { metadata: ParameterMetadataRecord }) { )} - + {error && ( diff --git a/web_ui/frontend/components/NamespaceCapabilitiesTable.tsx b/web_ui/frontend/components/NamespaceCapabilitiesTable.tsx index 0ba7ae659..4470973a0 100644 --- a/web_ui/frontend/components/NamespaceCapabilitiesTable.tsx +++ b/web_ui/frontend/components/NamespaceCapabilitiesTable.tsx @@ -41,14 +41,14 @@ export const NamespaceCapabilitiesTable = ({ borderRadius={1} > - + Namespace Capabilities - + @@ -61,7 +61,7 @@ export const NamespaceCapabilitiesTable = ({ - + - + - + {server.type}'s Namespace Capabilities - + @@ -52,14 +52,14 @@ export const ServerCapabilitiesTable = ({ - + {namespace.path} - + { return ( - + {children} ); From 1e134a6e00fe13a29b924d477751a38d91d4dad9 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Thu, 12 Dec 2024 11:37:09 -0600 Subject: [PATCH 04/10] Make Registry Mobile Friendly --- .../app/(login)/components/PasswordInput.tsx | 4 ++- web_ui/frontend/app/(login)/login/page.tsx | 13 +++++++-- web_ui/frontend/components/Namespace/Card.tsx | 25 ++++++++++------- .../components/Namespace/PendingCard.tsx | 27 ++++++++++++++----- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/web_ui/frontend/app/(login)/components/PasswordInput.tsx b/web_ui/frontend/app/(login)/components/PasswordInput.tsx index 8d3d593cc..0c7e2cdbe 100644 --- a/web_ui/frontend/app/(login)/components/PasswordInput.tsx +++ b/web_ui/frontend/app/(login)/components/PasswordInput.tsx @@ -60,7 +60,9 @@ export default function PasswordInput({ label='Admin Password' id='outlined-start-adornment' size={'small'} - sx={{ m: 1, width: '50ch' }} + sx={{ m: 1 }} + autoComplete={'current-password'} + name={'password'} type={showPassword ? 'text' : 'password'} {...TextFieldProps} InputProps={{ diff --git a/web_ui/frontend/app/(login)/login/page.tsx b/web_ui/frontend/app/(login)/login/page.tsx index 570942951..f86e0ea27 100644 --- a/web_ui/frontend/app/(login)/login/page.tsx +++ b/web_ui/frontend/app/(login)/login/page.tsx @@ -199,7 +199,16 @@ export default function Home() { Login - + Administer your Pelican Platform @@ -226,7 +235,7 @@ export default function Home() { )} diff --git a/web_ui/frontend/components/Namespace/Card.tsx b/web_ui/frontend/components/Namespace/Card.tsx index f6fa4c377..095ae0e23 100644 --- a/web_ui/frontend/components/Namespace/Card.tsx +++ b/web_ui/frontend/components/Namespace/Card.tsx @@ -6,7 +6,7 @@ import { IconButton, Paper, Tooltip, - Typography, + Typography, useMediaQuery, } from '@mui/material'; import { Delete, Download, Edit, Person } from '@mui/icons-material'; import Link from 'next/link'; @@ -19,6 +19,7 @@ import { useSWRConfig } from 'swr'; import { AlertDispatchContext } from '@/components/AlertProvider'; import CodeBlock from '@/components/CodeBlock'; import { alertOnError } from '@/helpers/util'; +import { Theme } from '@mui/system'; export interface CardProps { namespace: RegistryNamespace; @@ -27,10 +28,14 @@ export interface CardProps { } export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { + + const size = useMediaQuery((theme: Theme) => theme.breakpoints.down("md")) ? 'small' : 'medium'; + const dispatch = useContext(AlertDispatchContext); const ref = useRef(null); const [transition, setTransition] = useState(false); const { mutate } = useSWRConfig(); + return ( <> @@ -50,9 +55,9 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { bgcolor={'secondary'} onClick={() => setTransition(!transition)} > - + - {namespace.prefix} + {namespace.prefix} @@ -62,13 +67,12 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { - + @@ -80,8 +84,9 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { e.stopPropagation()} sx={{ mx: 1 }} + size={size} > - + @@ -93,14 +98,16 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { > e.stopPropagation()} + size={size} > - + { e.stopPropagation(); @@ -115,7 +122,7 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { } }} > - + diff --git a/web_ui/frontend/components/Namespace/PendingCard.tsx b/web_ui/frontend/components/Namespace/PendingCard.tsx index 1b55f43d9..ae08b9e2a 100644 --- a/web_ui/frontend/components/Namespace/PendingCard.tsx +++ b/web_ui/frontend/components/Namespace/PendingCard.tsx @@ -3,6 +3,7 @@ import { Authenticated, secureFetch } from '@/helpers/login'; import { Avatar, Box, IconButton, Tooltip, Typography } from '@mui/material'; import { Block, Check, Edit, Person } from '@mui/icons-material'; import Link from 'next/link'; +import { useMediaQuery } from '@mui/material'; import { Alert, RegistryNamespace } from '@/index'; import InformationDropdown from './InformationDropdown'; @@ -11,6 +12,8 @@ import { User } from '@/index'; import { alertOnError } from '@/helpers/util'; import { AlertDispatchContext } from '@/components/AlertProvider'; import { approveNamespace, denyNamespace } from '@/helpers/api'; +import { Theme } from '@mui/system'; + export interface PendingCardProps { namespace: RegistryNamespace; @@ -25,6 +28,9 @@ export const PendingCard = ({ onAlert, authenticated, }: PendingCardProps) => { + + const size = useMediaQuery((theme: Theme) => theme.breakpoints.down("md")) ? 'small' : 'medium'; + const ref = useRef(null); const [transition, setTransition] = useState(false); @@ -48,9 +54,9 @@ export const PendingCard = ({ bgcolor={'secondary'} onClick={() => setTransition(!transition)} > - + - {namespace.prefix} + {namespace.prefix} @@ -59,9 +65,13 @@ export const PendingCard = ({ - + @@ -71,6 +81,7 @@ export const PendingCard = ({ { e.stopPropagation(); @@ -82,12 +93,13 @@ export const PendingCard = ({ onUpdate(); }} > - + { e.stopPropagation(); @@ -99,7 +111,7 @@ export const PendingCard = ({ onUpdate(); }} > - + @@ -112,8 +124,9 @@ export const PendingCard = ({ > e.stopPropagation()} + size={size} > - + From 4a2237c007ed23e29eba9afb9c9aa467d27cf237 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Thu, 12 Dec 2024 11:45:21 -0600 Subject: [PATCH 05/10] Make Initialization Mobile Friendly --- web_ui/frontend/app/(login)/components/CodeInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_ui/frontend/app/(login)/components/CodeInput.tsx b/web_ui/frontend/app/(login)/components/CodeInput.tsx index e625645bf..cecf9fcb5 100644 --- a/web_ui/frontend/app/(login)/components/CodeInput.tsx +++ b/web_ui/frontend/app/(login)/components/CodeInput.tsx @@ -140,9 +140,9 @@ export default function CodeInput({ Date: Thu, 12 Dec 2024 12:34:09 -0600 Subject: [PATCH 06/10] Make Origin Mobile Friendly --- web_ui/frontend/app/origin/metrics/page.tsx | 20 +++++------ .../frontend/components/DataExportTable.tsx | 10 +++--- .../Fields/ObjectField/ObjectField.tsx | 2 ++ .../components/graphs/GraphOverlay.tsx | 33 ++++++++++++------- .../components/layout/PaddedContent.tsx | 2 +- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/web_ui/frontend/app/origin/metrics/page.tsx b/web_ui/frontend/app/origin/metrics/page.tsx index fb1d91cce..8810ca95f 100644 --- a/web_ui/frontend/app/origin/metrics/page.tsx +++ b/web_ui/frontend/app/origin/metrics/page.tsx @@ -17,7 +17,7 @@ import { StorageGraph } from '@/app/origin/metrics/components/StorageGraph'; const Page = () => { return ( - + { ))} - + { - + - + { color={green[300]} /> - + { color={blue[200]} /> - + { - + - + { color={green[300]} /> - + { color={green[300]} /> - + { {entry.status != 'Completed' && } - + { label={'Sentinel Location'} /> - + @@ -191,14 +191,14 @@ export const S3DataExportCard = ({ entry }: { entry: S3ExportEntry }) => { {entry.status != 'Completed' && } - + - + @@ -215,7 +215,7 @@ export const GlobusDataExportCard = ({ {entry.status != 'Completed' && } - + { fontSize: '1.2rem', my: 'auto', ml: 1, + overflow: 'hidden', + textOverflow: 'ellipsis' }} > {name} diff --git a/web_ui/frontend/components/graphs/GraphOverlay.tsx b/web_ui/frontend/components/graphs/GraphOverlay.tsx index 55b662adc..2ac3c731e 100644 --- a/web_ui/frontend/components/graphs/GraphOverlay.tsx +++ b/web_ui/frontend/components/graphs/GraphOverlay.tsx @@ -15,6 +15,7 @@ import { MenuItem, Select, Typography, + Grid } from '@mui/material'; import { DateTimePicker } from '@mui/x-date-pickers'; import { KeyboardArrowLeft, KeyboardArrowRight } from '@mui/icons-material'; @@ -82,17 +83,21 @@ export const GraphOverlay = ({ children }: { children: ReactNode }) => { return ( <> - - {graphStart.toFormat(format)} - {graphContext.time.toFormat('f')} - - - - - + + + + {graphStart.toFormat(format)} - {graphContext.time.toFormat('f')} + + + + + + + + + {children} @@ -176,7 +181,10 @@ const DateTimePickerWithArrows = () => { onClick={() => { dispatch({ type: 'decrementTimeByRange' }); }} - sx={{ height: '100%' }} + sx={{ + height: '100%', + display: {xs: 'none', md: 'flex'} + }} > @@ -196,7 +204,10 @@ const DateTimePickerWithArrows = () => { onClick={() => { dispatch({ type: 'incrementTimeByRange' }); }} - sx={{ height: '100%' }} + sx={{ + height: '100%', + display: {xs: 'none', md: 'flex'} + }} > diff --git a/web_ui/frontend/components/layout/PaddedContent.tsx b/web_ui/frontend/components/layout/PaddedContent.tsx index 1ec9f7508..1f6165e36 100644 --- a/web_ui/frontend/components/layout/PaddedContent.tsx +++ b/web_ui/frontend/components/layout/PaddedContent.tsx @@ -3,7 +3,7 @@ import { ReactNode } from 'react'; export const PaddedContent = ({ children }: { children: ReactNode }) => { return ( - + {children} ); From 7a934c8c235d1e5abecdb017600de2cb7b0c21af Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Thu, 12 Dec 2024 13:05:30 -0600 Subject: [PATCH 07/10] Make Director Mobile Friendly --- web_ui/frontend/app/director/metrics/page.tsx | 81 +++++++++---------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/web_ui/frontend/app/director/metrics/page.tsx b/web_ui/frontend/app/director/metrics/page.tsx index 2d47410f7..b23550b31 100644 --- a/web_ui/frontend/app/director/metrics/page.tsx +++ b/web_ui/frontend/app/director/metrics/page.tsx @@ -13,7 +13,7 @@ import { TransferBarGraph } from '@/app/director/metrics/components/TransferBarG const Page = () => { return ( - + {[ , @@ -25,56 +25,47 @@ const Page = () => { ))} - + - + - - - - - - - - - - - - - - - - - - - - + {[ + , + , + , + + ].map((component, index) => ( + + {component} + + ))} - - + + + + + + - + {[ { color={green[300]} />, ].map((component, index) => ( - + {component} ))} From 31d4190a66eeab06b3a7c22cb12a8950e14cde52 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Thu, 12 Dec 2024 13:07:50 -0600 Subject: [PATCH 08/10] Linter Fixups --- web_ui/frontend/app/(landing)/page.tsx | 24 +++++++++++--- .../app/(login)/components/CodeInput.tsx | 4 +-- .../app/(login)/components/PasswordInput.tsx | 6 +--- web_ui/frontend/app/(login)/layout.tsx | 4 +-- web_ui/frontend/app/(login)/login/page.tsx | 2 +- web_ui/frontend/app/config/Config.tsx | 2 +- web_ui/frontend/app/director/metrics/page.tsx | 21 ++++++++++--- web_ui/frontend/components/Namespace/Card.tsx | 31 +++++++++++++++---- .../components/Namespace/PendingCard.tsx | 31 ++++++++++++++----- .../Fields/ObjectField/ObjectField.tsx | 2 +- .../components/graphs/GraphOverlay.tsx | 12 +++---- .../components/layout/PaddedContent.tsx | 2 +- 12 files changed, 98 insertions(+), 43 deletions(-) diff --git a/web_ui/frontend/app/(landing)/page.tsx b/web_ui/frontend/app/(landing)/page.tsx index 004bb5b4c..c75db6247 100644 --- a/web_ui/frontend/app/(landing)/page.tsx +++ b/web_ui/frontend/app/(landing)/page.tsx @@ -19,13 +19,21 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import { Box, Container, Grid, List, ListItemButton, ListItemText, Skeleton, Typography } from '@mui/material'; +import { + Box, + Container, + Grid, + List, + ListItemButton, + ListItemText, + Skeleton, + Typography, +} from '@mui/material'; import Link from 'next/link'; import useSWR from 'swr'; import { getEnabledServers } from '@/helpers/util'; import { ServerType } from '@/index'; - export default function Home() { const { data: enabledServers, isLoading } = useSWR( 'getEnabledServers', @@ -42,8 +50,16 @@ export default function Home() { {enabledServers && enabledServers.map((service) => { return ( - - + + ); })} diff --git a/web_ui/frontend/app/(login)/components/CodeInput.tsx b/web_ui/frontend/app/(login)/components/CodeInput.tsx index cecf9fcb5..0a2b4330d 100644 --- a/web_ui/frontend/app/(login)/components/CodeInput.tsx +++ b/web_ui/frontend/app/(login)/components/CodeInput.tsx @@ -140,9 +140,9 @@ export default function CodeInput({ +
- - {children} - + {children}
); diff --git a/web_ui/frontend/app/(login)/login/page.tsx b/web_ui/frontend/app/(login)/login/page.tsx index f86e0ea27..2f8da6f24 100644 --- a/web_ui/frontend/app/(login)/login/page.tsx +++ b/web_ui/frontend/app/(login)/login/page.tsx @@ -206,7 +206,7 @@ export default function Home() { textAlign: 'center', whiteSpace: 'nowrap', overflow: 'hidden', - textOverflow: 'ellipsis' + textOverflow: 'ellipsis', }} > Administer your Pelican Platform diff --git a/web_ui/frontend/app/config/Config.tsx b/web_ui/frontend/app/config/Config.tsx index 12819c4b6..4bdb551dd 100644 --- a/web_ui/frontend/app/config/Config.tsx +++ b/web_ui/frontend/app/config/Config.tsx @@ -116,7 +116,7 @@ function Config({ metadata }: { metadata: ParameterMetadataRecord }) { )} - + {error && ( diff --git a/web_ui/frontend/app/director/metrics/page.tsx b/web_ui/frontend/app/director/metrics/page.tsx index b23550b31..f1c660c49 100644 --- a/web_ui/frontend/app/director/metrics/page.tsx +++ b/web_ui/frontend/app/director/metrics/page.tsx @@ -25,12 +25,15 @@ const Page = () => { ))} - + {[ - , + , { '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'} - /> + />, ].map((component, index) => ( - + {component} ))} @@ -59,7 +70,7 @@ const Page = () => { - + diff --git a/web_ui/frontend/components/Namespace/Card.tsx b/web_ui/frontend/components/Namespace/Card.tsx index 095ae0e23..d8a13935d 100644 --- a/web_ui/frontend/components/Namespace/Card.tsx +++ b/web_ui/frontend/components/Namespace/Card.tsx @@ -6,7 +6,8 @@ import { IconButton, Paper, Tooltip, - Typography, useMediaQuery, + Typography, + useMediaQuery, } from '@mui/material'; import { Delete, Download, Edit, Person } from '@mui/icons-material'; import Link from 'next/link'; @@ -28,8 +29,9 @@ export interface CardProps { } export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { - - const size = useMediaQuery((theme: Theme) => theme.breakpoints.down("md")) ? 'small' : 'medium'; + const size = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')) + ? 'small' + : 'medium'; const dispatch = useContext(AlertDispatchContext); const ref = useRef(null); @@ -55,9 +57,24 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { bgcolor={'secondary'} onClick={() => setTransition(!transition)} > - + - {namespace.prefix} + + {namespace.prefix} + @@ -69,7 +86,9 @@ export const Card = ({ namespace, authenticated, onUpdate }: CardProps) => { sx={{ my: 'auto', mr: 2, - ...(size === 'small' ? { width: 30, height: 30 } : { width: 40, height: 40 }) + ...(size === 'small' + ? { width: 30, height: 30 } + : { width: 40, height: 40 }), }} > diff --git a/web_ui/frontend/components/Namespace/PendingCard.tsx b/web_ui/frontend/components/Namespace/PendingCard.tsx index ae08b9e2a..e99451d5c 100644 --- a/web_ui/frontend/components/Namespace/PendingCard.tsx +++ b/web_ui/frontend/components/Namespace/PendingCard.tsx @@ -14,7 +14,6 @@ import { AlertDispatchContext } from '@/components/AlertProvider'; import { approveNamespace, denyNamespace } from '@/helpers/api'; import { Theme } from '@mui/system'; - export interface PendingCardProps { namespace: RegistryNamespace; onUpdate: () => void; @@ -28,8 +27,9 @@ export const PendingCard = ({ onAlert, authenticated, }: PendingCardProps) => { - - const size = useMediaQuery((theme: Theme) => theme.breakpoints.down("md")) ? 'small' : 'medium'; + const size = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')) + ? 'small' + : 'medium'; const ref = useRef(null); const [transition, setTransition] = useState(false); @@ -54,9 +54,24 @@ export const PendingCard = ({ bgcolor={'secondary'} onClick={() => setTransition(!transition)} > - + - {namespace.prefix} + + {namespace.prefix} + @@ -68,8 +83,10 @@ export const PendingCard = ({ sx={{ my: 'auto', mr: 2, - ...(size === 'small' ? { width: 30, height: 30 } : { width: 40, height: 40 }) - }} + ...(size === 'small' + ? { width: 30, height: 30 } + : { width: 40, height: 40 }), + }} > diff --git a/web_ui/frontend/components/configuration/Fields/ObjectField/ObjectField.tsx b/web_ui/frontend/components/configuration/Fields/ObjectField/ObjectField.tsx index c8e8bf221..7d7d17788 100644 --- a/web_ui/frontend/components/configuration/Fields/ObjectField/ObjectField.tsx +++ b/web_ui/frontend/components/configuration/Fields/ObjectField/ObjectField.tsx @@ -186,7 +186,7 @@ const ObjectCard = ({ name, updated, onClick }: ObjectCardProps) => { my: 'auto', ml: 1, overflow: 'hidden', - textOverflow: 'ellipsis' + textOverflow: 'ellipsis', }} > {name} diff --git a/web_ui/frontend/components/graphs/GraphOverlay.tsx b/web_ui/frontend/components/graphs/GraphOverlay.tsx index 2ac3c731e..b7187965b 100644 --- a/web_ui/frontend/components/graphs/GraphOverlay.tsx +++ b/web_ui/frontend/components/graphs/GraphOverlay.tsx @@ -15,7 +15,7 @@ import { MenuItem, Select, Typography, - Grid + Grid, } from '@mui/material'; import { DateTimePicker } from '@mui/x-date-pickers'; import { KeyboardArrowLeft, KeyboardArrowRight } from '@mui/icons-material'; @@ -82,11 +82,9 @@ export const GraphOverlay = ({ children }: { children: ReactNode }) => { return ( <> - + - + {graphStart.toFormat(format)} - {graphContext.time.toFormat('f')} @@ -183,7 +181,7 @@ const DateTimePickerWithArrows = () => { }} sx={{ height: '100%', - display: {xs: 'none', md: 'flex'} + display: { xs: 'none', md: 'flex' }, }} > @@ -206,7 +204,7 @@ const DateTimePickerWithArrows = () => { }} sx={{ height: '100%', - display: {xs: 'none', md: 'flex'} + display: { xs: 'none', md: 'flex' }, }} > diff --git a/web_ui/frontend/components/layout/PaddedContent.tsx b/web_ui/frontend/components/layout/PaddedContent.tsx index 1f6165e36..39e518643 100644 --- a/web_ui/frontend/components/layout/PaddedContent.tsx +++ b/web_ui/frontend/components/layout/PaddedContent.tsx @@ -3,7 +3,7 @@ import { ReactNode } from 'react'; export const PaddedContent = ({ children }: { children: ReactNode }) => { return ( - + {children} ); From efdba92d8abbd65e8f41412639b615b6f0234dab Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Mon, 16 Dec 2024 09:56:30 -0600 Subject: [PATCH 09/10] Add keys to iterated components --- web_ui/frontend/app/director/metrics/page.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web_ui/frontend/app/director/metrics/page.tsx b/web_ui/frontend/app/director/metrics/page.tsx index f1c660c49..ba926d3a9 100644 --- a/web_ui/frontend/app/director/metrics/page.tsx +++ b/web_ui/frontend/app/director/metrics/page.tsx @@ -31,22 +31,26 @@ const Page = () => { {[ , , , Date: Mon, 16 Dec 2024 10:33:51 -0600 Subject: [PATCH 10/10] JS linter --- web_ui/frontend/app/navigation.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/web_ui/frontend/app/navigation.tsx b/web_ui/frontend/app/navigation.tsx index 9781ad237..2119b3150 100644 --- a/web_ui/frontend/app/navigation.tsx +++ b/web_ui/frontend/app/navigation.tsx @@ -68,11 +68,19 @@ const NavigationConfig: NavigationConfiguration = { ], director: [ { title: 'Dashboard', href: '/director/', icon: }, - { title: 'Metrics', href: '/director/metrics/', icon: , - allowedRoles: ['admin'], }, + { + title: 'Metrics', + href: '/director/metrics/', + icon: , + allowedRoles: ['admin'], + }, { title: 'Map', href: '/director/map/', icon: }, - { title: 'Config', href: '/config/', icon: , - allowedRoles: ['admin'], }, + { + title: 'Config', + href: '/config/', + icon: , + allowedRoles: ['admin'], + }, ], cache: [ { title: 'Dashboard', href: '/cache/', icon: },