[ENDPOINT] Reintroduced tabs to endpoint management and migrated pages to use common security components (#74886) (#75352)

* Reintroduced tabs to endpoint management and migrated pages to use common security components.

* Empty trusted apps tab.

* Changed casing in the translations.

* Switched to using route path generation functions.

* Added propagation of data-test-subj attribute to Wrapper component.

* Fixed CommonProps import.

* Moved out shared component for administration list page.

* Removed unused file.

* Removed unused translation keys.

* Removed redundant snapshot.

* Added some minimal tests.

* Attempt to fix functional tests.

* Attempt to fix functional tests again.

* Reverted function declarations back to const.

* Wrapped component in memo.
This commit is contained in:
Bohdan Tsymbala 2020-08-18 22:06:16 +02:00 committed by GitHub
parent 27f124a8c0
commit c1b50e6a05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 325 additions and 1274 deletions

View file

@ -1,802 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PageView component should display body header custom element 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
bodyHeader={
<p>
body header
</p>
}
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentHeader>
<div
className="euiPageContentHeader euiPageContentHeader--responsive"
>
<EuiPageContentHeaderSection
data-test-subj="pageViewBodyTitleArea"
>
<div
className="euiPageContentHeaderSection"
data-test-subj="pageViewBodyTitleArea"
>
<p>
body header
</p>
</div>
</EuiPageContentHeaderSection>
</div>
</EuiPageContentHeader>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should display body header wrapped in EuiTitle 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
bodyHeader="body header"
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentHeader>
<div
className="euiPageContentHeader euiPageContentHeader--responsive"
>
<EuiPageContentHeaderSection
data-test-subj="pageViewBodyTitleArea"
>
<div
className="euiPageContentHeaderSection"
data-test-subj="pageViewBodyTitleArea"
>
<PageViewBodyHeaderTitle>
<EuiTitle>
<h2
className="euiTitle euiTitle--medium"
data-test-subj="pageViewBodyTitle"
>
body header
</h2>
</EuiTitle>
</PageViewBodyHeaderTitle>
</div>
</EuiPageContentHeaderSection>
</div>
</EuiPageContentHeader>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should display header left and right 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
headerLeft="page title"
headerRight="right side actions"
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageHeader
className="endpoint-header"
>
<div
className="euiPageHeader euiPageHeader--responsive endpoint-header"
>
<EuiPageHeaderSection
className="endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<div
className="euiPageHeaderSection endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<PageViewHeaderTitle>
<EuiTitle
size="l"
>
<h1
className="euiTitle euiTitle--large"
data-test-subj="pageViewHeaderLeftTitle"
>
page title
</h1>
</EuiTitle>
</PageViewHeaderTitle>
</div>
</EuiPageHeaderSection>
<EuiPageHeaderSection
data-test-subj="pageViewHeaderRight"
>
<div
className="euiPageHeaderSection"
data-test-subj="pageViewHeaderRight"
>
right side actions
</div>
</EuiPageHeaderSection>
</div>
</EuiPageHeader>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should display only body if not header props used 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should display only header left 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
headerLeft="page title"
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageHeader
className="endpoint-header"
>
<div
className="euiPageHeader euiPageHeader--responsive endpoint-header"
>
<EuiPageHeaderSection
className="endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<div
className="euiPageHeaderSection endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<PageViewHeaderTitle>
<EuiTitle
size="l"
>
<h1
className="euiTitle euiTitle--large"
data-test-subj="pageViewHeaderLeftTitle"
>
page title
</h1>
</EuiTitle>
</PageViewHeaderTitle>
</div>
</EuiPageHeaderSection>
</div>
</EuiPageHeader>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should display only header right but include an empty left side 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
headerRight="right side actions"
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageHeader
className="endpoint-header"
>
<div
className="euiPageHeader euiPageHeader--responsive endpoint-header"
>
<EuiPageHeaderSection
className="endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<div
className="euiPageHeaderSection endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
/>
</EuiPageHeaderSection>
<EuiPageHeaderSection
data-test-subj="pageViewHeaderRight"
>
<div
className="euiPageHeaderSection"
data-test-subj="pageViewHeaderRight"
>
right side actions
</div>
</EuiPageHeaderSection>
</div>
</EuiPageHeader>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should pass through EuiPage props 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
aria-label="test-aria-label-here"
className="test-class-name-here"
data-test-subj="test-data-test-subj-here"
restrictWidth="1000"
viewType="list"
>
<Styled(EuiPage)
aria-label="test-aria-label-here"
className="test-class-name-here"
data-test-subj="test-data-test-subj-here"
restrictWidth="1000"
>
<EuiPage
aria-label="test-aria-label-here"
className="c0 test-class-name-here"
data-test-subj="test-data-test-subj-here"
restrictWidth="1000"
>
<div
aria-label="test-aria-label-here"
className="euiPage euiPage--restrictWidth-custom c0 test-class-name-here"
data-test-subj="test-data-test-subj-here"
style={
Object {
"maxWidth": "1000",
}
}
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;
exports[`PageView component should use custom element for header left and not wrap in EuiTitle 1`] = `
.c0.endpoint--isListView {
padding: 0 24px;
}
.c0.endpoint--isListView .endpoint-header {
padding: 24px;
margin-bottom: 0;
}
.c0.endpoint--isListView .endpoint-page-content {
border-left: none;
border-right: none;
}
.c0.endpoint--isDetailsView .endpoint-page-content {
padding: 0;
border: none;
background: none;
}
.c0 .endpoint-navTabs {
margin-left: 12px;
}
.c0 .endpoint-header-leftSection {
overflow: hidden;
}
<PageView
headerLeft={
<p>
title here
</p>
}
viewType="list"
>
<Styled(EuiPage)
className="endpoint--isListView"
>
<EuiPage
className="c0 endpoint--isListView"
>
<div
className="euiPage c0 endpoint--isListView"
>
<EuiPageBody>
<main
className="euiPageBody"
>
<EuiPageHeader
className="endpoint-header"
>
<div
className="euiPageHeader euiPageHeader--responsive endpoint-header"
>
<EuiPageHeaderSection
className="endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<div
className="euiPageHeaderSection endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
<p>
title here
</p>
</div>
</EuiPageHeaderSection>
</div>
</EuiPageHeader>
<EuiPageContent
className="endpoint-page-content"
>
<EuiPanel
className="euiPageContent endpoint-page-content"
paddingSize="l"
>
<div
className="euiPanel euiPanel--paddingLarge euiPageContent endpoint-page-content"
>
<EuiPageContentBody
data-test-subj="pageViewBodyContent"
>
<div
className="euiPageContentBody"
data-test-subj="pageViewBodyContent"
>
body content
</div>
</EuiPageContentBody>
</div>
</EuiPanel>
</EuiPageContent>
</main>
</EuiPageBody>
</div>
</EuiPage>
</Styled(EuiPage)>
</PageView>
`;

View file

@ -1,88 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { PageView } from './page_view';
import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
describe('PageView component', () => {
const render = (ui: Parameters<typeof mount>[0]) =>
mount(ui, { wrappingComponent: EuiThemeProvider });
it('should display only body if not header props used', () => {
expect(render(<PageView viewType="list">{'body content'}</PageView>)).toMatchSnapshot();
});
it('should display header left and right', () => {
expect(
render(
<PageView viewType="list" headerLeft="page title" headerRight="right side actions">
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
it('should display only header left', () => {
expect(
render(
<PageView viewType="list" headerLeft="page title">
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
it('should display only header right but include an empty left side', () => {
expect(
render(
<PageView viewType="list" headerRight="right side actions">
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
it(`should use custom element for header left and not wrap in EuiTitle`, () => {
expect(
render(
<PageView viewType="list" headerLeft={<p>{'title here'}</p>}>
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
it('should display body header wrapped in EuiTitle', () => {
expect(
render(
<PageView viewType="list" bodyHeader="body header">
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
it('should display body header custom element', () => {
expect(
render(
<PageView viewType="list" bodyHeader={<p>{'body header'}</p>}>
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
it('should pass through EuiPage props', () => {
expect(
render(
<PageView
viewType="list"
restrictWidth="1000"
className="test-class-name-here"
aria-label="test-aria-label-here"
data-test-subj="test-data-test-subj-here"
>
{'body content'}
</PageView>
)
).toMatchSnapshot();
});
});

View file

@ -1,184 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
EuiPage,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageContentHeader,
EuiPageContentHeaderSection,
EuiPageHeader,
EuiPageHeaderSection,
EuiPageProps,
EuiTab,
EuiTabs,
EuiTitle,
EuiTitleProps,
} from '@elastic/eui';
import React, { memo, MouseEventHandler, ReactNode, useMemo } from 'react';
import styled from 'styled-components';
import { EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
const StyledEuiPage = styled(EuiPage)`
&.endpoint--isListView {
padding: 0 ${(props) => props.theme.eui.euiSizeL};
.endpoint-header {
padding: ${(props) => props.theme.eui.euiSizeL};
margin-bottom: 0;
}
.endpoint-page-content {
border-left: none;
border-right: none;
}
}
&.endpoint--isDetailsView {
.endpoint-page-content {
padding: 0;
border: none;
background: none;
}
}
.endpoint-navTabs {
margin-left: ${(props) => props.theme.eui.euiSizeM};
}
.endpoint-header-leftSection {
overflow: hidden;
}
`;
const isStringOrNumber = /(string|number)/;
/**
* The `PageView` component used to render `headerLeft` when it is set as a `string`
* Can be used when wanting to customize the `headerLeft` value but still use the standard
* title component
*/
export const PageViewHeaderTitle = memo<Omit<EuiTitleProps, 'children'> & { children: ReactNode }>(
({ children, size = 'l', ...otherProps }) => {
return (
<EuiTitle {...otherProps} size={size}>
<h1 data-test-subj="pageViewHeaderLeftTitle">{children}</h1>
</EuiTitle>
);
}
);
PageViewHeaderTitle.displayName = 'PageViewHeaderTitle';
/**
* The `PageView` component used to render `bodyHeader` when it is set as a `string`
* Can be used when wanting to customize the `bodyHeader` value but still use the standard
* title component
*/
export const PageViewBodyHeaderTitle = memo<{ children: ReactNode }>(
({ children, ...otherProps }) => {
return (
<EuiTitle {...otherProps}>
<h2 data-test-subj="pageViewBodyTitle">{children}</h2>
</EuiTitle>
);
}
);
PageViewBodyHeaderTitle.displayName = 'PageViewBodyHeaderTitle';
export type PageViewProps = EuiPageProps & {
/**
* The type of view
*/
viewType: 'list' | 'details';
/**
* content to be placed on the left side of the header. If a `string` is used, then it will
* be wrapped with `<EuiTitle><h1></h1></EuiTitle>`, else it will just be used as is.
*/
headerLeft?: ReactNode;
/** Content for the right side of the header */
headerRight?: ReactNode;
/**
* body (sub-)header section. If a `string` is used, then it will be wrapped with
* `<EuiTitle><h2></h2></EuiTitle>`
*/
bodyHeader?: ReactNode;
/**
* The list of tab navigation items
*/
tabs?: Array<
EuiTabProps & {
name: ReactNode;
id: string;
href?: string;
onClick?: MouseEventHandler<HTMLAnchorElement | HTMLButtonElement>;
}
>;
children?: ReactNode;
};
/**
* Page View layout for use in Endpoint
*/
export const PageView = memo<PageViewProps>(
({ viewType, children, headerLeft, headerRight, bodyHeader, tabs, ...otherProps }) => {
const tabComponents = useMemo(() => {
if (!tabs) {
return [];
}
return tabs.map(({ name, id, ...otherEuiTabProps }) => (
<EuiTab {...otherEuiTabProps} key={id}>
{name}
</EuiTab>
));
}, [tabs]);
return (
<StyledEuiPage
className={(viewType === 'list' && 'endpoint--isListView') || 'endpoint--isDetailsView'}
{...otherProps}
>
<EuiPageBody>
{(headerLeft || headerRight) && (
<EuiPageHeader className="endpoint-header">
<EuiPageHeaderSection
className="endpoint-header-leftSection"
data-test-subj="pageViewHeaderLeft"
>
{isStringOrNumber.test(typeof headerLeft) ? (
<PageViewHeaderTitle>{headerLeft}</PageViewHeaderTitle>
) : (
headerLeft
)}
</EuiPageHeaderSection>
{headerRight && (
<EuiPageHeaderSection data-test-subj="pageViewHeaderRight">
{headerRight}
</EuiPageHeaderSection>
)}
</EuiPageHeader>
)}
{tabComponents.length > 0 && (
<EuiTabs className="endpoint-navTabs">{tabComponents}</EuiTabs>
)}
<EuiPageContent className="endpoint-page-content">
{bodyHeader && (
<EuiPageContentHeader>
<EuiPageContentHeaderSection data-test-subj="pageViewBodyTitleArea">
{isStringOrNumber.test(typeof bodyHeader) ? (
<PageViewBodyHeaderTitle>{bodyHeader}</PageViewBodyHeaderTitle>
) : (
bodyHeader
)}
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
)}
<EuiPageContentBody data-test-subj="pageViewBodyContent">{children}</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</StyledEuiPage>
);
}
);
PageView.displayName = 'PageView';

View file

@ -7,14 +7,15 @@
import classNames from 'classnames';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { CommonProps } from '@elastic/eui';
import { useFullScreen } from '../../containers/use_full_screen';
import { gutterTimeline } from '../../lib/helpers';
import { AppGlobalStyle } from '../page/index';
const Wrapper = styled.div`
padding: ${({ theme }) =>
`${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l}`};
padding: ${(props) => `${props.theme.eui.paddingSizes.l}`};
&.siemWrapperPage--restrictWidthDefault,
&.siemWrapperPage--restrictWidthCustom {
box-sizing: content-box;
@ -29,6 +30,10 @@ const Wrapper = styled.div`
height: 100%;
}
&.siemWrapperPage--withTimeline {
padding-right: ${gutterTimeline};
}
&.siemWrapperPage--noPadding {
padding: 0;
}
@ -38,18 +43,20 @@ Wrapper.displayName = 'Wrapper';
interface WrapperPageProps {
children: React.ReactNode;
className?: string;
restrictWidth?: boolean | number | string;
style?: Record<string, string>;
noPadding?: boolean;
noTimeline?: boolean;
}
const WrapperPageComponent: React.FC<WrapperPageProps> = ({
const WrapperPageComponent: React.FC<WrapperPageProps & CommonProps> = ({
children,
className,
restrictWidth,
style,
noPadding,
noTimeline,
...otherProps
}) => {
const { globalFullScreen, setGlobalFullScreen } = useFullScreen();
useEffect(() => {
@ -59,6 +66,7 @@ const WrapperPageComponent: React.FC<WrapperPageProps> = ({
const classes = classNames(className, {
siemWrapperPage: true,
'siemWrapperPage--noPadding': noPadding,
'siemWrapperPage--withTimeline': !noTimeline,
'siemWrapperPage--fullHeight': globalFullScreen,
'siemWrapperPage--restrictWidthDefault':
restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true,
@ -73,7 +81,7 @@ const WrapperPageComponent: React.FC<WrapperPageProps> = ({
}
return (
<Wrapper className={classes} style={customStyle || style}>
<Wrapper className={classes} style={customStyle || style} {...otherProps}>
{children}
<AppGlobalStyle />
</Wrapper>

View file

@ -13,6 +13,7 @@ export const MANAGEMENT_ROUTING_ROOT_PATH = '';
export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.endpoints})`;
export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`;
export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`;
// --[ STORE ]---------------------------------------------------------------------------
/** The SIEM global store namespace where the management state will be mounted */

View file

@ -13,6 +13,7 @@ import {
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH,
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
} from './constants';
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
@ -72,13 +73,21 @@ export const getEndpointDetailsPath = (
})}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`;
};
export const getPoliciesPath = (search?: string) =>
`${generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
export const getPoliciesPath = (search?: string) => {
return `${generatePath(MANAGEMENT_ROUTING_POLICIES_PATH, {
tabName: AdministrationSubTab.policies,
})}${appendSearch(search)}`;
};
export const getPolicyDetailPath = (policyId: string, search?: string) =>
`${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
export const getPolicyDetailPath = (policyId: string, search?: string) => {
return `${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, {
tabName: AdministrationSubTab.policies,
policyId,
})}${appendSearch(search)}`;
};
export const getTrustedAppsListPath = (search?: string) => {
return `${generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
tabName: AdministrationSubTab.trustedApps,
})}${appendSearch(search)}`;
};

View file

@ -13,3 +13,11 @@ export const ENDPOINTS_TAB = i18n.translate('xpack.securitySolution.endpointsTab
export const POLICIES_TAB = i18n.translate('xpack.securitySolution.policiesTab', {
defaultMessage: 'Policies',
});
export const TRUSTED_APPS_TAB = i18n.translate('xpack.securitySolution.trustedAppsTab', {
defaultMessage: 'Trusted applications',
});
export const BETA_BADGE_LABEL = i18n.translate('xpack.securitySolution.administration.list.beta', {
defaultMessage: 'Beta',
});

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, memo } from 'react';
import { EuiPanel, EuiSpacer, CommonProps } from '@elastic/eui';
import { SecurityPageName } from '../../../common/constants';
import { WrapperPage } from '../../common/components/wrapper_page';
import { HeaderPage } from '../../common/components/header_page';
import { SiemNavigation } from '../../common/components/navigation';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { AdministrationSubTab } from '../types';
import { ENDPOINTS_TAB, TRUSTED_APPS_TAB, BETA_BADGE_LABEL } from '../common/translations';
import { getEndpointListPath, getTrustedAppsListPath } from '../common/routing';
interface AdministrationListPageProps {
beta: boolean;
title: React.ReactNode;
subtitle: React.ReactNode;
actions?: React.ReactNode;
}
export const AdministrationListPage: FC<AdministrationListPageProps & CommonProps> = memo(
({ beta, title, subtitle, actions, children, ...otherProps }) => {
const badgeOptions = !beta ? undefined : { beta: true, text: BETA_BADGE_LABEL };
return (
<WrapperPage noTimeline {...otherProps}>
<HeaderPage title={title} subtitle={subtitle} badgeOptions={badgeOptions}>
{actions}
</HeaderPage>
<SiemNavigation
navTabs={{
[AdministrationSubTab.endpoints]: {
name: ENDPOINTS_TAB,
id: AdministrationSubTab.endpoints,
href: getEndpointListPath({ name: 'endpointList' }),
urlKey: 'administration',
pageId: SecurityPageName.administration,
disabled: false,
},
[AdministrationSubTab.trustedApps]: {
name: TRUSTED_APPS_TAB,
id: AdministrationSubTab.trustedApps,
href: getTrustedAppsListPath(),
urlKey: 'administration',
pageId: SecurityPageName.administration,
disabled: false,
},
}}
/>
<EuiSpacer />
<EuiPanel>{children}</EuiPanel>
<SpyRoute pageName={SecurityPageName.administration} />
</WrapperPage>
);
}
);
AdministrationListPage.displayName = 'AdministrationListPage';

View file

@ -1,19 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { memo } from 'react';
import { EuiErrorBoundary } from '@elastic/eui';
import { PageView, PageViewProps } from '../../common/components/endpoint/page_view';
export const ManagementPageView = memo<Omit<PageViewProps, 'tabs'>>((options) => {
return (
<EuiErrorBoundary>
<PageView {...options} />
</EuiErrorBoundary>
);
});
ManagementPageView.displayName = 'ManagementPageView';

View file

@ -8,6 +8,7 @@ import React from 'react';
import * as reactTestingLibrary from '@testing-library/react';
import { EndpointList } from './index';
import '../../../../common/mock/match_media.ts';
import {
mockEndpointDetailsApiResult,
mockEndpointResultList,

View file

@ -10,15 +10,10 @@ import {
EuiBasicTable,
EuiBasicTableColumn,
EuiText,
EuiTitle,
EuiSpacer,
EuiLink,
EuiHealth,
EuiToolTip,
EuiSelectableProps,
EuiBetaBadge,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
@ -36,8 +31,6 @@ import {
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { CreateStructuredSelector } from '../../../../common/store';
import { Immutable, HostInfo } from '../../../../../common/endpoint/types';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { ManagementPageView } from '../../../components/management_page_view';
import { PolicyEmptyState, HostsEmptyState } from '../../../components/management_empty_state';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
@ -50,6 +43,7 @@ import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/rou
import { useFormatUrl } from '../../../../common/components/link_to';
import { EndpointAction } from '../store/action';
import { EndpointPolicyLink } from './components/endpoint_policy_link';
import { AdministrationListPage } from '../../../components/administration_list_page';
const EndpointListNavLink = memo<{
name: string;
@ -375,40 +369,20 @@ export const EndpointList = () => {
]);
return (
<ManagementPageView
viewType="list"
<AdministrationListPage
data-test-subj="endpointPage"
headerLeft={
<>
<EuiFlexGroup alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1 data-test-subj="pageViewHeaderLeftTitle">
<FormattedMessage
id="xpack.securitySolution.endpoint.list.pageTitle"
defaultMessage="Endpoints"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBetaBadge
label={i18n.translate('xpack.securitySolution.endpoint.list.beta', {
defaultMessage: 'Beta',
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.securitySolution.endpoint.list.pageSubTitle"
defaultMessage="Hosts running Elastic Endpoint Security"
/>
</p>
</EuiText>
</>
beta={true}
title={
<FormattedMessage
id="xpack.securitySolution.endpoint.list.pageTitle"
defaultMessage="Endpoints"
/>
}
subtitle={
<FormattedMessage
id="xpack.securitySolution.endpoint.list.pageSubTitle"
defaultMessage="Hosts running Elastic Endpoint Security"
/>
}
>
{hasSelectedEndpoint && <EndpointDetailsFlyout />}
@ -425,7 +399,6 @@ export const EndpointList = () => {
</>
)}
{renderTableOrEmptyState}
<SpyRoute pageName={SecurityPageName.administration} />
</ManagementPageView>
</AdministrationListPage>
);
};

View file

@ -7,6 +7,7 @@
import React from 'react';
import { ManagementContainer } from './index';
import '../../common/mock/match_media.ts';
import { AppContextTestRender, createAppRootMockRenderer } from '../../common/mock/endpoint';
import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled';
@ -22,15 +23,13 @@ describe('when in the Admistration tab', () => {
it('should display the No Permissions view when Ingest is OFF', async () => {
(useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false });
const renderResult = render();
const noIngestPermissions = await renderResult.findByTestId('noIngestPermissions');
expect(noIngestPermissions).not.toBeNull();
expect(await render().findByTestId('noIngestPermissions')).not.toBeNull();
});
it('should display the Management view when Ingest is ON', async () => {
(useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true });
const renderResult = render();
const endpointPage = await renderResult.findByTestId('endpointPage');
expect(endpointPage).not.toBeNull();
expect(await render().findByTestId('endpointPage')).not.toBeNull();
});
});

View file

@ -11,55 +11,50 @@ import { useHistory, Route, Switch } from 'react-router-dom';
import { ChromeBreadcrumb } from 'kibana/public';
import { EuiText, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { PolicyContainer } from './policy';
import {
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_ROOT_PATH,
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
} from '../common/constants';
import { NotFoundPage } from '../../app/404';
import { EndpointsContainer } from './endpoint_hosts';
import { PolicyContainer } from './policy';
import { TrustedAppsContainer } from './trusted_apps';
import { getEndpointListPath } from '../common/routing';
import { APP_ID, SecurityPageName } from '../../../common/constants';
import { GetUrlForApp } from '../../common/components/navigation/types';
import { AdministrationRouteSpyState } from '../../common/utils/route/types';
import { ADMINISTRATION } from '../../app/home/translations';
import { AdministrationSubTab } from '../types';
import { ENDPOINTS_TAB, POLICIES_TAB } from '../common/translations';
import { ENDPOINTS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from '../common/translations';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled';
const TabNameMappedToI18nKey: Record<string, string> = {
const TabNameMappedToI18nKey: Record<AdministrationSubTab, string> = {
[AdministrationSubTab.endpoints]: ENDPOINTS_TAB,
[AdministrationSubTab.policies]: POLICIES_TAB,
[AdministrationSubTab.trustedApps]: TRUSTED_APPS_TAB,
};
export const getBreadcrumbs = (
export function getBreadcrumbs(
params: AdministrationRouteSpyState,
search: string[],
getUrlForApp: GetUrlForApp
): ChromeBreadcrumb[] => {
let breadcrumb = [
): ChromeBreadcrumb[] {
return [
{
text: ADMINISTRATION,
href: getUrlForApp(`${APP_ID}:${SecurityPageName.administration}`, {
path: !isEmpty(search[0]) ? search[0] : '',
}),
},
];
const tabName = params?.tabName;
if (!tabName) return breadcrumb;
breadcrumb = [
...breadcrumb,
{
...(params?.tabName ? [params?.tabName] : []).map((tabName) => ({
text: TabNameMappedToI18nKey[tabName],
href: '',
},
})),
];
return breadcrumb;
};
}
const NoPermissions = memo(() => {
return (
@ -104,6 +99,7 @@ export const ManagementContainer = memo(() => {
<Switch>
<Route path={MANAGEMENT_ROUTING_ENDPOINTS_PATH} component={EndpointsContainer} />
<Route path={MANAGEMENT_ROUTING_POLICIES_PATH} component={PolicyContainer} />
<Route path={MANAGEMENT_ROUTING_TRUSTED_APPS_PATH} component={TrustedAppsContainer} />
<Route
path={MANAGEMENT_ROUTING_ROOT_PATH}
exact

View file

@ -8,6 +8,7 @@ import React from 'react';
import { mount } from 'enzyme';
import { PolicyDetails } from './policy_details';
import '../../../../common/mock/match_media.ts';
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
import { getPolicyDetailPath, getEndpointListPath } from '../../../common/routing';
@ -123,35 +124,29 @@ describe('Policy Details', () => {
it('should display back to list button and policy title', () => {
policyView.update();
const pageHeaderLeft = policyView.find(
'EuiPageHeaderSection[data-test-subj="pageViewHeaderLeft"]'
);
const backToListButton = pageHeaderLeft.find('EuiButtonEmpty');
expect(backToListButton.prop('iconType')).toBe('arrowLeft');
expect(backToListButton.prop('href')).toBe(endpointListPath);
expect(backToListButton.text()).toBe('Back to endpoint hosts');
const backToListLink = policyView.find('LinkIcon[dataTestSubj="policyDetailsBackLink"]');
expect(backToListLink.prop('iconType')).toBe('arrowLeft');
expect(backToListLink.prop('href')).toBe(endpointListPath);
expect(backToListLink.text()).toBe('Back to endpoint hosts');
const pageTitle = pageHeaderLeft.find('[data-test-subj="pageViewHeaderLeftTitle"]');
const pageTitle = policyView.find('h1[data-test-subj="header-page-title"]');
expect(pageTitle).toHaveLength(1);
expect(pageTitle.text()).toEqual(policyPackageConfig.name);
});
it('should navigate to list if back to link is clicked', async () => {
policyView.update();
const backToListButton = policyView.find(
'EuiPageHeaderSection[data-test-subj="pageViewHeaderLeft"] EuiButtonEmpty'
);
const backToListLink = policyView.find('a[data-test-subj="policyDetailsBackLink"]');
expect(history.location.pathname).toEqual(policyDetailsPathUrl);
backToListButton.simulate('click', { button: 0 });
backToListLink.simulate('click', { button: 0 });
expect(history.location.pathname).toEqual(endpointListPath);
});
it('should display agent stats', async () => {
await asyncActions;
policyView.update();
const headerRight = policyView.find(
'EuiPageHeaderSection[data-test-subj="pageViewHeaderRight"]'
);
const agentsSummary = headerRight.find('EuiFlexGroup[data-test-subj="policyAgentsSummary"]');
const agentsSummary = policyView.find('EuiFlexGroup[data-test-subj="policyAgentsSummary"]');
expect(agentsSummary).toHaveLength(1);
expect(agentsSummary.text()).toBe('Endpoints5Online3Offline1Error1');
});

View file

@ -38,9 +38,6 @@ import { WindowsEvents, MacEvents, LinuxEvents } from './policy_forms/events';
import { MalwareProtections } from './policy_forms/protections/malware';
import { useToasts } from '../../../../common/lib/kibana';
import { AppAction } from '../../../../common/store/actions';
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { PageViewHeaderTitle } from '../../../../common/components/endpoint/page_view';
import { ManagementPageView } from '../../../components/management_page_view';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { SecurityPageName } from '../../../../app/types';
import { getEndpointListPath } from '../../../common/routing';
@ -48,6 +45,8 @@ import { useFormatUrl } from '../../../../common/components/link_to';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { MANAGEMENT_APP_ID } from '../../../common/constants';
import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types';
import { WrapperPage } from '../../../../common/components/wrapper_page';
import { HeaderPage } from '../../../../common/components/header_page';
export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
@ -109,8 +108,6 @@ export const PolicyDetails = React.memo(() => {
}
}, [navigateToApp, toasts, policyName, policyUpdateStatus, routeState]);
const handleBackToListOnClick = useNavigateByRouterEventHandler(hostListRouterPath);
const navigateToAppArguments = useMemo((): Parameters<ApplicationStart['navigateToApp']> => {
return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }];
}, [hostListRouterPath, routeState?.onCancelNavigateTo]);
@ -142,7 +139,7 @@ export const PolicyDetails = React.memo(() => {
// Else, if we have an error, then show error on the page.
if (!policyItem) {
return (
<ManagementPageView viewType="details">
<WrapperPage noTimeline>
{isPolicyLoading ? (
<EuiLoadingSpinner size="xl" />
) : policyApiError ? (
@ -151,28 +148,10 @@ export const PolicyDetails = React.memo(() => {
</EuiCallOut>
) : null}
<SpyRoute pageName={SecurityPageName.administration} />
</ManagementPageView>
</WrapperPage>
);
}
const headerLeftContent = (
<div>
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
<EuiButtonEmpty
iconType="arrowLeft"
contentProps={{ style: { paddingLeft: '0' } }}
onClick={handleBackToListOnClick}
href={formatUrl(hostListRouterPath)}
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.backToListTitle"
defaultMessage="Back to endpoint hosts"
/>
</EuiButtonEmpty>
<PageViewHeaderTitle className="eui-textTruncate">{policyItem.name}</PageViewHeaderTitle>
</div>
);
const headerRightContent = (
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
<EuiFlexItem grow={false}>
@ -222,12 +201,21 @@ export const PolicyDetails = React.memo(() => {
onConfirm={handleSaveConfirmation}
/>
)}
<ManagementPageView
viewType="details"
data-test-subj="policyDetailsPage"
headerLeft={headerLeftContent}
headerRight={headerRightContent}
>
<WrapperPage noTimeline data-test-subj="policyDetailsPage">
<HeaderPage
title={policyItem.name}
backOptions={{
text: i18n.translate('xpack.securitySolution.endpoint.policy.details.backToListTitle', {
defaultMessage: 'Back to endpoint hosts',
}),
href: formatUrl(hostListRouterPath),
pageId: SecurityPageName.administration,
dataTestSubj: 'policyDetailsBackLink',
}}
>
{headerRightContent}
</HeaderPage>
<EuiText size="xs" color="subdued">
<h4>
<FormattedMessage
@ -236,9 +224,11 @@ export const PolicyDetails = React.memo(() => {
/>
</h4>
</EuiText>
<EuiSpacer size="xs" />
<MalwareProtections />
<EuiSpacer size="l" />
<EuiText size="xs" color="subdued">
<h4>
<FormattedMessage
@ -247,13 +237,15 @@ export const PolicyDetails = React.memo(() => {
/>
</h4>
</EuiText>
<EuiSpacer size="xs" />
<WindowsEvents />
<EuiSpacer size="l" />
<MacEvents />
<EuiSpacer size="l" />
<LinuxEvents />
</ManagementPageView>
</WrapperPage>
<SpyRoute pageName={SecurityPageName.administration} />
</>
);

View file

@ -6,6 +6,7 @@
import React from 'react';
import { PolicyList } from './index';
import '../../../../common/mock/match_media.ts';
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
import { setPolicyListApiMockImplementation } from '../store/policy_list/test_mock_utils';

View file

@ -8,7 +8,6 @@ import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from
import {
EuiBasicTable,
EuiText,
EuiTitle,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
@ -23,7 +22,6 @@ import {
EuiConfirmModal,
EuiCallOut,
EuiButton,
EuiBetaBadge,
EuiHorizontalRule,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -41,9 +39,7 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ
import { Immutable, PolicyData } from '../../../../../common/endpoint/types';
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { LinkToApp } from '../../../../common/components/endpoint/link_to_app';
import { ManagementPageView } from '../../../components/management_page_view';
import { PolicyEmptyState } from '../../../components/management_empty_state';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time';
import { SecurityPageName } from '../../../../app/types';
import { useFormatUrl } from '../../../../common/components/link_to';
@ -51,6 +47,7 @@ import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { CreatePackageConfigRouteState } from '../../../../../../ingest_manager/public';
import { MANAGEMENT_APP_ID } from '../../../common/constants';
import { AdministrationListPage } from '../../../components/administration_list_page';
interface TableChangeCallbackArguments {
page: { index: number; size: number };
@ -405,42 +402,22 @@ export const PolicyList = React.memo(() => {
}}
/>
)}
<ManagementPageView
viewType="list"
<AdministrationListPage
data-test-subj="policyListPage"
headerLeft={
<>
<EuiFlexGroup alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1 data-test-subj="pageViewHeaderLeftTitle">
<FormattedMessage
id="xpack.securitySolution.policyList.pageTitle"
defaultMessage="Policies"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBetaBadge
label={i18n.translate('xpack.securitySolution.endpoint.policyList.beta', {
defaultMessage: 'Beta',
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.securitySolution.policyList.pageSubTitle"
defaultMessage="View and configure protections"
/>
</p>
</EuiText>
</>
beta={true}
title={
<FormattedMessage
id="xpack.securitySolution.policyList.pageTitle"
defaultMessage="Policies"
/>
}
headerRight={
subtitle={
<FormattedMessage
id="xpack.securitySolution.policyList.pageSubTitle"
defaultMessage="View and configure protections"
/>
}
actions={
<EuiButton
iconType="plusInCircle"
onClick={handleCreatePolicyClick}
@ -466,8 +443,7 @@ export const PolicyList = React.memo(() => {
</>
)}
{bodyContent}
<SpyRoute pageName={SecurityPageName.administration} />
</ManagementPageView>
</AdministrationListPage>
</>
);
});

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Switch, Route } from 'react-router-dom';
import React from 'react';
import { TrustedAppsPage } from './view';
import { MANAGEMENT_ROUTING_TRUSTED_APPS_PATH } from '../../common/constants';
import { NotFoundPage } from '../../../app/404';
export function TrustedAppsContainer() {
return (
<Switch>
<Route path={MANAGEMENT_ROUTING_TRUSTED_APPS_PATH} exact component={TrustedAppsPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
);
}

View file

@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TrustedAppsPage rendering 1`] = `
<AdministrationListPage
beta={true}
subtitle={
<FormattedMessage
defaultMessage="View and configure trusted applications"
id="xpack.securitySolution.trustedapps.list.pageSubTitle"
values={Object {}}
/>
}
title={
<FormattedMessage
defaultMessage="Trusted Applications"
id="xpack.securitySolution.trustedapps.list.pageTitle"
values={Object {}}
/>
}
/>
`;

View file

@ -0,0 +1,6 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export * from './trusted_apps_page';

View file

@ -3,16 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { EndpointList } from './view';
import { TrustedAppsPage } from './trusted_apps_page';
export const EndpointHostsRoutes: React.FC = () => (
<Switch>
<Route path="/:pageName(endpoint-endpoints)">
<EndpointList />
</Route>
</Switch>
);
describe('TrustedAppsPage', () => {
test('rendering', () => {
expect(shallow(<TrustedAppsPage />)).toMatchSnapshot();
});
});

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { AdministrationListPage } from '../../../components/administration_list_page';
export function TrustedAppsPage() {
return (
<AdministrationListPage
beta={true}
title={
<FormattedMessage
id="xpack.securitySolution.trustedapps.list.pageTitle"
defaultMessage="Trusted Applications"
/>
}
subtitle={
<FormattedMessage
id="xpack.securitySolution.trustedapps.list.pageSubTitle"
defaultMessage="View and configure trusted applications"
/>
}
/>
);
}

View file

@ -27,6 +27,7 @@ export type ManagementState = CombinedState<{
export enum AdministrationSubTab {
endpoints = 'endpoints',
policies = 'policy',
trustedApps = 'trusted_apps',
}
/**

View file

@ -16266,7 +16266,6 @@
"xpack.securitySolution.endpoint.details.policyResponse.success": "成功",
"xpack.securitySolution.endpoint.details.policyResponse.warning": "警告",
"xpack.securitySolution.endpoint.details.policyResponse.workflow": "ワークフロー",
"xpack.securitySolution.endpoint.list.beta": "ベータ",
"xpack.securitySolution.endpoint.list.loadingPolicies": "ポリシー構成を読み込んでいます…",
"xpack.securitySolution.endpoint.list.noEndpointsInstructions": "セキュリティポリシーを作成しました。以下のステップに従い、エージェントでElastic Endpoint Security機能を有効にする必要があります。",
"xpack.securitySolution.endpoint.list.noEndpointsPrompt": "エージェントでElastic Endpoint Securityを有効にする",
@ -16328,7 +16327,6 @@
"xpack.securitySolution.endpoint.policyList.actionButtonText": "Endpoint Securityを追加",
"xpack.securitySolution.endpoint.policyList.actionMenu": "開く",
"xpack.securitySolution.endpoint.policyList.agentConfigAction": "エージェント構成を表示",
"xpack.securitySolution.endpoint.policyList.beta": "ベータ",
"xpack.securitySolution.endpoint.policyList.createdAt": "作成日時",
"xpack.securitySolution.endpoint.policyList.createdBy": "作成者",
"xpack.securitySolution.endpoint.policyList.createNewButton": "新しいポリシーを作成",

View file

@ -16272,7 +16272,6 @@
"xpack.securitySolution.endpoint.details.policyResponse.success": "成功",
"xpack.securitySolution.endpoint.details.policyResponse.warning": "警告",
"xpack.securitySolution.endpoint.details.policyResponse.workflow": "工作流",
"xpack.securitySolution.endpoint.list.beta": "公测版",
"xpack.securitySolution.endpoint.list.loadingPolicies": "正在加载政策配置",
"xpack.securitySolution.endpoint.list.noEndpointsInstructions": "您已创建安全策略。现在您需要按照下面的步骤在代理上启用 Elastic Endpoint Security 功能。",
"xpack.securitySolution.endpoint.list.noEndpointsPrompt": "在您的代理上启用 Elastic Endpoint Security",
@ -16335,7 +16334,6 @@
"xpack.securitySolution.endpoint.policyList.actionButtonText": "添加 Endpoint Security",
"xpack.securitySolution.endpoint.policyList.actionMenu": "打开",
"xpack.securitySolution.endpoint.policyList.agentConfigAction": "查看代理配置",
"xpack.securitySolution.endpoint.policyList.beta": "公测版",
"xpack.securitySolution.endpoint.policyList.createdAt": "创建日期",
"xpack.securitySolution.endpoint.policyList.createdBy": "创建者",
"xpack.securitySolution.endpoint.policyList.createNewButton": "创建新策略",

View file

@ -27,8 +27,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('finds page title', async () => {
const title = await testSubjects.getVisibleText('pageViewHeaderLeftTitle');
expect(title).to.equal('Endpoints');
const title = await testSubjects.getVisibleText('header-page-title');
expect(title).to.equal('Endpoints BETA');
});
it('displays table data', async () => {

View file

@ -47,7 +47,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
it('should display policy view', async () => {
expect(await testSubjects.getVisibleText('pageViewHeaderLeftTitle')).to.equal(
expect(await testSubjects.getVisibleText('header-page-title')).to.equal(
policyInfo.packageConfig.name
);
});

View file

@ -29,8 +29,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await testSubjects.existOrFail('policyListPage');
});
it('displays page title', async () => {
const policyTitle = await testSubjects.getVisibleText('pageViewHeaderLeftTitle');
expect(policyTitle).to.equal('Policies');
const policyTitle = await testSubjects.getVisibleText('header-page-title');
expect(policyTitle).to.equal('Policies BETA');
});
it('shows header create policy button', async () => {
const createButtonTitle = await testSubjects.getVisibleText('headerCreateNewPolicyButton');

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['trustedApps']);
const testSubjects = getService('testSubjects');
describe('endpoint list', function () {
this.tags('ciGroup7');
describe('when there is data', () => {
before(async () => {
await pageObjects.trustedApps.navigateToTrustedAppsList();
});
it('finds page title', async () => {
expect(await testSubjects.getVisibleText('header-page-title')).to.equal(
'Trusted applications BETA'
);
});
});
});
};

View file

@ -7,6 +7,7 @@
import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects';
import { EndpointPageProvider } from './endpoint_page';
import { EndpointPolicyPageProvider } from './policy_page';
import { TrustedAppsPageProvider } from './trusted_apps_page';
import { EndpointPageUtils } from './page_utils';
import { IngestManagerCreatePackageConfig } from './ingest_manager_create_package_config_page';
@ -14,6 +15,7 @@ export const pageObjects = {
...xpackFunctionalPageObjects,
endpoint: EndpointPageProvider,
policy: EndpointPolicyPageProvider,
trustedApps: TrustedAppsPageProvider,
endpointPageUtils: EndpointPageUtils,
ingestManagerCreatePackageConfig: IngestManagerCreatePackageConfig,
};

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FtrProviderContext } from '../ftr_provider_context';
export function TrustedAppsPageProvider({ getPageObjects }: FtrProviderContext) {
const pageObjects = getPageObjects(['common', 'header', 'endpointPageUtils']);
return {
async navigateToTrustedAppsList(searchParams?: string) {
await pageObjects.common.navigateToUrlWithBrowserHistory(
'securitySolutionManagement',
`/trusted_apps${searchParams ? `?${searchParams}` : ''}`
);
await pageObjects.header.waitUntilLoadingHasFinished();
},
};
}