1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-23 22:50:09 +03:00

F #5422: Improve description about current route (#2036)

This commit is contained in:
Sergio Betanzos 2022-05-12 15:38:27 +02:00 committed by GitHub
parent c5ccd9e83d
commit bb715f162e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 137 additions and 112 deletions

View File

@ -22,6 +22,8 @@ import {
import loadable from '@loadable/component'
import { T } from 'client/constants'
const Dashboard = loadable(
() => import('client/containers/Dashboard/Provision'),
{ ssr: false }
@ -64,48 +66,48 @@ export const PATH = {
export const ENDPOINTS = [
{
label: 'Dashboard',
title: T.Dashboard,
path: PATH.DASHBOARD,
sidebar: true,
icon: DashboardIcon,
Component: Dashboard,
},
{
label: 'Providers',
title: T.Providers,
path: PATH.PROVIDERS.LIST,
sidebar: true,
icon: ProvidersIcon,
Component: Providers,
},
{
label: 'Create Provider',
title: T.CreateProvider,
path: PATH.PROVIDERS.CREATE,
Component: CreateProvider,
},
{
label: 'Edit Provider template',
title: T.UpdateProvider,
path: PATH.PROVIDERS.EDIT,
Component: CreateProvider,
},
{
label: 'Provisions',
title: T.Provisions,
path: PATH.PROVISIONS.LIST,
sidebar: true,
icon: ProvisionsIcon,
Component: Provisions,
},
{
label: 'Create Provision',
title: T.CreateProvision,
path: PATH.PROVISIONS.CREATE,
Component: CreateProvision,
},
{
label: 'Edit Provision template',
title: 'Edit Provision template',
path: PATH.PROVISIONS.EDIT,
Component: CreateProvision,
},
{
label: 'Settings',
title: T.Settings,
path: PATH.SETTINGS,
sidebar: true,
icon: SettingsIcon,

View File

@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import loadable from '@loadable/component'
import {
ReportColumns as DashboardIcon,
Settings as SettingsIcon,
} from 'iconoir-react'
import loadable from '@loadable/component'
import { T } from 'client/constants'
const Dashboard = loadable(
() => import('client/containers/Dashboard/Sunstone'),
{ ssr: false }
@ -43,7 +46,7 @@ export const PATH = {
export const ENDPOINTS = [
{
label: 'Dashboard',
title: T.Dashboard,
path: PATH.DASHBOARD,
sidebar: true,
icon: DashboardIcon,
@ -51,7 +54,7 @@ export const ENDPOINTS = [
Component: Dashboard,
},
{
label: 'Settings',
title: T.Settings,
path: PATH.SETTINGS,
sidebar: true,
icon: SettingsIcon,
@ -59,13 +62,13 @@ export const ENDPOINTS = [
Component: Settings,
},
{
label: 'Guacamole',
title: 'Guacamole', // no need to translate
disableLayout: true,
path: PATH.GUACAMOLE,
Component: Guacamole,
},
{
label: 'WebMKS',
title: 'WebMKS', // no need to translate
disableLayout: true,
path: PATH.WMKS,
Component: WebMKS,

View File

@ -204,23 +204,24 @@ export const PATH = {
const ENDPOINTS = [
{
label: T.Instances,
title: T.Instances,
icon: InstancesIcons,
routes: [
{
label: T.VMs,
title: T.VMs,
path: PATH.INSTANCE.VMS.LIST,
sidebar: true,
icon: VmsIcons,
Component: VirtualMachines,
},
{
label: (params) => [T.VMDetailId, params.id],
title: T.VM,
description: (params) => `#${params?.id}`,
path: PATH.INSTANCE.VMS.DETAIL,
Component: VirtualMachineDetail,
},
{
label: T.VirtualRouters,
title: T.VirtualRouters,
path: PATH.INSTANCE.VROUTERS.LIST,
sidebar: true,
icon: VRoutersIcons,
@ -229,80 +230,85 @@ const ENDPOINTS = [
],
},
{
label: T.Templates,
title: T.Templates,
icon: TemplatesIcon,
routes: [
{
label: T.VMTemplates,
title: T.VMTemplates,
path: PATH.TEMPLATE.VMS.LIST,
sidebar: true,
icon: TemplateIcon,
Component: VmTemplates,
},
{
label: T.InstantiateVmTemplate,
title: T.InstantiateVmTemplate,
description: (_, state) =>
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
path: PATH.TEMPLATE.VMS.INSTANTIATE,
Component: InstantiateVmTemplate,
},
{
label: T.CreateVmTemplate,
title: (_, state) =>
state?.ID !== undefined ? T.UpdateVmTemplate : T.CreateVmTemplate,
description: (_, state) =>
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
path: PATH.TEMPLATE.VMS.CREATE,
Component: CreateVmTemplate,
},
],
},
{
label: T.Storage,
title: T.Storage,
icon: StorageIcon,
routes: [
{
label: T.Datastores,
title: T.Datastores,
path: PATH.STORAGE.DATASTORES.LIST,
sidebar: true,
icon: DatastoreIcon,
Component: Datastores,
},
{
label: T.Images,
title: T.Images,
path: PATH.STORAGE.IMAGES.LIST,
sidebar: true,
icon: ImageIcon,
Component: Images,
},
{
label: T.Marketplaces,
title: T.Marketplaces,
path: PATH.STORAGE.MARKETPLACES.LIST,
sidebar: true,
icon: MarketplaceIcon,
Component: Marketplaces,
},
{
label: T.Apps,
title: T.Apps,
path: PATH.STORAGE.MARKETPLACE_APPS.LIST,
sidebar: true,
icon: MarketplaceAppIcon,
Component: MarketplaceApps,
},
{
label: T.CreateMarketApp,
title: T.CreateMarketApp,
path: PATH.STORAGE.MARKETPLACE_APPS.CREATE,
Component: CreateMarketplaceApp,
},
],
},
{
label: T.Networks,
title: T.Networks,
icon: NetworksIcon,
routes: [
{
label: T.VirtualNetworks,
title: T.VirtualNetworks,
path: PATH.NETWORK.VNETS.LIST,
sidebar: true,
icon: NetworkIcon,
Component: VirtualNetworks,
},
{
label: T.NetworkTemplates,
title: T.NetworkTemplates,
path: PATH.NETWORK.VN_TEMPLATES.LIST,
sidebar: true,
icon: NetworkTemplateIcon,
@ -311,40 +317,42 @@ const ENDPOINTS = [
],
},
{
label: T.Infrastructure,
title: T.Infrastructure,
icon: InfrastructureIcon,
routes: [
{
label: T.Clusters,
title: T.Clusters,
path: PATH.INFRASTRUCTURE.CLUSTERS.LIST,
sidebar: true,
icon: ClusterIcon,
Component: Clusters,
},
{
label: (params) => [T.ClusterDetailId, params.id],
title: T.Cluster,
description: (params) => `#${params?.id}`,
path: PATH.INFRASTRUCTURE.CLUSTERS.DETAIL,
Component: ClusterDetail,
},
{
label: T.Hosts,
title: T.Hosts,
path: PATH.INFRASTRUCTURE.HOSTS.LIST,
sidebar: true,
icon: HostIcon,
Component: Hosts,
},
{
label: T.CreateHost,
title: T.CreateHost,
path: PATH.INFRASTRUCTURE.HOSTS.CREATE,
Component: CreateHost,
},
{
label: (params) => [T.HostDetailId, params.id],
title: T.Host,
description: (params) => `#${params?.id}`,
path: PATH.INFRASTRUCTURE.HOSTS.DETAIL,
Component: HostDetail,
},
{
label: T.Zones,
title: T.Zones,
path: PATH.INFRASTRUCTURE.ZONES.LIST,
sidebar: true,
icon: ZoneIcon,
@ -353,30 +361,32 @@ const ENDPOINTS = [
],
},
{
label: T.System,
title: T.System,
icon: SystemIcon,
routes: [
{
label: T.Users,
title: T.Users,
path: PATH.SYSTEM.USERS.LIST,
sidebar: true,
icon: UserIcon,
Component: Users,
},
{
label: (params) => [T.UserDetailId, params.id],
title: T.User,
description: (params) => `#${params?.id}`,
path: PATH.SYSTEM.USERS.DETAIL,
Component: UserDetail,
},
{
label: T.Groups,
title: T.Groups,
path: PATH.SYSTEM.GROUPS.LIST,
sidebar: true,
icon: GroupIcon,
Component: Groups,
},
{
label: (params) => [T.GroupDetailId, params.id],
title: T.Group,
description: (params) => `#${params?.id}`,
path: PATH.SYSTEM.GROUPS.DETAIL,
Component: GroupDetail,
},

View File

@ -14,30 +14,23 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useRef, useEffect, useMemo } from 'react'
import { useRef, useMemo } from 'react'
import PropTypes from 'prop-types'
import { useParams } from 'react-router-dom'
import { Box, Container } from '@mui/material'
import { CSSTransition } from 'react-transition-group'
import { useGeneral, useGeneralApi } from 'client/features/General'
import { useGeneral } from 'client/features/General'
import Header from 'client/components/Header'
import Footer from 'client/components/Footer'
import internalStyles from 'client/components/HOC/InternalLayout/styles'
import { sidebar, footer } from 'client/theme/defaults'
const InternalLayout = ({ title, disableLayout, children }) => {
const InternalLayout = ({ children, ...route }) => {
const classes = internalStyles()
const container = useRef()
const { isFixMenu } = useGeneral()
const { changeTitle } = useGeneralApi()
const params = useParams()
useEffect(() => {
changeTitle(typeof title === 'function' ? title(params) : title)
}, [title])
if (disableLayout) {
if (route.disableLayout) {
return (
<Box data-cy="main-layout" className={classes.root}>
<Box
@ -64,7 +57,7 @@ const InternalLayout = ({ title, disableLayout, children }) => {
[isFixMenu]
)}
>
<Header scrollContainer={container.current} />
<Header scrollContainer={container.current} route={route} />
<Box component="main" className={classes.main}>
<CSSTransition
in
@ -92,7 +85,6 @@ const InternalLayout = ({ title, disableLayout, children }) => {
}
InternalLayout.propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
disableLayout: PropTypes.bool,
children: PropTypes.any,
}

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useMemo } from 'react'
import { memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import { useParams, useLocation } from 'react-router-dom'
import {
AppBar,
@ -37,12 +38,25 @@ import Zone from 'client/components/Header/Zone'
import { Translate } from 'client/components/HOC'
import { sentenceCase } from 'client/utils'
const Header = () => {
const Header = memo(({ route: { title, description } = {} }) => {
const { isOneAdmin } = useAuth()
const { fixMenu } = useGeneralApi()
const { appTitle, title, isBeta, withGroupSwitcher } = useGeneral()
const { appTitle, isBeta, withGroupSwitcher } = useGeneral()
const params = useParams()
const { state } = useLocation()
const appAsSentence = useMemo(() => sentenceCase(appTitle), [appTitle])
const [ensuredTitle, ensuredDescription] = useMemo(() => {
if (!title) return []
const ensure = (label) =>
typeof label === 'function' ? label(params, state) : label
return [ensure(title), ensure(description)]
}, [params, state, title, description])
return (
<AppBar data-cy="header" elevation={0} position="absolute">
<Toolbar>
@ -58,10 +72,9 @@ const Header = () => {
<Box
flexGrow={1}
ml={2}
sx={{
display: { xs: 'none', sm: 'inline-flex' },
userSelect: 'none',
}}
alignItems="baseline"
display={{ xs: 'none', sm: 'inline-flex' }}
sx={{ userSelect: 'none' }}
>
<Typography variant="h6" data-cy="header-app-title">
{'One'}
@ -83,22 +96,35 @@ const Header = () => {
</Typography>
)}
</Typography>
{title && (
<Typography
variant="h6"
data-cy="header-description"
sx={{
display: { xs: 'none', md: 'block' },
'&::before': {
content: '"|"',
margin: '0.5em',
color: 'primary.contrastText',
},
}}
>
<Translate word={title} />
</Typography>
)}
<Box
alignItems="baseline"
display={{ xs: 'none', md: 'inline-flex' }}
gap=".5em"
sx={{
'&::before': { content: '"|"', ml: '.5em' },
}}
>
{ensuredTitle && (
<Typography
noWrap
variant="subtitle1"
data-cy="header-title"
sx={{ color: 'primary.contrastText' }}
>
<Translate word={ensuredTitle} />
</Typography>
)}
{ensuredDescription && (
<Typography
noWrap
variant="subtitle2"
data-cy="header-description"
sx={{ color: 'text.secondary' }}
>
<Translate word={ensuredDescription} />
</Typography>
)}
</Box>
</Box>
<Stack
direction="row"
@ -113,10 +139,13 @@ const Header = () => {
</Toolbar>
</AppBar>
)
}
})
Header.propTypes = {
route: PropTypes.object,
scrollContainer: PropTypes.object,
}
Header.displayName = 'Header'
export default Header

View File

@ -38,12 +38,12 @@ import { Translate } from 'client/components/HOC'
* Renders nested list options for sidebar.
*
* @param {object} props - Props
* @param {string} props.label - Label
* @param {string} props.title - Title
* @param {object[]} props.routes - Nested list of routes
* @param {ReactElement} props.icon - Icon
* @returns {ReactElement} Sidebar option that includes other list of routes
*/
const SidebarCollapseItem = ({ label = '', routes = [], icon: Icon }) => {
const SidebarCollapseItem = ({ title = '', routes = [], icon: Icon }) => {
const classes = sidebarStyles()
const { pathname } = useLocation()
const { isFixMenu } = useGeneral()
@ -75,8 +75,8 @@ const SidebarCollapseItem = ({ label = '', routes = [], icon: Icon }) => {
</ListItemIcon>
)}
<ListItemText
data-cy={label.toLocaleLowerCase()}
primary={<Translate word={label} />}
data-cy={title.toLocaleLowerCase()}
primary={<Translate word={title} />}
{...(expanded && { className: 'open' })}
primaryTypographyProps={{ variant: 'body1' }}
/>
@ -90,10 +90,7 @@ const SidebarCollapseItem = ({ label = '', routes = [], icon: Icon }) => {
>
<List component="div" disablePadding>
{routes
?.filter(
({ sidebar = false, ...route }) =>
sidebar && typeof route.label === 'string'
)
?.filter(({ sidebar = false }) => sidebar)
?.map((subItem, index) => (
<SidebarLink key={`subitem-${index}`} isSubItem {...subItem} />
))}
@ -104,7 +101,7 @@ const SidebarCollapseItem = ({ label = '', routes = [], icon: Icon }) => {
}
SidebarCollapseItem.propTypes = {
label: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
icon: PropTypes.oneOfType([PropTypes.node, PropTypes.object]),
routes: PropTypes.arrayOf(
PropTypes.shape({

View File

@ -31,7 +31,7 @@ import { Translate } from 'client/components/HOC'
const SidebarLink = memo(
({
label = '',
title = '',
path = '/',
icon: Icon,
devMode = false,
@ -63,7 +63,7 @@ const SidebarLink = memo(
</ListItemIcon>
)}
<ListItemText
primary={<Translate word={label} />}
primary={<Translate word={title} />}
primaryTypographyProps={{
...(devMode && { component: DevTypography }),
'data-cy': 'main-menu-item-text',
@ -76,7 +76,7 @@ const SidebarLink = memo(
)
SidebarLink.propTypes = {
label: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
icon: PropTypes.any,
devMode: PropTypes.bool,

View File

@ -57,16 +57,18 @@ module.exports = {
Create: 'Create',
CreateHost: 'Create Host',
CreateMarketApp: 'Create Marketplace App',
CreateProvider: 'Create Provider',
CreateProvision: 'Create Provision',
CreateVmTemplate: 'Create VM Template',
CurrentGroup: 'Current group: %s',
CurrentOwner: 'Current owner: %s',
Delete: 'Delete',
DeleteAllImages: 'Delete all images',
DeleteDb: 'Delete database',
DeleteScheduleAction: 'Delete schedule action: %s',
DeleteSeveralTemplates: 'Delete several Templates',
DeleteTemplate: 'Delete Template',
DeleteSomething: 'Delete: %s',
DeleteAllImages: 'Delete all images',
DeleteTemplate: 'Delete Template',
Deploy: 'Deploy',
Detach: 'Detach',
DetachSomething: 'Detach: %s',
@ -152,7 +154,9 @@ module.exports = {
UnReschedule: 'Un-Reschedule',
Unshare: 'Unshare',
Update: 'Update',
UpdateProvider: 'Update Provider',
UpdateScheduleAction: 'Update schedule action: %s',
UpdateVmTemplate: 'Update VM Template',
/* questions */
Yes: 'Yes',
@ -285,10 +289,8 @@ module.exports = {
/* sections - system */
User: 'User',
UserDetailId: 'User #%s',
Users: 'Users',
Group: 'Group',
GroupDetailId: 'Group #%s',
Groups: 'Groups',
VDC: 'VDC',
VDCs: 'VDCs',
@ -297,10 +299,8 @@ module.exports = {
/* sections - infrastructure */
Cluster: 'Cluster',
ClusterDetailId: 'Cluster #%s',
Clusters: 'Clusters',
Host: 'Host',
HostDetailId: 'Host #%s',
Hosts: 'Hosts',
Infrastructure: 'Infrastructure',
Zone: 'Zone',
@ -333,7 +333,6 @@ module.exports = {
/* sections - templates & instances */
Instances: 'Instances',
VM: 'VM',
VMDetailId: 'VM #%s',
VMs: 'VMs',
VirtualRouter: 'Virtual Router',
VirtualRouters: 'Virtual Routers',

View File

@ -18,7 +18,6 @@ import { createAction } from '@reduxjs/toolkit'
export const fixMenu = createAction('Fix menu')
export const changeZone = createAction('Change zone')
export const changeLoading = createAction('Change loading')
export const changeTitle = createAction('Change title')
export const changeAppTitle = createAction('Change App title')
export const dismissSnackbar = createAction('Dismiss snackbar')

View File

@ -29,7 +29,6 @@ export const useGeneralApi = () => {
return {
fixMenu: (isFixed) => dispatch(actions.fixMenu(isFixed)),
changeLoading: (isLoading) => dispatch(actions.changeLoading(isLoading)),
changeTitle: (title) => dispatch(actions.changeTitle(title)),
changeAppTitle: (appTitle) => dispatch(actions.changeAppTitle(appTitle)),
changeZone: (zone) => dispatch(actions.changeZone(zone)),

View File

@ -22,7 +22,6 @@ import { APPS_IN_BETA, APPS_WITH_SWITCHER } from 'client/constants'
const initial = {
zone: 0,
title: null,
appTitle: null,
isBeta: false,
withGroupSwitcher: false,
@ -54,10 +53,6 @@ const { name, reducer } = createSlice({
...state,
isLoading: !!payload,
}))
.addCase(actions.changeTitle, (state, { payload }) => ({
...state,
title: payload,
}))
.addCase(actions.changeAppTitle, (state, { payload: appTitle }) => {
const lowerAppTitle = String(appTitle).toLowerCase()
const isBeta = APPS_IN_BETA?.includes(lowerAppTitle)

View File

@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import loadable from '@loadable/component'
import { Code as DevIcon } from 'iconoir-react'
import loadable from '@loadable/component'
const TestApi = loadable(() => import('client/containers/TestApi'), {
ssr: false,
@ -30,7 +30,7 @@ export const PATH = {
export const ENDPOINTS = [
{
label: 'Test API',
title: 'Test API', // no need to translate
path: PATH.TEST_API,
devMode: true,
sidebar: true,
@ -38,7 +38,7 @@ export const ENDPOINTS = [
Component: TestApi,
},
{
label: 'Test Form',
title: 'Test Form', // no need to translate
path: PATH.TEST_FORM,
devMode: true,
sidebar: true,

View File

@ -28,9 +28,9 @@ import {
import { ProtectedRoute, NoAuthRoute } from 'client/components/Route'
import { InternalLayout } from 'client/components/HOC'
const renderRoute = ({ Component, label, disableLayout, ...rest }, index) => (
<ProtectedRoute key={index} exact {...rest}>
<InternalLayout title={label} disableLayout={disableLayout}>
const renderRoute = ({ Component, ...route }) => (
<ProtectedRoute key={route.path} exact {...route}>
<InternalLayout {...route}>
<Component fallback={<LinearProgress color="secondary" />} />
</InternalLayout>
</ProtectedRoute>