mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Siem Global Navigation Updates (#36372)
* use new condensed tabs * pull button out of settings menu * clean up global nav styles * account for dark mode * remove unnecessary styles * move kql bar to relevant location * tweak page header styles * add border prop to HeaderPanel * placeholder text updates * fiddling with direction control visuals * move kql back above kpis, per tudor * change host table name to “all hosts” * change direction select and tests (TY Garrett) * rm console.log * rm `EuiHorizontalRule` import * sticky poc * apply poc to all relevant pages * general cleanup * update snapshots * Override EuiSuperDatePicker spacing bug * restrict overview beats data to the last 24 hours * garrett & tudor’s suggested changes * overview data refresh on render, per garrett * update tests and snapshots * remove sticky at small bp and update tests
This commit is contained in:
parent
ee1335b464
commit
f7519aaf95
50 changed files with 882 additions and 1147 deletions
|
@ -12,14 +12,16 @@
|
|||
"@types/color": "^3.0.0",
|
||||
"@types/lodash": "^4.14.110",
|
||||
"@types/memoize-one": "^4.1.0",
|
||||
"@types/react-beautiful-dnd": "^10.0.1"
|
||||
"@types/react-beautiful-dnd": "^10.0.1",
|
||||
"@types/react-sticky": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-beautiful-dnd": "^10.0.1",
|
||||
"react-markdown": "^4.0.6",
|
||||
"apollo-link-error": "^1.1.7",
|
||||
"lodash": "^4.17.10",
|
||||
"memoize-one": "^5.0.0",
|
||||
"apollo-link-error": "^1.1.7",
|
||||
"react-beautiful-dnd": "^10.0.1",
|
||||
"react-markdown": "^4.0.6",
|
||||
"react-sticky": "^6.0.3",
|
||||
"suricata-sid-db": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AppSettingsPopover rendering it renders against snapshot 1`] = `
|
||||
<Component
|
||||
onClick={[Function]}
|
||||
onClose={[Function]}
|
||||
showPopover={false}
|
||||
/>
|
||||
`;
|
|
@ -1,49 +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 { mount, shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import { noop } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
|
||||
import { AppSettingsPopover } from './app_settings_popover';
|
||||
|
||||
describe('AppSettingsPopover', () => {
|
||||
describe('rendering', () => {
|
||||
test('it renders against snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<AppSettingsPopover onClick={noop} onClose={noop} showPopover={false} />
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders a settings gear icon', () => {
|
||||
const wrapper = mount(
|
||||
<AppSettingsPopover onClick={noop} onClose={noop} showPopover={false} />
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="gear"]').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onClick', () => {
|
||||
test('it invokes onClick when clicked', () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<AppSettingsPopover onClick={onClick} onClose={noop} showPopover={false} />
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="gear"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,45 +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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPopover } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
showPopover: boolean;
|
||||
onClick: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const SettingsPopover = styled(EuiPopover)`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const AppSettingsPopover = pure<Props>(({ showPopover, onClick, onClose }) => (
|
||||
<SettingsPopover
|
||||
anchorPosition="downRight"
|
||||
button={<EuiIcon data-test-subj="gear" type="gear" size="l" onClick={onClick} />}
|
||||
closePopover={onClose}
|
||||
data-test-subj="app-settings-popover"
|
||||
id="timelineSettingsPopover"
|
||||
isOpen={showPopover}
|
||||
>
|
||||
<EuiFlexGroup direction="column" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="add-data"
|
||||
href="kibana#home/tutorial_directory/security"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.ADD_DATA}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</SettingsPopover>
|
||||
));
|
|
@ -1,41 +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 * as React from 'react';
|
||||
|
||||
import { AppSettingsPopover } from './app_settings_popover';
|
||||
|
||||
export interface State {
|
||||
showPopover: boolean;
|
||||
}
|
||||
|
||||
export class AppSettings extends React.PureComponent<{}, State> {
|
||||
public readonly state = {
|
||||
showPopover: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<AppSettingsPopover
|
||||
onClick={this.onClick}
|
||||
onClose={this.onClose}
|
||||
showPopover={this.state.showPopover}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
this.setState({
|
||||
showPopover: !this.state.showPopover,
|
||||
});
|
||||
};
|
||||
|
||||
private onClose = () => {
|
||||
this.setState({
|
||||
showPopover: false,
|
||||
});
|
||||
};
|
||||
}
|
9
x-pack/plugins/siem/public/components/filters_global/__snapshots__/filters_global.test.tsx.snap
generated
Normal file
9
x-pack/plugins/siem/public/components/filters_global/__snapshots__/filters_global.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rendering renders correctly 1`] = `
|
||||
<Component>
|
||||
<p>
|
||||
Additional filters here.
|
||||
</p>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import React from 'react';
|
||||
|
||||
import '../../mock/match_media';
|
||||
import { FiltersGlobal } from './index';
|
||||
|
||||
describe('rendering', () => {
|
||||
test('renders correctly', () => {
|
||||
const wrapper = shallow(
|
||||
<FiltersGlobal>
|
||||
<p>Additional filters here.</p>
|
||||
</FiltersGlobal>
|
||||
);
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React from 'react';
|
||||
import { Sticky } from 'react-sticky';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { SuperDatePicker } from '../super_date_picker';
|
||||
|
||||
const offsetChrome = 49;
|
||||
const gutterTimeline = '70px'; // Temporary until timeline is moved - MichaelMarcialis
|
||||
|
||||
const disableSticky = 'screen and (max-width: ' + euiLightVars.euiBreakpoints.s + ')';
|
||||
const disableStickyMq = window.matchMedia(disableSticky);
|
||||
|
||||
const Aside = styled.aside<{ isSticky?: boolean }>`
|
||||
${props => `
|
||||
background: ${props.theme.eui.euiColorEmptyShade};
|
||||
border-bottom: ${props.theme.eui.euiBorderThin};
|
||||
box-sizing: content-box;
|
||||
margin: 0 -${gutterTimeline} 0 -${props.theme.eui.euiSizeL};
|
||||
padding: ${props.theme.eui.euiSize} ${gutterTimeline} ${props.theme.eui.euiSize} ${
|
||||
props.theme.eui.euiSizeL
|
||||
};
|
||||
|
||||
${props.isSticky &&
|
||||
`
|
||||
top: ${offsetChrome}px !important;
|
||||
z-index: ${props.theme.eui.euiZNavigation};
|
||||
`}
|
||||
|
||||
@media only ${disableSticky} {
|
||||
position: static !important;
|
||||
z-index: ${props.theme.eui.euiZContent} !important;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
// Temporary fix for EuiSuperDatePicker whitespace bug and auto width - Michael Marcialis
|
||||
const FlexItemWithDatePickerFix = styled(EuiFlexItem)`
|
||||
.euiSuperDatePicker__flexWrapper {
|
||||
max-width: none;
|
||||
width: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface FiltersGlobalProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const FiltersGlobal = pure<FiltersGlobalProps>(({ children }) => (
|
||||
<Sticky disableCompensation={disableStickyMq.matches} topOffset={-offsetChrome}>
|
||||
{({ style, isSticky }) => (
|
||||
<Aside isSticky={isSticky} style={style}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={8}>{children}</EuiFlexItem>
|
||||
|
||||
<FlexItemWithDatePickerFix grow={4}>
|
||||
<SuperDatePicker id="global" />
|
||||
</FlexItemWithDatePickerFix>
|
||||
</EuiFlexGroup>
|
||||
</Aside>
|
||||
)}
|
||||
</Sticky>
|
||||
));
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { FiltersGlobal } from './filters_global';
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
exports[`Select Flow Direction rendering it renders the basic group button for uni-direction and bi-direction 1`] = `
|
||||
<Component
|
||||
id="TestFlowDirectionId"
|
||||
onChangeDirection={[MockFunction]}
|
||||
selectedDirection="uniDirectional"
|
||||
/>
|
||||
|
|
|
@ -20,7 +20,6 @@ describe('Select Flow Direction', () => {
|
|||
test('it renders the basic group button for uni-direction and bi-direction', () => {
|
||||
const wrapper = shallow(
|
||||
<FlowDirectionSelect
|
||||
id={TestFlowDirectionId}
|
||||
selectedDirection={FlowDirection.uniDirectional}
|
||||
onChangeDirection={mockOnChange}
|
||||
/>
|
||||
|
@ -40,22 +39,18 @@ describe('Select Flow Direction', () => {
|
|||
};
|
||||
const wrapper = mount(
|
||||
<FlowDirectionSelect
|
||||
id={TestFlowDirectionId}
|
||||
selectedDirection={FlowDirection.uniDirectional}
|
||||
onChangeDirection={mockOnChange}
|
||||
/>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('input[value="biDirectional"]')
|
||||
.find(`[data-test-subj="${FlowDirection.biDirectional}"]`)
|
||||
.first()
|
||||
.simulate('change', event);
|
||||
.simulate('click', event);
|
||||
wrapper.update();
|
||||
|
||||
expect(mockOnChange.mock.calls[0]).toEqual([
|
||||
`${TestFlowDirectionId}-select-flow-direction-biDirectional`,
|
||||
'biDirectional',
|
||||
]);
|
||||
expect(mockOnChange.mock.calls[0]).toEqual(['biDirectional']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,49 +4,36 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButtonGroup, EuiButtonGroupProps } from '@elastic/eui';
|
||||
import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { pure } from 'recompose';
|
||||
import { FlowDirection } from '../../graphql/types';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
type MyEuiButtonGroupProps = Pick<
|
||||
EuiButtonGroupProps,
|
||||
'options' | 'idSelected' | 'onChange' | 'color' | 'type'
|
||||
> & {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
const MyEuiButtonGroup: React.FC<MyEuiButtonGroupProps> = EuiButtonGroup;
|
||||
|
||||
const getToggleButtonDirection = (id: string) => [
|
||||
{
|
||||
id: `${id}-select-flow-direction-${FlowDirection.uniDirectional}`,
|
||||
label: i18n.UNIDIRECTIONAL,
|
||||
value: FlowDirection.uniDirectional,
|
||||
},
|
||||
{
|
||||
id: `${id}-select-flow-direction-${FlowDirection.biDirectional}`,
|
||||
label: i18n.BIDIRECTIONAL,
|
||||
value: FlowDirection.biDirectional,
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
selectedDirection: FlowDirection;
|
||||
onChangeDirection: (id: string, value: FlowDirection) => void;
|
||||
onChangeDirection: (value: FlowDirection) => void;
|
||||
}
|
||||
|
||||
export const FlowDirectionSelect = pure<Props>(({ id, onChangeDirection, selectedDirection }) => (
|
||||
<MyEuiButtonGroup
|
||||
name={`${id}-${selectedDirection}`}
|
||||
options={getToggleButtonDirection(id)}
|
||||
idSelected={`${id}-select-flow-direction-${selectedDirection}`}
|
||||
onChange={onChangeDirection}
|
||||
color="primary"
|
||||
type="single"
|
||||
/>
|
||||
export const FlowDirectionSelect = pure<Props>(({ onChangeDirection, selectedDirection }) => (
|
||||
<EuiFilterGroup>
|
||||
<EuiFilterButton
|
||||
withNext
|
||||
hasActiveFilters={selectedDirection === FlowDirection.uniDirectional}
|
||||
onClick={() => onChangeDirection(FlowDirection.uniDirectional)}
|
||||
data-test-subj={FlowDirection.uniDirectional}
|
||||
>
|
||||
{i18n.UNIDIRECTIONAL}
|
||||
</EuiFilterButton>
|
||||
|
||||
<EuiFilterButton
|
||||
hasActiveFilters={selectedDirection === FlowDirection.biDirectional}
|
||||
onClick={() => onChangeDirection(FlowDirection.biDirectional)}
|
||||
data-test-subj={FlowDirection.biDirectional}
|
||||
>
|
||||
{i18n.BIDIRECTIONAL}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
));
|
||||
|
|
12
x-pack/plugins/siem/public/components/header_page/__snapshots__/header_page.test.tsx.snap
generated
Normal file
12
x-pack/plugins/siem/public/components/header_page/__snapshots__/header_page.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rendering renders correctly 1`] = `
|
||||
<Component
|
||||
subtitle="My Test Subtitle"
|
||||
title="My Test Title"
|
||||
>
|
||||
<p>
|
||||
My test supplement.
|
||||
</p>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderPage } from './index';
|
||||
|
||||
describe('rendering', () => {
|
||||
test('renders correctly', () => {
|
||||
const wrapper = shallow(
|
||||
<HeaderPage subtitle="My Test Subtitle" title="My Test Title">
|
||||
<p>My test supplement.</p>
|
||||
</HeaderPage>
|
||||
);
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Header = styled.header`
|
||||
${({ theme }) => `
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
padding-bottom: ${theme.eui.euiSizeL};
|
||||
margin: ${theme.eui.euiSizeL} 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
export interface HeaderPageProps {
|
||||
children?: React.ReactNode;
|
||||
subtitle?: string | React.ReactNode;
|
||||
title: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export const HeaderPage = pure<HeaderPageProps>(({ children, subtitle, title }) => (
|
||||
<Header>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="page_headline_title">{title}</h1>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
|
||||
</EuiFlexGroup>
|
||||
</Header>
|
||||
));
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { HeaderPage } from './header_page';
|
|
@ -9,32 +9,45 @@ import React from 'react';
|
|||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Header = styled.header<{ border?: boolean }>`
|
||||
${props => `
|
||||
margin-bottom: ${props.theme.eui.euiSizeL};
|
||||
|
||||
${props.border &&
|
||||
`
|
||||
border-bottom: ${props.theme.eui.euiBorderThin};
|
||||
padding-bottom: ${props.theme.eui.euiSizeL};
|
||||
`}
|
||||
`}
|
||||
`;
|
||||
|
||||
export interface HeaderPanelProps {
|
||||
border?: boolean;
|
||||
children?: React.ReactNode;
|
||||
subtitle?: string | React.ReactNode;
|
||||
title: string | React.ReactNode;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const HeaderPanel = pure<HeaderPanelProps>(({ children, subtitle, title, tooltip }) => (
|
||||
<FlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="page_headline_title">
|
||||
{title}{' '}
|
||||
{tooltip && <EuiIconTip color="subdued" content={tooltip} position="top" size="l" />}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
export const HeaderPanel = pure<HeaderPanelProps>(
|
||||
({ border, children, subtitle, title, tooltip }) => (
|
||||
<Header border={border}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="page_headline_title">
|
||||
{title}{' '}
|
||||
{tooltip && <EuiIconTip color="subdued" content={tooltip} position="top" size="l" />}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
|
||||
</FlexGroup>
|
||||
));
|
||||
|
||||
const FlexGroup = styled(EuiFlexGroup)`
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
|
||||
</EuiFlexGroup>
|
||||
</Header>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,195 +1,105 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Load More Table Component rendering it renders the default load more table 1`] = `
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
>
|
||||
<styled.div>
|
||||
<pure(Component)
|
||||
subtitle="Showing: 1 Test Unit"
|
||||
title="Hosts"
|
||||
tooltip="My test tooltip"
|
||||
>
|
||||
<p>
|
||||
My test supplement.
|
||||
</p>
|
||||
</pure(Component)>
|
||||
<Styled(EuiBasicTable)
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "node.host.name",
|
||||
"hideForMobile": false,
|
||||
"name": "Host",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
Object {
|
||||
"field": "node.host.firstSeen",
|
||||
"hideForMobile": false,
|
||||
"name": "First seen",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
Object {
|
||||
"field": "node.host.os",
|
||||
"hideForMobile": false,
|
||||
"name": "OS",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
Object {
|
||||
"field": "node.host.version",
|
||||
"hideForMobile": false,
|
||||
"name": "Version",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
]
|
||||
}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"cursor": Object {
|
||||
"value": "98966fa2013c396155c460d35c0902be",
|
||||
},
|
||||
"host": Object {
|
||||
"_id": "cPsuhGcB0WOhS6qyTKC0",
|
||||
"firstSeen": "2018-12-06T15:40:53.319Z",
|
||||
"name": "elrond.elstc.co",
|
||||
"os": "Ubuntu",
|
||||
"version": "18.04.1 LTS (Bionic Beaver)",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"cursor": Object {
|
||||
"value": "aa7ca589f1b8220002f2fc61c64cfbf1",
|
||||
},
|
||||
"host": Object {
|
||||
"_id": "KwQDiWcB0WOhS6qyXmrW",
|
||||
"firstSeen": "2018-12-07T14:12:38.560Z",
|
||||
"name": "siem-kibana",
|
||||
"os": "Debian GNU/Linux",
|
||||
"version": "9 (stretch)",
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
onChange={[Function]}
|
||||
sorting={null}
|
||||
/>
|
||||
<styled.div>
|
||||
<EuiFlexGroup
|
||||
alignItems="flexStart"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
justifyContent="flexStart"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiPopover
|
||||
anchorPosition="downCenter"
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
type="button"
|
||||
>
|
||||
Rows:
|
||||
1
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
closePopover={[Function]}
|
||||
data-test-subj="loadingMoreSizeRowPopover"
|
||||
hasArrow={true}
|
||||
id="customizablePagination"
|
||||
isOpen={false}
|
||||
ownFocus={false}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
data-test-subj="loadingMorePickSizeRow"
|
||||
hasFocus={true}
|
||||
items={
|
||||
Array [
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
layoutAlign="center"
|
||||
onClick={[Function]}
|
||||
toolTipPosition="right"
|
||||
>
|
||||
2 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
layoutAlign="center"
|
||||
onClick={[Function]}
|
||||
toolTipPosition="right"
|
||||
>
|
||||
5 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
layoutAlign="center"
|
||||
onClick={[Function]}
|
||||
toolTipPosition="right"
|
||||
>
|
||||
10 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
layoutAlign="center"
|
||||
onClick={[Function]}
|
||||
toolTipPosition="right"
|
||||
>
|
||||
20 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
layoutAlign="center"
|
||||
onClick={[Function]}
|
||||
toolTipPosition="right"
|
||||
>
|
||||
50 rows
|
||||
</EuiContextMenuItem>,
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
alignItems="flexStart"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="loadingMoreButton"
|
||||
fill={false}
|
||||
iconSide="left"
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
Load More
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</styled.div>
|
||||
</styled.div>
|
||||
</EuiPanel>
|
||||
<LoadMoreTable
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "node.host.name",
|
||||
"hideForMobile": false,
|
||||
"name": "Host",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
Object {
|
||||
"field": "node.host.firstSeen",
|
||||
"hideForMobile": false,
|
||||
"name": "First seen",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
Object {
|
||||
"field": "node.host.os",
|
||||
"hideForMobile": false,
|
||||
"name": "OS",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
Object {
|
||||
"field": "node.host.version",
|
||||
"hideForMobile": false,
|
||||
"name": "Version",
|
||||
"render": [Function],
|
||||
"truncateText": false,
|
||||
},
|
||||
]
|
||||
}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={
|
||||
<p>
|
||||
My test supplement.
|
||||
</p>
|
||||
}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={
|
||||
Array [
|
||||
Object {
|
||||
"numberOfRow": 2,
|
||||
"text": "2 rows",
|
||||
},
|
||||
Object {
|
||||
"numberOfRow": 5,
|
||||
"text": "5 rows",
|
||||
},
|
||||
Object {
|
||||
"numberOfRow": 10,
|
||||
"text": "10 rows",
|
||||
},
|
||||
Object {
|
||||
"numberOfRow": 20,
|
||||
"text": "20 rows",
|
||||
},
|
||||
Object {
|
||||
"numberOfRow": 50,
|
||||
"text": "50 rows",
|
||||
},
|
||||
]
|
||||
}
|
||||
limit={1}
|
||||
loadMore={[Function]}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
pageOfItems={
|
||||
Array [
|
||||
Object {
|
||||
"cursor": Object {
|
||||
"value": "98966fa2013c396155c460d35c0902be",
|
||||
},
|
||||
"host": Object {
|
||||
"_id": "cPsuhGcB0WOhS6qyTKC0",
|
||||
"firstSeen": "2018-12-06T15:40:53.319Z",
|
||||
"name": "elrond.elstc.co",
|
||||
"os": "Ubuntu",
|
||||
"version": "18.04.1 LTS (Bionic Beaver)",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"cursor": Object {
|
||||
"value": "aa7ca589f1b8220002f2fc61c64cfbf1",
|
||||
},
|
||||
"host": Object {
|
||||
"_id": "KwQDiWcB0WOhS6qyXmrW",
|
||||
"firstSeen": "2018-12-07T14:12:38.560Z",
|
||||
"name": "siem-kibana",
|
||||
"os": "Debian GNU/Linux",
|
||||
"version": "9 (stretch)",
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
updateLimitPagination={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -12,29 +12,34 @@ import { Direction } from '../../graphql/types';
|
|||
|
||||
import { LoadMoreTable } from './index';
|
||||
import { getHostsColumns, mockData, rowItems, sortedHosts } from './index.mock';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
describe('Load More Table Component', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
const loadMore = jest.fn();
|
||||
const updateLimitPagination = jest.fn();
|
||||
describe('rendering', () => {
|
||||
test('it renders the default load more table', () => {
|
||||
const wrapper = shallow(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
|
@ -42,22 +47,24 @@ describe('Load More Table Component', () => {
|
|||
|
||||
test('it renders the loading panel at the beginning ', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={true}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={[]}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={true}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={[]}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -67,47 +74,49 @@ describe('Load More Table Component', () => {
|
|||
|
||||
test('it renders the over loading panel after data has been in the table ', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={true}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
wrapper.setState({ isEmptyTable: false });
|
||||
wrapper.setProps({ loading: true });
|
||||
|
||||
expect(wrapper.find('[data-test-subj="LoadingPanelLoadMoreTable"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it renders the loadMore button if need to fetch more', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -120,26 +129,26 @@ describe('Load More Table Component', () => {
|
|||
|
||||
test('it renders the Loading... in the more load button when fetching new data', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={true}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper.setProps({ loading: true });
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="InitialLoadingPanelLoadMoreTable"]').exists()
|
||||
).toBeFalsy();
|
||||
|
@ -148,27 +157,29 @@ describe('Load More Table Component', () => {
|
|||
.find('[data-test-subj="loadingMoreButton"]')
|
||||
.first()
|
||||
.text()
|
||||
).toContain('Loading...');
|
||||
).toContain('Loading…');
|
||||
});
|
||||
|
||||
test('it does NOT render the loadMore button because there is nothing else to fetch', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={false}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={false}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="loadingMoreButton"]').exists()).toBeFalsy();
|
||||
|
@ -176,22 +187,24 @@ describe('Load More Table Component', () => {
|
|||
|
||||
test('it render popover to select new limit in table', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
@ -203,22 +216,24 @@ describe('Load More Table Component', () => {
|
|||
|
||||
test('it will NOT render popover to select new limit in table if props itemsPerRow is empty', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={[]}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={[]}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="loadingMoreSizeRowPopover"]').exists()).toBeFalsy();
|
||||
|
@ -227,24 +242,26 @@ describe('Load More Table Component', () => {
|
|||
test('It should render a sort icon if sorting is defined', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={sortedHosts}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={jest.fn()}
|
||||
onChange={mockOnChange}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
sorting={{ direction: Direction.asc, field: 'node.host.name' }}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={sortedHosts}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={jest.fn()}
|
||||
onChange={mockOnChange}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
sorting={{ direction: Direction.asc, field: 'node.host.name' }}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.euiTable thead tr th button svg')).toBeTruthy();
|
||||
|
@ -254,22 +271,24 @@ describe('Load More Table Component', () => {
|
|||
describe('Events', () => {
|
||||
test('should call loadmore when clicking on the button load more', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={mockData.Hosts.pageInfo.hasNextPage!}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={1}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
@ -282,22 +301,24 @@ describe('Load More Table Component', () => {
|
|||
|
||||
test('Should call updateLimitPagination when you pick a new limit', () => {
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={getHostsColumns()}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={() => loadMore(mockData.Hosts.pageInfo.endCursor)}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
@ -315,24 +336,26 @@ describe('Load More Table Component', () => {
|
|||
test('Should call onChange when you choose a new sort in the table', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const wrapper = mount(
|
||||
<LoadMoreTable
|
||||
columns={sortedHosts}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={jest.fn()}
|
||||
onChange={mockOnChange}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
sorting={{ direction: Direction.asc, field: 'node.host.name' }}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LoadMoreTable
|
||||
columns={sortedHosts}
|
||||
hasNextPage={true}
|
||||
headerCount={1}
|
||||
headerSupplement={<p>My test supplement.</p>}
|
||||
headerTitle="Hosts"
|
||||
headerTooltip="My test tooltip"
|
||||
headerUnit="Test Unit"
|
||||
itemsPerRow={rowItems}
|
||||
limit={2}
|
||||
loading={false}
|
||||
loadingTitle="Hosts"
|
||||
loadMore={jest.fn()}
|
||||
onChange={mockOnChange}
|
||||
pageOfItems={mockData.Hosts.edges}
|
||||
sorting={{ direction: Direction.asc, field: 'node.host.name' }}
|
||||
updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
|
|
|
@ -267,7 +267,7 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
|
|||
isLoading={loading}
|
||||
onClick={this.props.loadMore}
|
||||
>
|
||||
{loading ? `${i18n.LOADING}...` : i18n.LOAD_MORE}
|
||||
{loading ? `${i18n.LOADING}` : i18n.LOAD_MORE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -296,10 +296,6 @@ export class LoadMoreTable<T, U, V, W, X, Y, Z, AA, AB> extends React.PureCompon
|
|||
}
|
||||
|
||||
export const BasicTableContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const LOADING = i18n.translate('xpack.siem.loadingMoreTable.loadingDescription', {
|
||||
defaultMessage: 'Loading',
|
||||
defaultMessage: 'Loading…',
|
||||
});
|
||||
|
||||
export const LOAD_MORE = i18n.translate('xpack.siem.loadingMoreTable.loadMoreDescription', {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import '../../../mock/match_media';
|
||||
import { encodeIpv6 } from '../../../lib/helpers';
|
||||
import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/host_details';
|
||||
import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details';
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { getHostsUrl, getNetworkUrl, getOverviewUrl, getTimelinesUrl } from '../../link_to';
|
||||
|
||||
|
@ -53,13 +52,6 @@ interface TabNavigationState {
|
|||
selectedTabId: string;
|
||||
}
|
||||
|
||||
const TabNavigationContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-top: -8px;
|
||||
`;
|
||||
|
||||
export class TabNavigation extends React.PureComponent<TabNavigationProps, TabNavigationState> {
|
||||
constructor(props: TabNavigationProps) {
|
||||
super(props);
|
||||
|
@ -81,11 +73,7 @@ export class TabNavigation extends React.PureComponent<TabNavigationProps, TabNa
|
|||
}
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<TabNavigationContainer>
|
||||
<EuiTabs>{this.renderTabs()}</EuiTabs>
|
||||
</TabNavigationContainer>
|
||||
);
|
||||
return <EuiTabs display="condensed">{this.renderTabs()}</EuiTabs>;
|
||||
}
|
||||
|
||||
public mapLocationToTab = (pathname: string) =>
|
||||
|
@ -109,10 +97,10 @@ export class TabNavigation extends React.PureComponent<TabNavigationProps, TabNa
|
|||
<EuiTab
|
||||
data-href={tab.href}
|
||||
data-test-subj={`navigation-${tab.id}`}
|
||||
onClick={() => this.handleTabClick(tab.href, tab.id)}
|
||||
isSelected={this.state.selectedTabId === tab.id}
|
||||
disabled={tab.disabled}
|
||||
isSelected={this.state.selectedTabId === tab.id}
|
||||
key={`navigation-${tab.id}`}
|
||||
onClick={() => this.handleTabClick(tab.href, tab.id)}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const HOSTS = i18n.translate('xpack.siem.hostsTable.hostsTitle', {
|
||||
defaultMessage: 'Hosts',
|
||||
defaultMessage: 'All Hosts',
|
||||
});
|
||||
|
||||
export const UNIT = (totalCount: number) =>
|
||||
|
|
|
@ -116,7 +116,6 @@ class DomainsTableComponent extends React.PureComponent<DomainsTableProps> {
|
|||
headerCount={totalCount}
|
||||
headerSupplement={
|
||||
<FlowDirectionSelect
|
||||
id={DomainsTableId}
|
||||
selectedDirection={flowDirection}
|
||||
onChangeDirection={this.onChangeDomainsDirection}
|
||||
/>
|
||||
|
@ -154,7 +153,7 @@ class DomainsTableComponent extends React.PureComponent<DomainsTableProps> {
|
|||
}
|
||||
};
|
||||
|
||||
private onChangeDomainsDirection = (_: string, flowDirection: FlowDirection) =>
|
||||
private onChangeDomainsDirection = (flowDirection: FlowDirection) =>
|
||||
this.props.updateDomainsDirection({ flowDirection, networkType: this.props.type });
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
});
|
||||
|
||||
describe('Direction', () => {
|
||||
test('when you click on the bi-direction button, it get selected', () => {
|
||||
test('when you click on the bi-directional button, it get selected', () => {
|
||||
const event = {
|
||||
target: { name: 'direction', value: FlowDirection.biDirectional },
|
||||
};
|
||||
|
@ -73,16 +73,18 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
);
|
||||
|
||||
wrapper
|
||||
.find('input[value="biDirectional"]')
|
||||
.find(`[data-test-subj="${FlowDirection.biDirectional}"]`)
|
||||
.first()
|
||||
.simulate('change', event);
|
||||
.simulate('click', event);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`button#${NetworkTopNFlowTableId}-select-flow-direction-biDirectional`)
|
||||
.hasClass('euiButton--fill')
|
||||
.find(`[data-test-subj="${FlowDirection.biDirectional}"]`)
|
||||
.first()
|
||||
.render()
|
||||
.hasClass('euiFilterButton-hasActiveFilters')
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -148,7 +148,6 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
|
|||
|
||||
<EuiFlexItem grow={false}>
|
||||
<FlowDirectionSelect
|
||||
id={NetworkTopNFlowTableId}
|
||||
selectedDirection={flowDirection}
|
||||
onChangeDirection={this.onChangeTopNFlowDirection}
|
||||
/>
|
||||
|
@ -189,7 +188,7 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
|
|||
}
|
||||
};
|
||||
|
||||
private onChangeTopNFlowDirection = (_: string, flowDirection: FlowDirection) =>
|
||||
private onChangeTopNFlowDirection = (flowDirection: FlowDirection) =>
|
||||
this.props.updateTopNFlowDirection({ flowDirection, networkType: this.props.type });
|
||||
}
|
||||
|
||||
|
|
|
@ -4,19 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { manageQuery } from '../../../../components/page/manage_query';
|
||||
import { HeaderPanel } from '../../../header_panel';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import { OverviewHostQuery } from '../../../../containers/overview/overview_host';
|
||||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { OverviewHostStats } from '../overview_host_stats';
|
||||
|
@ -34,26 +28,25 @@ type OverviewHostProps = OwnProps;
|
|||
export const OverviewHost = pure<OverviewHostProps>(({ endDate, startDate, setQuery }) => (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsTitle"
|
||||
defaultMessage="Hosts Ingest Indices"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton href="#/link-to/hosts">
|
||||
<FormattedMessage id="xpack.siem.overview.hostsAction" defaultMessage="View Hosts" />
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
<HeaderPanel
|
||||
border
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsSubtitle"
|
||||
defaultMessage="Showing: Last 24 Hours"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsTitle"
|
||||
defaultMessage="Host Beats Events"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButton href="#/link-to/hosts">
|
||||
<FormattedMessage id="xpack.siem.overview.hostsAction" defaultMessage="View Hosts" />
|
||||
</EuiButton>
|
||||
</HeaderPanel>
|
||||
|
||||
<OverviewHostQuery endDate={endDate} sourceId="default" startDate={startDate}>
|
||||
{({ overviewHost, loading, id, refetch }) => (
|
||||
|
|
|
@ -4,19 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { manageQuery } from '../../../../components/page/manage_query';
|
||||
import { HeaderPanel } from '../../../header_panel';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import { OverviewNetworkQuery } from '../../../../containers/overview/overview_network';
|
||||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { OverviewNetworkStats } from '../overview_network_stats';
|
||||
|
@ -34,29 +28,25 @@ const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats);
|
|||
export const OverviewNetwork = pure<OwnProps>(({ endDate, startDate, setQuery }) => (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkTitle"
|
||||
defaultMessage="Network Ingest Indices"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton href="#/link-to/network/">
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkAction"
|
||||
defaultMessage="View Network"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
<HeaderPanel
|
||||
border
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkSubtitle"
|
||||
defaultMessage="Showing: Last 24 Hours"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkTitle"
|
||||
defaultMessage="Network Beats Events"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiButton href="#/link-to/network/">
|
||||
<FormattedMessage id="xpack.siem.overview.networkAction" defaultMessage="View Network" />
|
||||
</EuiButton>
|
||||
</HeaderPanel>
|
||||
|
||||
<OverviewNetworkQuery endDate={endDate} sourceId="default" startDate={startDate}>
|
||||
{({ overviewNetwork, loading, id, refetch }) => (
|
||||
|
|
|
@ -1,37 +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 { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: 120px;
|
||||
`;
|
||||
|
||||
export interface PageHeadlineProps {
|
||||
children?: React.ReactNode;
|
||||
statType?: string;
|
||||
subtitle?: string | React.ReactNode;
|
||||
title: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export const PageHeadlineComponent = pure<PageHeadlineProps>(({ children, subtitle, title }) => (
|
||||
<FlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="page_headline_title">{title}</h1>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiText color="subdued" size="s">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
|
||||
</FlexGroup>
|
||||
));
|
|
@ -1,123 +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 { shallow } from 'enzyme';
|
||||
import { get } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
|
||||
import { PageHeadlineComponents } from '.';
|
||||
|
||||
type Action = 'PUSH' | 'POP' | 'REPLACE';
|
||||
const pop: Action = 'POP';
|
||||
const getMockProps = (routePath: string) => {
|
||||
const location = {
|
||||
pathname: routePath,
|
||||
search: '',
|
||||
state: '',
|
||||
hash: '',
|
||||
};
|
||||
return {
|
||||
location,
|
||||
match: {
|
||||
isExact: true,
|
||||
params: {},
|
||||
path: '',
|
||||
url: '',
|
||||
},
|
||||
history: {
|
||||
length: 2,
|
||||
location,
|
||||
action: pop,
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
go: jest.fn(),
|
||||
goBack: jest.fn(),
|
||||
goForward: jest.fn(),
|
||||
block: jest.fn(),
|
||||
createHref: jest.fn(),
|
||||
listen: jest.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('SIEM Page Headline', () => {
|
||||
test('Renders hosts headline', () => {
|
||||
const mockProps = getMockProps('/hosts');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(get('props.defaultMessage', pageHeadline)).toEqual('Hosts');
|
||||
});
|
||||
|
||||
test('Renders overview headline', () => {
|
||||
const mockProps = getMockProps('/overview');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(get('props.defaultMessage', pageHeadline)).toEqual('SIEM');
|
||||
});
|
||||
|
||||
test('Renders network headline', () => {
|
||||
const mockProps = getMockProps('/network');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(get('props.defaultMessage', pageHeadline)).toEqual('Network');
|
||||
});
|
||||
|
||||
test('Renders timelines headline', () => {
|
||||
const mockProps = getMockProps('/timelines');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(get('props.defaultMessage', pageHeadline)).toEqual('Timelines');
|
||||
});
|
||||
|
||||
test('Renders host details headline', () => {
|
||||
const mockProps = getMockProps('/hosts/siem-kibana');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(pageHeadline).toEqual('siem-kibana');
|
||||
});
|
||||
|
||||
test('Renders ip overview headline', () => {
|
||||
const mockProps = getMockProps('/network/ip/123.45.678.10');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(pageHeadline).toEqual('123.45.678.10');
|
||||
});
|
||||
|
||||
test('Wacky path renders overview headline', () => {
|
||||
const mockProps = getMockProps('/boogity/woogity');
|
||||
const wrapper = shallow(<PageHeadlineComponents {...mockProps} />);
|
||||
const pageHeadline = wrapper
|
||||
.dive()
|
||||
.find('[data-test-subj="page_headline"]')
|
||||
.props().title;
|
||||
|
||||
expect(get('props.defaultMessage', pageHeadline)).toEqual('SIEM');
|
||||
});
|
||||
});
|
|
@ -1,91 +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 { EuiHorizontalRule } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { pure } from 'recompose';
|
||||
import { LastEventIndexKey } from '../../graphql/types';
|
||||
import { decodeIpv6 } from '../../lib/helpers';
|
||||
import { FlowTargetSelectConnected } from '../page/network/flow_target_select_connected';
|
||||
import { LastEventTime } from '../last_event_time';
|
||||
|
||||
import { PageHeadlineComponent } from './headline';
|
||||
const overviewPageHeadline = {
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.pageSubtitle"
|
||||
defaultMessage="Security Information & Event Management with the Elastic Stack"
|
||||
/>
|
||||
),
|
||||
title: <FormattedMessage id="xpack.siem.overview.pageTitle" defaultMessage="SIEM" />,
|
||||
};
|
||||
|
||||
export const getHeaderForRoute = (pathname: string) => {
|
||||
const trailingPath = pathname.match(/.*\/(.*)$/);
|
||||
if (trailingPath !== null) {
|
||||
const pathSegment = trailingPath[1];
|
||||
switch (pathSegment) {
|
||||
case 'hosts': {
|
||||
return {
|
||||
subtitle: <LastEventTime indexKey={LastEventIndexKey.hosts} />,
|
||||
title: <FormattedMessage id="xpack.siem.hosts.pageTitle" defaultMessage="Hosts" />,
|
||||
};
|
||||
}
|
||||
case 'overview': {
|
||||
return overviewPageHeadline;
|
||||
}
|
||||
case 'network': {
|
||||
return {
|
||||
subtitle: <LastEventTime indexKey={LastEventIndexKey.network} />,
|
||||
title: <FormattedMessage id="xpack.siem.network.pageTitle" defaultMessage="Network" />,
|
||||
};
|
||||
}
|
||||
case 'timelines': {
|
||||
return {
|
||||
subtitle: null,
|
||||
title: (
|
||||
<FormattedMessage id="xpack.siem.timelines.pageTitle" defaultMessage="Timelines" />
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname.match(/hosts\/.*?/)) {
|
||||
const hostId = pathSegment;
|
||||
return {
|
||||
subtitle: <LastEventTime indexKey={LastEventIndexKey.hostDetails} hostName={hostId} />,
|
||||
title: hostId,
|
||||
};
|
||||
}
|
||||
if (pathname.match(/network\/ip\/.*?/)) {
|
||||
const ip = decodeIpv6(pathSegment);
|
||||
return {
|
||||
subtitle: <LastEventTime indexKey={LastEventIndexKey.ipDetails} ip={ip} />,
|
||||
title: ip,
|
||||
children: <FlowTargetSelectConnected />,
|
||||
};
|
||||
}
|
||||
}
|
||||
return overviewPageHeadline;
|
||||
};
|
||||
|
||||
type PageHeadlineComponentProps = RouteComponentProps;
|
||||
|
||||
export const PageHeadlineComponents = pure<PageHeadlineComponentProps>(({ location }) => {
|
||||
return (
|
||||
<>
|
||||
<PageHeadlineComponent
|
||||
data-test-subj="page_headline"
|
||||
{...getHeaderForRoute(location.pathname)}
|
||||
/>
|
||||
<EuiHorizontalRule />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const PageHeadline = withRouter(PageHeadlineComponents);
|
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ADD_DATA = i18n.translate('xpack.siem.appSettings.addData', {
|
||||
defaultMessage: 'Add Data',
|
||||
});
|
||||
|
||||
export const THEME = i18n.translate('xpack.siem.appSettings.theme', {
|
||||
defaultMessage: 'Theme',
|
||||
window.matchMedia = jest.fn().mockImplementation(query => {
|
||||
return {
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
};
|
||||
});
|
|
@ -4,29 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageHeader,
|
||||
EuiPageHeaderSection,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPage, EuiPageBody } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import * as React from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { AppSettings } from '../../components/app_settings';
|
||||
import { AutoSizer } from '../../components/auto_sizer';
|
||||
import { DragDropContextWrapper } from '../../components/drag_and_drop/drag_drop_context_wrapper';
|
||||
import { Flyout, flyoutHeaderHeight } from '../../components/flyout';
|
||||
import { HelpMenu } from '../../components/help_menu';
|
||||
import { LinkToPage } from '../../components/link_to';
|
||||
import { SiemNavigation } from '../../components/navigation';
|
||||
import { PageHeadline } from '../../components/page_headline';
|
||||
import { SuperDatePicker } from '../../components/super_date_picker';
|
||||
import { StatefulTimeline } from '../../components/timeline';
|
||||
import { NotFoundPage } from '../404';
|
||||
import { HostsContainer } from '../hosts';
|
||||
|
@ -38,6 +29,23 @@ const WrappedByAutoSizer = styled.div`
|
|||
height: 100%;
|
||||
`;
|
||||
|
||||
const gutterTimeline = '70px'; // Temporary until timeline is moved - MichaelMarcialis
|
||||
|
||||
const Page = styled(EuiPage)`
|
||||
${({ theme }) => `
|
||||
padding: 0 ${gutterTimeline} ${theme.eui.euiSizeL} ${theme.eui.euiSizeL};
|
||||
`}
|
||||
`;
|
||||
|
||||
const NavGlobal = styled.nav`
|
||||
${({ theme }) => `
|
||||
background: ${theme.eui.euiColorEmptyShade};
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
margin: 0 -${gutterTimeline} 0 -${theme.eui.euiSizeL};
|
||||
padding: ${theme.eui.euiSize} ${gutterTimeline} ${theme.eui.euiSize} ${theme.eui.euiSizeL};
|
||||
`}
|
||||
`;
|
||||
|
||||
const usersViewing = ['elastic']; // TODO: get the users viewing this timeline from Elasticsearch (persistance)
|
||||
|
||||
/** Returns true if we are running with the k7 design */
|
||||
|
@ -58,6 +66,8 @@ export const HomePage = pure(() => (
|
|||
{({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => (
|
||||
<WrappedByAutoSizer data-test-subj="wrapped-by-auto-sizer" innerRef={measureRef}>
|
||||
<Page data-test-subj="pageContainer">
|
||||
<HelpMenu />
|
||||
|
||||
<DragDropContextWrapper>
|
||||
<Flyout
|
||||
flyoutHeight={calculateFlyoutHeight({
|
||||
|
@ -77,28 +87,26 @@ export const HomePage = pure(() => (
|
|||
id="timeline-1"
|
||||
/>
|
||||
</Flyout>
|
||||
|
||||
<EuiPageBody>
|
||||
<PageHeader data-test-subj="pageHeader">
|
||||
<PageHeaderSection>
|
||||
<FixEuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SiemNavigation />
|
||||
<HelpMenu />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" wrap={false} gutterSize="s">
|
||||
<EuiFlexItem grow={false} data-test-subj="datePickerContainer">
|
||||
<SuperDatePicker id="global" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj="appSettingsContainer">
|
||||
<AppSettings />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</FixEuiFlexGroup>
|
||||
</PageHeaderSection>
|
||||
</PageHeader>
|
||||
<PageHeadline />
|
||||
<NavGlobal>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<SiemNavigation />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="add-data"
|
||||
href="kibana#home/tutorial_directory/security"
|
||||
iconType="plusInCircle"
|
||||
>
|
||||
<FormattedMessage id="xpack.siem.global.addData" defaultMessage="Add data" />
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</NavGlobal>
|
||||
|
||||
<Switch>
|
||||
<Redirect from="/" exact={true} to="/overview" />
|
||||
<Route path="/overview" component={Overview} />
|
||||
|
@ -115,27 +123,3 @@ export const HomePage = pure(() => (
|
|||
)}
|
||||
</AutoSizer>
|
||||
));
|
||||
|
||||
const Page = styled(EuiPage)`
|
||||
padding: 0px 70px 24px 24px; // 70px temporary until timeline is moved - MichaelMarcialis
|
||||
`;
|
||||
|
||||
const PageHeader = styled(EuiPageHeader)`
|
||||
background-color: ${props => props.theme.eui.euiColorLightestShade};
|
||||
position: fixed;
|
||||
width: calc(100% - 75px);
|
||||
z-index: 10;
|
||||
padding: 6px 0px 6px 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
`;
|
||||
|
||||
const PageHeaderSection = styled(EuiPageHeaderSection)`
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const FixEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: -6px;
|
||||
`;
|
||||
|
|
|
@ -8,12 +8,16 @@ import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
|||
import { getOr, isEmpty } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { pure } from 'recompose';
|
||||
import chrome, { Breadcrumb } from 'ui/chrome';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { ESTermQuery } from '../../../common/typed_json';
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { LastEventTime } from '../../components/last_event_time';
|
||||
import { getHostsUrl, HostComponentProps } from '../../components/link_to/redirect_to_hosts';
|
||||
import { EventsTable, UncommonProcessTable } from '../../components/page/hosts';
|
||||
import { AuthenticationTable } from '../../components/page/hosts/authentications_table';
|
||||
|
@ -25,7 +29,7 @@ import { GlobalTime } from '../../containers/global_time';
|
|||
import { HostOverviewByNameQuery } from '../../containers/hosts/overview';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { UncommonProcessesQuery } from '../../containers/uncommon_processes';
|
||||
import { IndexType } from '../../graphql/types';
|
||||
import { IndexType, LastEventIndexKey } from '../../graphql/types';
|
||||
import { convertKueryToElasticSearchQuery, escapeQueryValue } from '../../lib/keury';
|
||||
import { hostsModel, hostsSelectors, State } from '../../store';
|
||||
|
||||
|
@ -58,8 +62,17 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
|
|||
<WithSource sourceId="default" indexTypes={indexTypes}>
|
||||
{({ auditbeatIndicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(auditbeatIndicesExist) ? (
|
||||
<>
|
||||
<HostsKql indexPattern={indexPattern} type={type} />
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<HostsKql indexPattern={indexPattern} type={type} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<HeaderPage
|
||||
subtitle={
|
||||
<LastEventTime indexKey={LastEventIndexKey.hostDetails} hostName={hostName} />
|
||||
}
|
||||
title={hostName}
|
||||
/>
|
||||
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
|
@ -81,9 +94,7 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
|
|||
)}
|
||||
</HostOverviewByNameQuery>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiSpacer />
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<AuthenticationsQuery
|
||||
sourceId="default"
|
||||
|
@ -177,7 +188,7 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
|
|||
</>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title={i18n.NO_AUDITBEAT_INDICES}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { mount } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import '../../mock/match_media';
|
||||
import { Hosts } from './hosts';
|
||||
|
||||
import { mocksSource } from '../../containers/source/mock';
|
||||
|
|
|
@ -8,10 +8,14 @@ import { EuiSpacer } from '@elastic/eui';
|
|||
import { getOr } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { pure } from 'recompose';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { LastEventTime } from '../../components/last_event_time';
|
||||
import {
|
||||
EventsTable,
|
||||
HostsTable,
|
||||
|
@ -27,7 +31,7 @@ import { HostsQuery } from '../../containers/hosts';
|
|||
import { KpiHostsQuery } from '../../containers/kpi_hosts';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { UncommonProcessesQuery } from '../../containers/uncommon_processes';
|
||||
import { IndexType } from '../../graphql/types';
|
||||
import { IndexType, LastEventIndexKey } from '../../graphql/types';
|
||||
import { hostsModel, hostsSelectors, State } from '../../store';
|
||||
|
||||
import { HostsKql } from './kql';
|
||||
|
@ -52,8 +56,16 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
|
|||
<WithSource sourceId="default" indexTypes={indexTypes}>
|
||||
{({ auditbeatIndicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(auditbeatIndicesExist) ? (
|
||||
<>
|
||||
<HostsKql indexPattern={indexPattern} type={hostsModel.HostsType.page} />
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<HostsKql indexPattern={indexPattern} type={hostsModel.HostsType.page} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<HeaderPage
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.hosts} />}
|
||||
title={i18n.HOSTS}
|
||||
/>
|
||||
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
<>
|
||||
|
@ -186,7 +198,7 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
|
|||
</>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title={i18n.NO_AUDITBEAT_INDICES}
|
||||
|
|
|
@ -12,7 +12,6 @@ import { AutocompleteField } from '../../components/autocomplete_field';
|
|||
import { HostsFilter } from '../../containers/hosts';
|
||||
import { KueryAutocompletion } from '../../containers/kuery_autocompletion';
|
||||
import { hostsModel } from '../../store';
|
||||
import { PageHeader, PageHeaderSection } from '../styles';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -22,31 +21,27 @@ interface HostsKqlProps {
|
|||
}
|
||||
|
||||
export const HostsKql = pure<HostsKqlProps>(({ indexPattern, type }) => (
|
||||
<PageHeader data-test-subj="paneHeader">
|
||||
<PageHeaderSection>
|
||||
<KueryAutocompletion indexPattern={indexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<HostsFilter indexPattern={indexPattern} type={type}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={i18n.KQL_PLACE_HOLDER}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</HostsFilter>
|
||||
<KueryAutocompletion indexPattern={indexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<HostsFilter indexPattern={indexPattern} type={type}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={i18n.KQL_PLACE_HOLDER}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</KueryAutocompletion>
|
||||
</PageHeaderSection>
|
||||
</PageHeader>
|
||||
</HostsFilter>
|
||||
)}
|
||||
</KueryAutocompletion>
|
||||
));
|
||||
|
|
|
@ -15,7 +15,7 @@ export const NO_AUDITBEAT_INDICES = i18n.translate('xpack.siem.hosts.noAuditBeat
|
|||
});
|
||||
|
||||
export const KQL_PLACE_HOLDER = i18n.translate('xpack.siem.hosts.kqlPlaceHolder', {
|
||||
defaultMessage: 'Search… (e.g. host.name:"foo" AND process.name:"bar")',
|
||||
defaultMessage: 'e.g. host.name: "foo"',
|
||||
});
|
||||
|
||||
export const LETS_ADD_SOME = i18n.translate('xpack.siem.hosts.letsAddSome.description', {
|
||||
|
|
|
@ -8,19 +8,24 @@ import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
|||
import { getOr } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { pure } from 'recompose';
|
||||
import chrome, { Breadcrumb } from 'ui/chrome';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { LastEventTime } from '../../components/last_event_time';
|
||||
import { getNetworkUrl, NetworkComponentProps } from '../../components/link_to/redirect_to_network';
|
||||
import { manageQuery } from '../../components/page/manage_query';
|
||||
import { DomainsTable } from '../../components/page/network/domains_table';
|
||||
import { FlowTargetSelectConnected } from '../../components/page/network/flow_target_select_connected';
|
||||
import { IpOverview } from '../../components/page/network/ip_overview';
|
||||
import { DomainsQuery } from '../../containers/domains';
|
||||
import { GlobalTime } from '../../containers/global_time';
|
||||
import { IpOverviewQuery } from '../../containers/ip_overview';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { FlowTarget, IndexType } from '../../graphql/types';
|
||||
import { FlowTarget, IndexType, LastEventIndexKey } from '../../graphql/types';
|
||||
import { decodeIpv6 } from '../../lib/helpers';
|
||||
import { networkModel, networkSelectors, State } from '../../store';
|
||||
import { TlsTable } from '../../components/page/network/tls_table';
|
||||
|
@ -57,8 +62,17 @@ const IPDetailsComponent = pure<IPDetailsComponentProps>(
|
|||
<WithSource sourceId="default" indexTypes={indexTypes}>
|
||||
{({ filebeatIndicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(filebeatIndicesExist) ? (
|
||||
<>
|
||||
<NetworkKql indexPattern={indexPattern} type={networkModel.NetworkType.details} />
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<NetworkKql indexPattern={indexPattern} type={networkModel.NetworkType.details} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<HeaderPage
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.ipDetails} ip={ip} />}
|
||||
title={ip}
|
||||
>
|
||||
<FlowTargetSelectConnected />
|
||||
</HeaderPage>
|
||||
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
|
@ -79,9 +93,8 @@ const IPDetailsComponent = pure<IPDetailsComponentProps>(
|
|||
/>
|
||||
)}
|
||||
</IpOverviewQuery>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<DomainsQuery
|
||||
endDate={to}
|
||||
|
@ -168,7 +181,7 @@ const IPDetailsComponent = pure<IPDetailsComponentProps>(
|
|||
</>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title={i18n.NO_FILEBEAT_INDICES}
|
||||
|
|
|
@ -12,7 +12,6 @@ import { AutocompleteField } from '../../components/autocomplete_field';
|
|||
import { KueryAutocompletion } from '../../containers/kuery_autocompletion';
|
||||
import { NetworkFilter } from '../../containers/network';
|
||||
import { networkModel } from '../../store';
|
||||
import { PageHeader, PageHeaderSection } from '../styles';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -22,31 +21,27 @@ interface NetworkKqlProps {
|
|||
}
|
||||
|
||||
export const NetworkKql = pure<NetworkKqlProps>(({ indexPattern, type }) => (
|
||||
<PageHeader data-test-subj="paneHeader">
|
||||
<PageHeaderSection>
|
||||
<KueryAutocompletion indexPattern={indexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<NetworkFilter indexPattern={indexPattern} type={type}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={i18n.KQL_PLACE_HOLDER}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</NetworkFilter>
|
||||
<KueryAutocompletion indexPattern={indexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<NetworkFilter indexPattern={indexPattern} type={type}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={i18n.KQL_PLACE_HOLDER}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</KueryAutocompletion>
|
||||
</PageHeaderSection>
|
||||
</PageHeader>
|
||||
</NetworkFilter>
|
||||
)}
|
||||
</KueryAutocompletion>
|
||||
));
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { mount } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import '../../mock/match_media';
|
||||
import { Network } from './network';
|
||||
|
||||
import { mocksSource } from '../../containers/source/mock';
|
||||
|
|
|
@ -8,10 +8,14 @@ import { EuiSpacer } from '@elastic/eui';
|
|||
import { getOr } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { pure } from 'recompose';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { LastEventTime } from '../../components/last_event_time';
|
||||
import { manageQuery } from '../../components/page/manage_query';
|
||||
import { KpiNetworkComponent, NetworkTopNFlowTable } from '../../components/page/network';
|
||||
import { NetworkDnsTable } from '../../components/page/network/network_dns_table';
|
||||
|
@ -20,7 +24,7 @@ import { KpiNetworkQuery } from '../../containers/kpi_network';
|
|||
import { NetworkDnsQuery } from '../../containers/network_dns';
|
||||
import { NetworkTopNFlowQuery } from '../../containers/network_top_n_flow';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { IndexType } from '../../graphql/types';
|
||||
import { IndexType, LastEventIndexKey } from '../../graphql/types';
|
||||
import { networkModel, networkSelectors, State } from '../../store';
|
||||
|
||||
import { NetworkKql } from './kql';
|
||||
|
@ -42,8 +46,15 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
|
|||
<WithSource sourceId="default" indexTypes={indexTypes}>
|
||||
{({ filebeatIndicesExist, indexPattern }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(filebeatIndicesExist) ? (
|
||||
<>
|
||||
<NetworkKql indexPattern={indexPattern} type={networkModel.NetworkType.page} />
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<NetworkKql indexPattern={indexPattern} type={networkModel.NetworkType.page} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<HeaderPage
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
|
||||
title={i18n.NETWORK}
|
||||
/>
|
||||
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
|
@ -118,7 +129,7 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
|
|||
</>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title={i18n.NO_FILEBEAT_INDICES}
|
||||
|
|
|
@ -15,7 +15,7 @@ export const NO_FILEBEAT_INDICES = i18n.translate('xpack.siem.network.noFilebeat
|
|||
});
|
||||
|
||||
export const KQL_PLACE_HOLDER = i18n.translate('xpack.siem.network.kqlPlaceHolder', {
|
||||
defaultMessage: 'Search… (e.g. network.name:"foo" AND process.name:"bar")',
|
||||
defaultMessage: 'e.g. source.ip: "foo"',
|
||||
});
|
||||
|
||||
export const LETS_ADD_SOME = i18n.translate('xpack.siem.network.letsAddSome.description', {
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { OverviewHost } from '../../components/page/overview/overview_host';
|
||||
import { OverviewNetwork } from '../../components/page/overview/overview_network';
|
||||
import { GlobalTime } from '../../containers/global_time';
|
||||
|
@ -24,28 +26,38 @@ const basePath = chrome.getBasePath();
|
|||
|
||||
const indexTypes = [IndexType.FILEBEAT, IndexType.AUDITBEAT, IndexType.PACKETBEAT];
|
||||
|
||||
export const OverviewComponent = pure(() => (
|
||||
<WithSource sourceId="default" indexTypes={indexTypes}>
|
||||
{({ auditbeatIndicesExist, filebeatIndicesExist }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(auditbeatIndicesExist) &&
|
||||
indicesExistOrDataTemporarilyUnavailable(filebeatIndicesExist) ? (
|
||||
<GlobalTime>
|
||||
{({ to, from, setQuery }) => (
|
||||
<EuiFlexGroup>
|
||||
<Summary />
|
||||
<OverviewHost endDate={to} startDate={from} setQuery={setQuery} />
|
||||
<OverviewNetwork endDate={to} startDate={from} setQuery={setQuery} />
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</GlobalTime>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title={i18n.NO_FILEBEAT_INDICES}
|
||||
message={i18n.LETS_ADD_SOME}
|
||||
actionLabel={i18n.SETUP_INSTRUCTIONS}
|
||||
actionUrl={`${basePath}/app/kibana#/home/tutorial_directory/security`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
));
|
||||
export const OverviewComponent = pure(() => {
|
||||
const dateEnd = Date.now();
|
||||
const dateRange = moment.duration(24, 'hours').asMilliseconds();
|
||||
const dateStart = dateEnd - dateRange;
|
||||
|
||||
return (
|
||||
<WithSource sourceId="default" indexTypes={indexTypes}>
|
||||
{({ auditbeatIndicesExist, filebeatIndicesExist }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(auditbeatIndicesExist) &&
|
||||
indicesExistOrDataTemporarilyUnavailable(filebeatIndicesExist) ? (
|
||||
<>
|
||||
<HeaderPage subtitle={i18n.PAGE_SUBTITLE} title={i18n.PAGE_TITLE} />
|
||||
|
||||
<GlobalTime>
|
||||
{({ setQuery }) => (
|
||||
<EuiFlexGroup>
|
||||
<Summary />
|
||||
<OverviewHost endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
<OverviewNetwork endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</GlobalTime>
|
||||
</>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title={i18n.NO_FILEBEAT_INDICES}
|
||||
message={i18n.LETS_ADD_SOME}
|
||||
actionLabel={i18n.SETUP_INSTRUCTIONS}
|
||||
actionUrl={`${basePath}/app/kibana#/home/tutorial_directory/security`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.overview.pageTitle', {
|
||||
defaultMessage: 'SIEM',
|
||||
});
|
||||
|
||||
export const PAGE_SUBTITLE = i18n.translate('xpack.siem.overview.pageSubtitle', {
|
||||
defaultMessage: 'Security Information & Event Management with the Elastic Stack',
|
||||
});
|
||||
|
||||
export const NO_FILEBEAT_INDICES = i18n.translate(
|
||||
'xpack.siem.overview.network.noFilebeatIndicies',
|
||||
{
|
||||
|
|
|
@ -1,22 +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 { EuiPageHeader, EuiPageHeaderSection } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const PageHeader = styled(EuiPageHeader)`
|
||||
background-color: ${props => props.theme.eui.euiColorLightestShade};
|
||||
position: fixed;
|
||||
width: calc(100% - 32px);
|
||||
z-index: 10;
|
||||
padding: 6px 0px 0px 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 50px;
|
||||
`;
|
||||
|
||||
export const PageHeaderSection = styled(EuiPageHeaderSection)`
|
||||
width: 100%;
|
||||
`;
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { StatefulOpenTimeline } from '../../components/open_timeline';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
@ -27,15 +28,19 @@ export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
|
|||
class Timelines extends React.PureComponent<DispatchProps> {
|
||||
public render() {
|
||||
return (
|
||||
<TimelinesContainer>
|
||||
<StatefulOpenTimeline
|
||||
deleteTimelines={this.deleteTimelines}
|
||||
openTimeline={this.openTimeline}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={[]}
|
||||
title={i18n.ALL_TIMELINES}
|
||||
/>
|
||||
</TimelinesContainer>
|
||||
<>
|
||||
<HeaderPage title={i18n.PAGE_TITLE} />
|
||||
|
||||
<TimelinesContainer>
|
||||
<StatefulOpenTimeline
|
||||
deleteTimelines={this.deleteTimelines}
|
||||
openTimeline={this.openTimeline}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
searchResults={[]}
|
||||
title={i18n.ALL_TIMELINES_PANEL_TITLE}
|
||||
/>
|
||||
</TimelinesContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ALL_TIMELINES = i18n.translate('xpack.siem.pages.timeline.allTimelinesTitle', {
|
||||
defaultMessage: 'All Timelines',
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.timelines.pageTitle', {
|
||||
defaultMessage: 'Timelines',
|
||||
});
|
||||
|
||||
export const ALL_TIMELINES_PANEL_TITLE = i18n.translate(
|
||||
'xpack.siem.timelines.allTimelines.panelTitle',
|
||||
{
|
||||
defaultMessage: 'All Timelines',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3626,6 +3626,13 @@
|
|||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-sticky@^6.0.3":
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-sticky/-/react-sticky-6.0.3.tgz#94d16a951467b29ad44c224081d9503e7e590434"
|
||||
integrity sha512-tW0Y1hTr2Tao4yX58iKl0i7BaqrdObGXAzsyzd8VGVrWVEgbQuV6P6QKVd/kFC7FroXyelftiVNJ09pnfkcjww==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-test-renderer@^16.8.0":
|
||||
version "16.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.1.tgz#96f3ce45a3a41c94eca532a99103dd3042c9d055"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue