From db3e57b1e18de76f74e138be263b5c71bc240516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 16 Apr 2024 13:11:48 +0200 Subject: [PATCH] Add: Implement new Menu Use opensight AppNavigation to implement a new menu. --- src/web/components/bar/menubar.jsx | 308 --------------- src/web/components/menu/menu.jsx | 585 +++++++++++++++++++++-------- src/web/pages/page.jsx | 123 ++---- 3 files changed, 461 insertions(+), 555 deletions(-) delete mode 100644 src/web/components/bar/menubar.jsx diff --git a/src/web/components/bar/menubar.jsx b/src/web/components/bar/menubar.jsx deleted file mode 100644 index ea33f47a71..0000000000 --- a/src/web/components/bar/menubar.jsx +++ /dev/null @@ -1,308 +0,0 @@ -/* Copyright (C) 2016-2022 Greenbone AG - * - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import React from 'react'; - -import {connect} from 'react-redux'; - -import styled from 'styled-components'; - -import _ from 'gmp/locale'; - -import {isDefined} from 'gmp/utils/identity'; - -import Layout from 'web/components/layout/layout'; - -import Menu from 'web/components/menu/menu'; -import MenuEntry from 'web/components/menu/menuentry'; -import MenuHelpEntry from 'web/components/menu/menuhelpentry'; -import MenuSection from 'web/components/menu/menusection'; - -import {isLoggedIn} from 'web/store/usersettings/selectors'; - -import compose from 'web/utils/compose'; -import PropTypes from 'web/utils/proptypes'; -import Theme from 'web/utils/theme'; -import withGmp from 'web/utils/withGmp'; -import withCapabilities from 'web/utils/withCapabilities'; - -const MENU_BAR_HEIGHT = '35px'; - -const Ul = styled.ul` - width: 100%; - display: flex; - flex-direction: row; - margin: 0; - padding: 0; - list-style: none; -`; - -const Wrapper = styled(Layout)` - background-color: ${Theme.darkGray}; - height: ${MENU_BAR_HEIGHT}; - position: fixed; - top: 42px; - left: 0; - right: 0; - z-index: ${Theme.Layers.menu}; -`; - -const MenuBarPlaceholder = styled.div` - height: ${MENU_BAR_HEIGHT}; -`; - -// eslint-disable-next-line no-shadow -const MenuBar = ({isLoggedIn, capabilities}) => { - if (!isLoggedIn || !isDefined(capabilities)) { - return null; - } - - const may_op_scans = [ - 'tasks', - 'reports', - 'results', - 'vulns', - 'overrides', - 'notes', - ].reduce((sum, cur) => sum || capabilities.mayAccess(cur), false); - - const may_op_configuration = [ - 'targets', - 'port_lists', - 'credentials', - 'scan_configs', - 'alerts', - 'schedules', - 'report_configs', - 'report_formats', - 'scanners', - 'filters', - 'tags', - ].reduce((sum, cur) => sum || capabilities.mayAccess(cur), false); - - const mayOpNotesOverrides = ['notes', 'overrides'].reduce( - (sum, cur) => sum || capabilities.mayAccess(cur), - false, - ); - - const mayOpAlertsSchedulesReportFormats = [ - 'alerts', - 'schedules', - 'report_formats', - ].reduce((sum, cur) => sum || capabilities.mayAccess(cur), false); - - const mayOpScannersFiltersTags = ['scanners', 'filters', 'tags'].reduce( - (sum, cur) => sum || capabilities.mayAccess(cur), - false, - ); - - const mayOpResilience = ['tickets', 'policies', 'audits'].reduce( - (sum, cur) => sum || capabilities.mayAccess(cur), - false, - ); - - const mayOpAssets = ['assets', 'tls_certificates'].reduce( - (sum, cur) => sum || capabilities.mayAccess(cur), - false, - ); - - return ( - - - -
    - - {may_op_scans && ( - - {capabilities.mayAccess('tasks') && ( - - )} - {capabilities.mayAccess('reports') && ( - - )} - {capabilities.mayAccess('results') && ( - - )} - {capabilities.mayAccess('vulns') && ( - - )} - {mayOpNotesOverrides && ( - - {capabilities.mayAccess('notes') && ( - - )} - {capabilities.mayAccess('overrides') && ( - - )} - - )} - - )} - {mayOpAssets && ( - - {capabilities.mayAccess('assets') && ( - - )} - {capabilities.mayAccess('assets') && ( - - )} - {capabilities.mayAccess('tls_certificates') && ( - - )} - - )} - {mayOpResilience && ( - - {capabilities.mayAccess('tickets') && ( - - )} - - {capabilities.mayAccess('policies') && ( - - )} - {capabilities.mayAccess('audits') && ( - - )} - - - )} - {capabilities.mayAccess('info') && ( - - - - - - - - )} - {may_op_configuration && ( - - {capabilities.mayAccess('targets') && ( - - )} - {capabilities.mayAccess('port_lists') && ( - - )} - {capabilities.mayAccess('credentials') && ( - - )} - {capabilities.mayAccess('configs') && ( - - )} - {mayOpAlertsSchedulesReportFormats && ( - - {capabilities.mayAccess('alerts') && ( - - )} - {capabilities.mayAccess('schedules') && ( - - )} - {capabilities.mayAccess('report_configs') && ( - - )} - {capabilities.mayAccess('report_formats') && ( - - )} - - )} - {mayOpScannersFiltersTags && ( - - {capabilities.mayAccess('scanners') && ( - - )} - {capabilities.mayAccess('filters') && ( - - )} - {capabilities.mayAccess('tags') && ( - - )} - - )} - - )} - - {capabilities.mayAccess('users') && ( - - )} - {capabilities.mayAccess('groups') && ( - - )} - {capabilities.mayAccess('roles') && ( - - )} - {capabilities.mayAccess('permissions') && ( - - )} - - {capabilities.mayAccess('system_reports') && ( - - )} - - {capabilities.mayAccess('feeds') && ( - - )} - - {capabilities.mayOp('describe_auth') && ( - - {capabilities.mayOp('modify_auth') && ( - - )} - {capabilities.mayOp('modify_auth') && ( - - )} - - )} - - - - - - -
-
-
- ); -}; - -MenuBar.propTypes = { - capabilities: PropTypes.capabilities, - gmp: PropTypes.gmp.isRequired, - isLoggedIn: PropTypes.bool.isRequired, -}; - -const mapStateToProps = rootState => ({ - isLoggedIn: isLoggedIn(rootState), -}); - -export default compose( - withCapabilities, - withGmp, - connect(mapStateToProps), -)(MenuBar); - -// vim: set ts=2 sw=2 tw=80: diff --git a/src/web/components/menu/menu.jsx b/src/web/components/menu/menu.jsx index 2ef166fc73..19be1eb7a4 100644 --- a/src/web/components/menu/menu.jsx +++ b/src/web/components/menu/menu.jsx @@ -1,173 +1,446 @@ -/* Copyright (C) 2016-2022 Greenbone AG +/* SPDX-FileCopyrightText: 2024 Greenbone AG * * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . */ import React from 'react'; -import styled, {keyframes} from 'styled-components'; - -import {isDefined, hasValue} from 'gmp/utils/identity'; +import {AppNavigation} from '@greenbone/opensight-ui-components'; -import PropTypes from 'web/utils/proptypes'; -import Theme from 'web/utils/theme'; +import useTranslation from 'web/hooks/useTranslation'; +import useCapabilities from 'web/utils/useCapabilities'; +import TaskIcon from 'web/components/icon/taskicon'; import Link from 'web/components/link/link'; +import ReportIcon from 'web/components/icon/reporticon'; +import ResultIcon from 'web/components/icon/resulticon'; +import VulnerabilityIcon from 'web/components/icon/vulnerabilityicon'; +import NoteIcon from 'web/components/icon/noteicon'; +import OverrideIcon from 'web/components/icon/overrideicon'; +import HostIcon from 'web/components/icon/hosticon'; +import OperatingSystemIcon from 'web/components/icon/ossvgicon'; +import TlsCertificateIcon from 'web/components/icon/tlscertificateicon'; +import TicketIcon from 'web/components/icon/ticketicon'; +import PolicyIcon from 'web/components/icon/policyicon'; +import AuditIcon from 'web/components/icon/auditicon'; +import NvtIcon from 'web/components/icon/nvticon'; +import CveIcon from 'web/components/icon/cveicon'; +import CpeIcon from 'web/components/icon/cpeicon'; +import CertBundAdvIcon from 'web/components/icon/certbundadvicon'; +import DfnCertAdvIcon from 'web/components/icon/dfncertadvicon'; +import TargetIcon from 'web/components/icon/targeticon'; +import PortListIcon from 'web/components/icon/portlisticon'; +import CvssIcon from 'web/components/icon/cvssicon'; +import CredentialIcon from 'web/components/icon/credentialicon'; +import ScanConfigIcon from 'web/components/icon/scanconfigicon'; +import AlertIcon from 'web/components/icon/alerticon'; +import ScheduleIcon from 'web/components/icon/scheduleicon'; +import ReportFormatIcon from 'web/components/icon/reportformaticon'; +import ScannerIcon from 'web/components/icon/scannericon'; +import FilterIcon from 'web/components/icon/filtericon'; +import TagsSvgIcon from 'web/components/icon/tagssvgicon'; +import UserIcon from 'web/components/icon/usericon'; +import GroupIcon from 'web/components/icon/groupicon'; +import RoleIcon from 'web/components/icon/roleicon'; +import PermissionIcon from 'web/components/icon/permissionicon'; +import PerformanceIcon from 'web/components/icon/performanceicon'; +import TrashcanIcon from 'web/components/icon/trashcanicon'; +import FeedIcon from 'web/components/icon/feedicon'; +import LdapIcon from 'web/components/icon/ldapicon'; +import RadiusIcon from 'web/components/icon/radiusicon'; -import MenuSection from 'web/components/menu/menusection'; - -const StyledMenu = styled.li` - flex-grow: 1; - flex-shrink: 1; - flex-basis: 0; - margin: 1px; - height: 35px; - - &:hover { - border-bottom: 3px solid ${Theme.green}; - } - - & a { - display: flex; - flex-grow: 1; - align-items: center ${'' /* center text vertically*/}; - } - - & a, - & a:hover, - & a:focus, - & a:link { - text-decoration: none; - color: ${Theme.black}; - } -`; +const Menu = () => { + const [_] = useTranslation(); + const capabilities = useCapabilities(); -const DefaultEntry = styled.div` - display: flex; - justify-content: center; - flex-grow: 1; - - & a, - & a:hover, - & a:focus, - & a:link { - color: ${Theme.white}; - display: block; - height: 35px; - line-height: 35px; - font-size: 10px; - font-weight: bold; - text-align: center; - } -`; - -export const StyledMenuEntry = styled.li` - display: flex; - list-style: none; - background: ${Theme.white}; - text-indent: 12px; - text-align: left; - color: ${Theme.darkGray}; - min-height: 22px; - font-size: 10px; - font-weight: bold; - - & a { - line-height: 22px; - color: ${Theme.darkGray}; - } - - & a:hover { - color: ${Theme.white}; - background: ${Theme.green}; - } -`; - -const MenuList = styled.ul` - width: 255px; - z-index: ${Theme.Layers.menu}; - position: absolute; - display: none; - background: ${Theme.green}; - border-top: 1px solid ${Theme.mediumGray}; - border-left: 1px solid ${Theme.mediumGray}; - border-right: 1px solid ${Theme.mediumGray}; - border-bottom: 1px solid ${Theme.mediumGray}; - list-style: none; - padding-left: 0px; - margin-left: -1px; + const mayOpScans = [ + 'tasks', + 'reports', + 'results', + 'vulns', + 'overrides', + 'notes', + ].reduce((sum, cur) => sum || capabilities.mayAccess(cur), false); + const mayOpConfiguration = [ + 'targets', + 'port_lists', + 'credentials', + 'scan_configs', + 'alerts', + 'schedules', + 'report_configs', + 'report_formats', + 'scanners', + 'filters', + 'tags', + ].reduce((sum, cur) => sum || capabilities.mayAccess(cur), false); + const mayOpResilience = ['tickets', 'policies', 'audits'].reduce( + (sum, cur) => sum || capabilities.mayAccess(cur), + false, + ); + const mayOpAssets = ['assets', 'tls_certificates'].reduce( + (sum, cur) => sum || capabilities.mayAccess(cur), + false, + ); - ${StyledMenu}:hover & { - display: block; - } - animation: ${keyframes({ - '0%': { - transform: 'scale(0.9)', - transformOrigin: 'top', - opacity: 0, - translateY: '0px', + const menuPoints = [ + [ + { + icon: () => {}, + label: _('Dashboards'), + to: '/', + active: false, + defaultOpened: false, + isExternal: false, }, - '100%': { - transform: 'scale(1.0)', - transformOrigin: 'top', - opacity: 1, - translate: 0, + ], + [ + mayOpScans && { + icon: () => {}, + label: _('Scans'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + capabilities.mayAccess('tasks') && { + icon: TaskIcon, + label: _('Tasks'), + to: '/tasks', + active: false, + isExternal: false, + }, + capabilities.mayAccess('reports') && { + icon: ReportIcon, + label: _('Reports'), + to: '/reports', + active: false, + isExternal: false, + }, + capabilities.mayAccess('results') && { + icon: ResultIcon, + label: _('Results'), + to: '/results', + active: false, + isExternal: false, + }, + capabilities.mayAccess('vulns') && { + icon: VulnerabilityIcon, + label: _('Vulnerabilities'), + to: '/vulnerabilities', + active: false, + isExternal: false, + }, + capabilities.mayAccess('notes') && { + icon: NoteIcon, + label: _('Notes'), + to: '/notes', + active: false, + isExternal: false, + }, + capabilities.mayAccess('overrides') && { + icon: OverrideIcon, + label: _('Overrides'), + to: '/overrides', + active: false, + isExternal: false, + }, + ].filter(Boolean), }, - })} - 0.1s ease-in; -`; - -const getFirstMenuEntry = child => { - // return menu entries without the MenuSection - if (child.type === MenuSection) { - return React.Children.toArray(child.props.children).find(chil => !!chil); - } - return child; -}; - -const Menu = ({children, title, to, ...props}) => { - let link; - children = React.Children.toArray(children).filter(hasValue); - - if (isDefined(to)) { - link = {title}; - } else if (isDefined(children) && children.length > 0) { - let [child] = children; - child = getFirstMenuEntry(child); - link = React.cloneElement(child, {title}); - } - - const menuentries = children.map(child => ( - {child} - )); - return ( - - {link} - {isDefined(children) && children.length > 0 && ( - {menuentries} - )} - - ); -}; - -Menu.propTypes = { - title: PropTypes.string.isRequired, - to: PropTypes.string, + mayOpAssets && { + icon: () => {}, + label: _('Assets'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + capabilities.mayAccess('assets') && { + icon: HostIcon, + label: _('Hosts'), + to: '/hosts', + active: false, + isExternal: false, + }, + capabilities.mayAccess('assets') && { + icon: OperatingSystemIcon, + label: _('Operating Systems'), + to: '/operatingsystems', + active: false, + isExternal: false, + }, + capabilities.mayAccess('tls_certificates') && { + icon: TlsCertificateIcon, + label: _('TLS Certificates'), + to: '/tlscertificates', + active: false, + isExternal: false, + }, + ].filter(Boolean), + }, + mayOpResilience && { + icon: () => {}, + label: _('Resilience'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + capabilities.mayAccess('tickets') && { + icon: TicketIcon, + label: _('Remediation Tickets'), + to: '/tickets', + active: false, + isExternal: false, + }, + capabilities.mayAccess('policies') && { + icon: PolicyIcon, + label: _('Compliance Policies'), + to: '/policies', + active: false, + isExternal: false, + }, + capabilities.mayAccess('audits') && { + icon: AuditIcon, + label: _('Compliance Audits'), + to: '/audits', + active: false, + isExternal: false, + }, + ].filter(Boolean), + }, + capabilities.mayAccess('info') && { + icon: () => {}, + label: _('SecInfo'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + { + icon: NvtIcon, + label: _('NVTs'), + to: '/nvts', + active: false, + isExternal: false, + }, + { + icon: CveIcon, + label: _('CVEs'), + to: '/cves', + active: false, + isExternal: false, + }, + { + icon: CpeIcon, + label: _('CPEs'), + to: '/cpes', + active: false, + isExternal: false, + }, + { + icon: CertBundAdvIcon, + label: _('CERT-Bund Advisories'), + to: '/certbunds', + active: false, + isExternal: false, + }, + { + icon: DfnCertAdvIcon, + label: _('DFN-CERT Advisories'), + to: '/dfncerts', + active: false, + isExternal: false, + }, + ], + }, + mayOpConfiguration && { + icon: () => {}, + label: _('Configuration'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + capabilities.mayAccess('targets') && { + icon: TargetIcon, + label: _('Targets'), + to: '/targets', + active: false, + isExternal: false, + }, + capabilities.mayAccess('port_lists') && { + icon: PortListIcon, + label: _('Port Lists'), + to: '/portlists', + active: false, + isExternal: false, + }, + capabilities.mayAccess('credentials') && { + icon: CredentialIcon, + label: _('Credentials'), + to: '/credentials', + active: false, + isExternal: false, + }, + capabilities.mayAccess('configs') && { + icon: ScanConfigIcon, + label: _('Scan Configs'), + to: '/scanconfigs', + active: false, + isExternal: false, + }, + capabilities.mayAccess('alerts') && { + icon: AlertIcon, + label: _('Alerts'), + to: '/alerts', + active: false, + isExternal: false, + }, + capabilities.mayAccess('schedules') && { + icon: ScheduleIcon, + label: _('Schedules'), + to: '/schedules', + active: false, + isExternal: false, + }, + capabilities.mayAccess('report_configs') && { + icon: ReportFormatIcon, + label: _('Report Configs'), + to: '/reportconfigs', + active: false, + isExternal: false, + }, + capabilities.mayAccess('report_formats') && { + icon: ReportFormatIcon, + label: _('Report Formats'), + to: '/reportformats', + active: false, + isExternal: false, + }, + capabilities.mayAccess('scanners') && { + icon: ScannerIcon, + label: _('Scanners'), + to: '/scanners', + active: false, + isExternal: false, + }, + capabilities.mayAccess('filters') && { + icon: FilterIcon, + label: _('Filters'), + to: '/filters', + active: false, + isExternal: false, + }, + capabilities.mayAccess('tags') && { + icon: TagsSvgIcon, + label: _('Tags'), + to: '/tags', + active: false, + isExternal: false, + }, + ].filter(Boolean), + }, + { + icon: () => {}, + label: _('Administration'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + capabilities.mayAccess('users') && { + icon: UserIcon, + label: _('Users'), + to: '/users', + active: false, + isExternal: false, + }, + capabilities.mayAccess('groups') && { + icon: GroupIcon, + label: _('Groups'), + to: '/groups', + active: false, + isExternal: false, + }, + capabilities.mayAccess('roles') && { + icon: RoleIcon, + label: _('Roles'), + to: '/roles', + active: false, + isExternal: false, + }, + capabilities.mayAccess('permissions') && { + icon: PermissionIcon, + label: _('Permissions'), + to: '/permissions', + active: false, + isExternal: false, + }, + capabilities.mayAccess('system_reports') && { + icon: PerformanceIcon, + label: _('Performance'), + to: '/performance', + active: false, + isExternal: false, + }, + { + icon: TrashcanIcon, + label: _('Trashcan'), + to: '/trashcan', + active: false, + isExternal: false, + }, + capabilities.mayAccess('feeds') && { + icon: FeedIcon, + label: _('Feed Status'), + to: '/feedstatus', + active: false, + isExternal: false, + }, + capabilities.mayOp('describe_auth') && + capabilities.mayOp('modify_auth') && { + icon: LdapIcon, + label: _('LDAP'), + to: '/ldap', + active: false, + isExternal: false, + }, + capabilities.mayOp('describe_auth') && + capabilities.mayOp('modify_auth') && { + icon: RadiusIcon, + label: _('RADIUS'), + to: '/radius', + active: false, + isExternal: false, + }, + ].filter(Boolean), + }, + { + icon: () => {}, + label: _('Help'), + active: false, + defaultOpened: false, + isExternal: false, + subNav: [ + { + label: _('User Manual'), + to: 'https://docs.greenbone.net/GSM-Manual/gos-22.04/en/', + active: false, + isExternal: true, + }, + { + icon: CvssIcon, + label: _('CVSS Calculator'), + to: '/cvsscalculator', + active: false, + isExternal: false, + }, + { + label: _('About'), + to: '/about', + active: false, + isExternal: false, + }, + ], + }, + ].filter(Boolean), + ]; + return ; }; export default Menu; - -// vim: set ts=2 sw=2 tw=80: diff --git a/src/web/pages/page.jsx b/src/web/pages/page.jsx index 1d3d371019..0f3fd01f60 100644 --- a/src/web/pages/page.jsx +++ b/src/web/pages/page.jsx @@ -18,119 +18,60 @@ import React from 'react'; -import {withRouter} from 'react-router-dom'; +import {useLocation} from 'react-router-dom'; import styled from 'styled-components'; -import _ from 'gmp/locale'; - -import logger from 'gmp/log'; - import {isDefined} from 'gmp/utils/identity'; -import Capabilities from 'gmp/capabilities/capabilities'; - -import MenuBar from 'web/components/bar/menubar'; - import ErrorBoundary from 'web/components/error/errorboundary'; import Layout from 'web/components/layout/layout'; -import LicenseNotification from 'web/components/notification/licensenotification'; - import CapabilitiesContext from 'web/components/provider/capabilitiesprovider'; -import LicenseProvider from 'web/components/provider/licenseprovider'; + +import useLoadCapabilities from 'web/hooks/useLoadCapabilities'; import Footer from 'web/components/structure/footer'; import Header from 'web/components/structure/header'; import Main from 'web/components/structure/main'; -import PropTypes from 'web/utils/proptypes'; -import withGmp from 'web/utils/withGmp'; -import compose from 'web/utils/compose'; +import Menu from 'web/components/menu/menu'; -const log = logger.getLogger('web.page'); +import useTranslation from 'web/hooks/useTranslation'; const StyledLayout = styled(Layout)` - height: 100%; + height: calc(-48px + 100vh); `; -class Page extends React.Component { - constructor(...args) { - super(...args); - - this.handleCloseLicenseNotification = - this.handleCloseLicenseNotification.bind(this); +const Page = ({children}) => { + const capabilities = useLoadCapabilities(); + const location = useLocation(); + const [_] = useTranslation(); - this.state = {}; + if (!isDefined(capabilities)) { + // only show content after caps have been loaded + // this avoids ugly re-rendering of parts of the ui (e.g. the menu) + return null; } - componentDidMount() { - const {gmp} = this.props; - - gmp.user - .currentCapabilities() - .then(response => { - const capabilities = response.data; - log.debug('User capabilities', capabilities); - this.setState({capabilities}); - }) - .catch(rejection => { - log.error('An error occurred during fetching capabilities', rejection); - // use empty capabilities - this.setState({capabilities: new Capabilities()}); - }); - - this.setState({ - notificationClosed: false, - }); - } - - handleCloseLicenseNotification() { - this.setState({notificationClosed: true}); - } - - render() { - const {children, location} = this.props; - const {capabilities, notificationClosed} = this.state; - if (!isDefined(capabilities)) { - // only show content after caps have been loaded - // this avoids ugly re-rendering of parts of the ui (e.g. the menu) - return null; - } - - return ( - - - - -
- {!notificationClosed && ( - - )} -
- - {children} - -
-