diff --git a/full-backend-tests/src/test/java/org/graylog/plugins/views/QueryValidationResourceIT.java b/full-backend-tests/src/test/java/org/graylog/plugins/views/QueryValidationResourceIT.java index 866e21311ed4..b8b5fccb4c09 100644 --- a/full-backend-tests/src/test/java/org/graylog/plugins/views/QueryValidationResourceIT.java +++ b/full-backend-tests/src/test/java/org/graylog/plugins/views/QueryValidationResourceIT.java @@ -146,6 +146,12 @@ void testInvalidValueType() { validatableResponse.assertThat().body("explanations.error_type[0]", equalTo("INVALID_VALUE_TYPE")); } + @ContainerMatrixTest + void testSuccessfullyValidatesExistsTerms() { + verifyQueryIsValidatedSuccessfully("_exists_:timestamp"); + verifyQueryIsValidatedSuccessfully("_exists_:level"); + } + @ContainerMatrixTest void testQuotedDefaultField() { // if the validation correctly recognizes the quoted text, it should not warn about lowercase or diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/ParsedQuery.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/ParsedQuery.java index a32fce0f3a28..8892111eaa67 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/ParsedQuery.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/ParsedQuery.java @@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableList; import javax.validation.constraints.NotNull; -import java.util.List; import java.util.Set; import java.util.stream.Collectors; diff --git a/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/validators/FieldValueTypeValidator.java b/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/validators/FieldValueTypeValidator.java index f8435d4a577a..dc0922f95ebc 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/validators/FieldValueTypeValidator.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/views/search/validation/validators/FieldValueTypeValidator.java @@ -93,6 +93,7 @@ private List validateQueryValues(PositionTrackingQuery decora final Map fields = availableFields.stream().collect(Collectors.toMap(MappedFieldTypeDTO::name, Function.identity())); return parsedQuery.terms().stream() + .filter(term -> !term.isExistsField()) .map(term -> { final MappedFieldTypeDTO fieldType = fields.get(term.getRealFieldName()); final Optional typeName = Optional.ofNullable(fieldType) diff --git a/graylog2-web-interface/src/components/bootstrap/BootstrapModalConfirm.jsx b/graylog2-web-interface/src/components/bootstrap/BootstrapModalConfirm.jsx index 95b986633597..979715cf7131 100644 --- a/graylog2-web-interface/src/components/bootstrap/BootstrapModalConfirm.jsx +++ b/graylog2-web-interface/src/components/bootstrap/BootstrapModalConfirm.jsx @@ -17,9 +17,10 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ModalSubmit from 'components/common/ModalSubmit'; + import Modal from './Modal'; import BootstrapModalWrapper from './BootstrapModalWrapper'; -import Button from './Button'; /** * Component that displays a confirmation dialog box that the user can @@ -34,8 +35,6 @@ class BootstrapModalConfirm extends React.Component { PropTypes.string, PropTypes.element, ]).isRequired, - /** Text to use in the cancel button. */ - cancelButtonText: PropTypes.string, /** Text to use in the confirmation button. */ confirmButtonText: PropTypes.string, /** Indicates whether the cancel button should be disabled or not. */ @@ -65,7 +64,6 @@ class BootstrapModalConfirm extends React.Component { static defaultProps = { showModal: false, - cancelButtonText: 'Cancel', confirmButtonText: 'Confirm', cancelButtonDisabled: false, confirmButtonDisabled: false, @@ -88,6 +86,7 @@ class BootstrapModalConfirm extends React.Component { onConfirm(this.close); }; + // eslint-disable-next-line react/no-unused-class-component-methods open = () => { this.modal.open(); }; @@ -105,7 +104,6 @@ class BootstrapModalConfirm extends React.Component { children, cancelButtonDisabled, confirmButtonDisabled, - cancelButtonText, confirmButtonText, } = this.props; @@ -125,8 +123,12 @@ class BootstrapModalConfirm extends React.Component { - - + ); diff --git a/graylog2-web-interface/src/components/bootstrap/BootstrapModalForm.jsx b/graylog2-web-interface/src/components/bootstrap/BootstrapModalForm.jsx index 8e42c351ae51..956b430035d5 100644 --- a/graylog2-web-interface/src/components/bootstrap/BootstrapModalForm.jsx +++ b/graylog2-web-interface/src/components/bootstrap/BootstrapModalForm.jsx @@ -18,9 +18,10 @@ import PropTypes from 'prop-types'; import React from 'react'; import $ from 'jquery'; +import ModalSubmit from 'components/common/ModalSubmit'; + import Modal from './Modal'; import BootstrapModalWrapper from './BootstrapModalWrapper'; -import Button from './Button'; /** * Encapsulates a form element inside a bootstrap modal, hiding some custom logic that this kind of component @@ -43,8 +44,6 @@ class BootstrapModalForm extends React.Component { onCancel: PropTypes.func, /* Object with additional props to pass to the form */ formProps: PropTypes.object, - /* Text to use in the cancel button. "Cancel" is the default */ - cancelButtonText: PropTypes.string, /* Text to use in the submit button. "Submit" is the default */ submitButtonText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), submitButtonDisabled: PropTypes.bool, @@ -54,7 +53,6 @@ class BootstrapModalForm extends React.Component { static defaultProps = { backdrop: undefined, formProps: {}, - cancelButtonText: 'Cancel', submitButtonText: 'Submit', submitButtonDisabled: false, onModalOpen: () => {}, @@ -104,7 +102,6 @@ class BootstrapModalForm extends React.Component { formProps, bsSize, onModalClose, - cancelButtonText, show, submitButtonText, onModalOpen, @@ -136,8 +133,9 @@ class BootstrapModalForm extends React.Component { {body} - - + diff --git a/graylog2-web-interface/src/components/bootstrap/NavDropdown.jsx b/graylog2-web-interface/src/components/bootstrap/NavDropdown.jsx index 68902b250ee8..05b2f53f455a 100644 --- a/graylog2-web-interface/src/components/bootstrap/NavDropdown.jsx +++ b/graylog2-web-interface/src/components/bootstrap/NavDropdown.jsx @@ -15,9 +15,11 @@ * . */ +import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { NavDropdown as BootstrapNavDropdown } from 'react-bootstrap'; import styled from 'styled-components'; +import PropTypes from 'prop-types'; import menuItemStyles from './styles/menuItem'; @@ -41,10 +43,25 @@ class ModifiedBootstrapNavDropdown extends BootstrapNavDropdown { } } -const NavDropdown = styled(BootstrapNavDropdown)` +const StyledNavDropdown = styled(BootstrapNavDropdown)` ${menuItemStyles} `; +const NavDropdown = ({ inactiveTitle, title, ...props }) => { + const isActive = inactiveTitle ? inactiveTitle !== title : undefined; + + return ; +}; + +NavDropdown.propTypes = { + inactiveTitle: PropTypes.string, + title: PropTypes.node.isRequired, +}; + +NavDropdown.defaultProps = { + inactiveTitle: undefined, +}; + const ModifiedNavDropdown = styled(ModifiedBootstrapNavDropdown)` ${menuItemStyles} `; diff --git a/graylog2-web-interface/src/components/bootstrap/NavItem.tsx b/graylog2-web-interface/src/components/bootstrap/NavItem.tsx new file mode 100644 index 000000000000..86849f332d2e --- /dev/null +++ b/graylog2-web-interface/src/components/bootstrap/NavItem.tsx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +// eslint-disable-next-line no-restricted-imports +import { NavItem as BootstrapNavItem } from 'react-bootstrap'; + +const NavItem = (props: React.ComponentProps) => ; + +NavItem.displayName = 'NavItem'; + +/** @component */ +export default NavItem; diff --git a/graylog2-web-interface/src/components/bootstrap/Navbar.jsx b/graylog2-web-interface/src/components/bootstrap/Navbar.jsx index 9a94b41c2cc2..03666d997962 100644 --- a/graylog2-web-interface/src/components/bootstrap/Navbar.jsx +++ b/graylog2-web-interface/src/components/bootstrap/Navbar.jsx @@ -56,11 +56,14 @@ const Navbar = styled(BootstrapNavbar)(({ theme }) => css` color: ${theme.colors.variant.darkest.default}; background-color: ${theme.colors.gray[90]}; - &:hover, - &:focus { + &:hover { color: ${theme.colors.variant.darkest.default}; background-color: ${theme.colors.gray[80]}; } + + &:focus { + background-color: ${theme.colors.gray[90]}; + } } > .disabled > a, diff --git a/graylog2-web-interface/src/components/bootstrap/imports.js b/graylog2-web-interface/src/components/bootstrap/imports.js index a0ca1ef373c7..7f3729b5aab5 100644 --- a/graylog2-web-interface/src/components/bootstrap/imports.js +++ b/graylog2-web-interface/src/components/bootstrap/imports.js @@ -27,7 +27,6 @@ export { Dropdown, Form, Grid, - NavItem, Pager, PanelGroup, Radio, // NOTE: do we want custom or keep OS styles diff --git a/graylog2-web-interface/src/components/bootstrap/index.js b/graylog2-web-interface/src/components/bootstrap/index.js index 1c14161c7c5b..bb956a97e772 100644 --- a/graylog2-web-interface/src/components/bootstrap/index.js +++ b/graylog2-web-interface/src/components/bootstrap/index.js @@ -36,6 +36,7 @@ export { default as MenuItem } from './MenuItem'; export { default as Modal } from './Modal'; export { default as Nav } from './Nav'; export { default as NavDropdown } from './NavDropdown'; +export { default as NavItem } from './NavItem'; export { default as Navbar } from './Navbar'; export { default as Panel } from './Panel'; export { default as Popover } from './Popover'; diff --git a/graylog2-web-interface/src/components/common/FormSubmit.tsx b/graylog2-web-interface/src/components/common/FormSubmit.tsx new file mode 100644 index 000000000000..4db532222806 --- /dev/null +++ b/graylog2-web-interface/src/components/common/FormSubmit.tsx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; + +import { Button, ButtonToolbar } from 'components/bootstrap'; +import type { IconName } from 'components/common/Icon'; +import Icon from 'components/common/Icon'; +import Spinner from 'components/common/Spinner'; + +type Props = { + className?: string, + disableCancel?: boolean, + disabledSubmit?: boolean, + isSubmitting?: boolean, + leftCol?: React.ReactNode, + onCancel: () => void, + onSubmit?: () => void, + submitButtonText: string, + submitIcon?: IconName, + submitButtonType?: 'submit' | 'button', + submitLoadingText?: string, +} + +const FormSubmit = ({ + className, + disableCancel, + disabledSubmit, + isSubmitting, + leftCol, + onCancel, + onSubmit, + submitButtonText, + submitButtonType, + submitIcon, + submitLoadingText, +}: Props) => ( + + {leftCol} + + + +); + +FormSubmit.defaultProps = { + className: undefined, + disableCancel: false, + disabledSubmit: false, + isSubmitting: false, + leftCol: undefined, + onSubmit: undefined, + submitButtonType: 'submit', + submitIcon: undefined, + submitLoadingText: undefined, +}; + +export default FormSubmit; diff --git a/graylog2-web-interface/src/components/common/ModalSubmit.tsx b/graylog2-web-interface/src/components/common/ModalSubmit.tsx new file mode 100644 index 000000000000..4f3cd2078d4c --- /dev/null +++ b/graylog2-web-interface/src/components/common/ModalSubmit.tsx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; + +import FormSubmit from 'components/common/FormSubmit'; + +type Props = React.ComponentProps + +/* eslint-disable react/prop-types */ +const ModalSubmit = ({ + className, + disabledSubmit, + disableCancel, + isSubmitting, + leftCol, + onCancel, + onSubmit, + submitLoadingText, + submitIcon, + submitButtonText, +}: Props) => ( + +); + +export default ModalSubmit; diff --git a/graylog2-web-interface/src/components/common/index.tsx b/graylog2-web-interface/src/components/common/index.tsx index 9b2e8c4ce73c..3c1ad0575560 100644 --- a/graylog2-web-interface/src/components/common/index.tsx +++ b/graylog2-web-interface/src/components/common/index.tsx @@ -52,6 +52,7 @@ export { default as ExternalLinkButton } from './ExternalLinkButton'; export { default as FlatContentRow } from './FlatContentRow'; export { default as FormikFormGroup } from './FormikFormGroup'; export { default as FormikInput } from './FormikInput'; +export { default as FormSubmit } from './FormSubmit'; export { default as HasOwnership } from './HasOwnership'; export { default as HoverForHelp } from './HoverForHelp'; export { default as ISODurationInput } from './ISODurationInput'; @@ -70,6 +71,7 @@ export { default as LoadingIndicator } from './LoadingIndicator'; export { default as LocaleSelect } from './LocaleSelect'; export { default as Markdown } from './Markdown'; export { default as MessageDetailsDefinitionList } from './MessageDetailsDefinitionList'; +export { default as ModalSubmit } from './ModalSubmit'; export { default as MultiSelect } from './MultiSelect'; export { default as OverlayElement } from './OverlayElement'; export { default as OverlayTrigger } from './OverlayTrigger'; diff --git a/graylog2-web-interface/src/components/common/router.tsx b/graylog2-web-interface/src/components/common/router.tsx index 7010738bd776..8a24b7e590b0 100644 --- a/graylog2-web-interface/src/components/common/router.tsx +++ b/graylog2-web-interface/src/components/common/router.tsx @@ -23,18 +23,17 @@ import history from 'util/History'; export type HistoryElement = Location; -const _targetPathname = (to) => { - const target = typeof to?.pathname === 'string' ? to.pathname : to; +// list of children which are being used for navigation and should receive the `active` class. +const NAV_CHILDREN = ['Button', 'NavItem']; - return String(target).split(/[?#]/)[0]; -}; +const _targetPathname = (to: string) => String(to).split(/[?#]/)[0]; -const _setActiveClassName = (pathname, to, currentClassName, displayName) => { +const _setActiveClassName = (pathname: string, to: string, currentClassName: string, displayName: string, relativeActive: boolean) => { const targetPathname = _targetPathname(to); - const isActive = targetPathname === pathname; - const isButton = displayName === 'Button'; + const isActive = relativeActive ? pathname.startsWith(targetPathname) : targetPathname === pathname; + const isNavComponent = NAV_CHILDREN.includes(displayName); - return isButton && isActive + return isNavComponent && isActive ? `active ${currentClassName ?? ''}` : currentClassName; }; @@ -48,18 +47,24 @@ type Props = { children: React.ReactElement, onClick?: () => unknown, disabled?: boolean, - to: string, + to: string | { pathname: string }, + // if set the child component will receive the active class + // when the part of the URL path matches the `to` prop. + relativeActive?: boolean, }; -const isLeftClickEvent = (e) => (e.button === 0); +const isLeftClickEvent = (e: React.MouseEvent) => (e.button === 0); -const isModifiedEvent = (e) => !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); +const isModifiedEvent = (e: React.MouseEvent) => !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey); -const LinkContainer = ({ children, onClick, to, ...rest }: Props) => { +const LinkContainer = ({ children, onClick, to: toProp, relativeActive, ...rest }: Props) => { const { pathname } = useLocation(); const { props: { onClick: childrenOnClick, className }, type: { displayName } } = React.Children.only(children); - const childrenClassName = useMemo(() => _setActiveClassName(pathname, to, className, displayName), [pathname, to, className, displayName]); - const _onClick = useCallback((e) => { + const to = (typeof toProp === 'object' && 'pathname' in toProp) ? toProp.pathname : toProp; + const childrenClassName = useMemo(() => _setActiveClassName(pathname, to, className, displayName, relativeActive), + [pathname, to, className, displayName, relativeActive], + ); + const _onClick = useCallback((e: React.MouseEvent) => { if (!isLeftClickEvent(e) || isModifiedEvent(e)) { return; } @@ -81,6 +86,10 @@ const LinkContainer = ({ children, onClick, to, ...rest }: Props) => { return React.cloneElement(React.Children.only(children), { ...rest, className: childrenClassName, onClick: _onClick, href: to }); }; +LinkContainer.defaultProps = { + relativeActive: false, +}; + export { Link, LinkContainer, diff --git a/graylog2-web-interface/src/components/configurations/decorators/DecoratorsConfigUpdate.tsx b/graylog2-web-interface/src/components/configurations/decorators/DecoratorsConfigUpdate.tsx index 2e21de12856d..d1311350b66a 100644 --- a/graylog2-web-interface/src/components/configurations/decorators/DecoratorsConfigUpdate.tsx +++ b/graylog2-web-interface/src/components/configurations/decorators/DecoratorsConfigUpdate.tsx @@ -18,8 +18,8 @@ import React, { useCallback, useState } from 'react'; import { cloneDeep } from 'lodash'; import BootstrapModalWrapper from 'components/bootstrap/BootstrapModalWrapper'; -import { Button, Modal } from 'components/bootstrap'; -import { IfPermitted } from 'components/common'; +import { Modal } from 'components/bootstrap'; +import { IfPermitted, ModalSubmit } from 'components/common'; import type { Stream } from 'stores/streams/StreamsStore'; import DecoratorList from 'views/components/messagelist/decorators/DecoratorList'; import AddDecoratorButton from 'views/components/messagelist/decorators/AddDecoratorButton'; @@ -98,8 +98,7 @@ const DecoratorsConfigUpdate = ({ streams, decorators, types, show = false, onCa - - + ); diff --git a/graylog2-web-interface/src/components/content-packs/ContentPackParameterList.jsx b/graylog2-web-interface/src/components/content-packs/ContentPackParameterList.jsx index f6bfd0d42995..e463055f0f71 100644 --- a/graylog2-web-interface/src/components/content-packs/ContentPackParameterList.jsx +++ b/graylog2-web-interface/src/components/content-packs/ContentPackParameterList.jsx @@ -19,7 +19,7 @@ import React from 'react'; import { findIndex } from 'lodash'; import { Badge, Button, Modal, ButtonToolbar } from 'components/bootstrap'; -import { DataTable, SearchForm, Icon } from 'components/common'; +import { DataTable, SearchForm, Icon, ModalSubmit } from 'components/common'; import BootstrapModalWrapper from 'components/bootstrap/BootstrapModalWrapper'; import ContentPackEditParameter from 'components/content-packs/ContentPackEditParameter'; import ObjectUtils from 'util/ObjectUtils'; @@ -145,7 +145,8 @@ class ContentPackParameterList extends React.Component { }; const size = parameter ? 'xsmall' : 'small'; - const name = parameter ? 'Edit' : 'Create parameter'; + const titleName = parameter ? 'Edit parameter' : 'Create parameter'; + const triggerButtonName = parameter ? 'Edit' : 'Create parameter'; const modal = ( { modalRef = node; }} bsSize="large"> @@ -162,12 +163,9 @@ class ContentPackParameterList extends React.Component { parameterToEdit={parameter} /> -
- - - - -
+
); @@ -178,7 +176,7 @@ class ContentPackParameterList extends React.Component { bsSize={size} title="Edit Modal" onClick={openModal}> - {name} + {triggerButtonName} {modal} diff --git a/graylog2-web-interface/src/components/content-packs/ContentPacksList.jsx b/graylog2-web-interface/src/components/content-packs/ContentPacksList.jsx index 120ea640d662..6ecc863f227f 100644 --- a/graylog2-web-interface/src/components/content-packs/ContentPacksList.jsx +++ b/graylog2-web-interface/src/components/content-packs/ContentPacksList.jsx @@ -21,7 +21,6 @@ import { LinkContainer, Link } from 'components/common/router'; import Routes from 'routing/Routes'; import { Button, - ButtonToolbar, Col, DropdownButton, MenuItem, @@ -30,6 +29,7 @@ import { } from 'components/bootstrap'; import { Pagination, PageSizeSelect, + ModalSubmit, } from 'components/common'; import TypeAheadDataFilter from 'components/common/TypeAheadDataFilter'; import BootstrapModalWrapper from 'components/bootstrap/BootstrapModalWrapper'; @@ -94,7 +94,7 @@ class ContentPacksList extends React.Component { const modal = ( { modalRef = node; }} bsSize="large"> - Install + Install Content Pack { installRef = node; }} @@ -102,12 +102,7 @@ class ContentPacksList extends React.Component { onInstall={onInstallProp} /> -
- - - - -
+
); diff --git a/graylog2-web-interface/src/components/extractors/ExtractorSortModal.jsx b/graylog2-web-interface/src/components/extractors/ExtractorSortModal.jsx index 9299286703d4..40e4aa192d62 100644 --- a/graylog2-web-interface/src/components/extractors/ExtractorSortModal.jsx +++ b/graylog2-web-interface/src/components/extractors/ExtractorSortModal.jsx @@ -17,9 +17,10 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { Row, Col, Modal, Button, BootstrapModalWrapper } from 'components/bootstrap'; +import { Row, Col, Modal, BootstrapModalWrapper } from 'components/bootstrap'; import SortableList from 'components/common/SortableList'; import { ExtractorsActions } from 'stores/extractors/ExtractorsStore'; +import { ModalSubmit } from 'components/common/index'; class ExtractorSortModal extends React.Component { static propTypes = { @@ -35,6 +36,7 @@ class ExtractorSortModal extends React.Component { }; } + // eslint-disable-next-line react/no-unused-class-component-methods open = () => { this.modal.open(); }; @@ -92,8 +94,7 @@ class ExtractorSortModal extends React.Component { - - + ); diff --git a/graylog2-web-interface/src/components/navigation/Navigation.test.tsx b/graylog2-web-interface/src/components/navigation/Navigation.test.tsx index 295a03ed4346..667ae698bc81 100644 --- a/graylog2-web-interface/src/components/navigation/Navigation.test.tsx +++ b/graylog2-web-interface/src/components/navigation/Navigation.test.tsx @@ -240,7 +240,7 @@ describe('Navigation', () => { describe('uses correct permissions:', () => { const verifyPermissions = ({ count, links }) => { const wrapper = mount(); - const navigationLinks = wrapper.find('NavItem'); + const navigationLinks = wrapper.find('NavItem[active=false]'); expect(navigationLinks).toHaveLength(count); diff --git a/graylog2-web-interface/src/components/navigation/Navigation.tsx b/graylog2-web-interface/src/components/navigation/Navigation.tsx index 5de8eed79e50..81427eda3620 100644 --- a/graylog2-web-interface/src/components/navigation/Navigation.tsx +++ b/graylog2-web-interface/src/components/navigation/Navigation.tsx @@ -85,7 +85,7 @@ const formatPluginRoute = (pluginRoute: PluginNavigation, currentUserPermissions } return ( - + {pluginRoute.children.map((child) => formatSinglePluginRoute(child, currentUserPermissions, false))} ); @@ -134,7 +134,7 @@ const Navigation = React.memo(({ pathname }: Props) => { - + @@ -145,19 +145,19 @@ const Navigation = React.memo(({ pathname }: Props) => {