mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* attempt at getting nav working * fix detection-engine href redirect issue * rough out basic page routing * kql placeholder * update page and panel headers * rough out card table poc styles * change HeaderPanel to HeaderSection * cleanup and unit test updates * rough out utilityBar poc * clean up UtilityBar naming and styles * support popovers in utility bar * refactor icon side * UtilityBar unit tests * remove page tests for now * adjust routes * add comment * cleanup chart * open/closed signals content toggle * remove EuiFilterButton icons * fix misaligned popover button * add split prop for HeaderSection * fleshing out activity monitor table * update global header to include logo * fix tests * correct table types; thanks Garrett! * LinkIcon comp poc * fix bugs, errors, tests * rm import * table cleanup * correct merge errors * switch All Rules to EuiBasicTable * expand table types and values * fleshing out all rules table * rough out rule details * move chart to separate comp * update supplement layout * example rule fail * switch to new discover-style search * add ProgressInline comp * update unit tests and snapshots * cleanup * correct merge weirdness * move text styles to all subtitle items * correct invalid nav markup; update tests; cleanup * fix console errors * add empty page * change to EuiButtonEmpty in HeaderGlobal * overflow popover * rough out edit layout * new WrapperPage comp POC * cleanup * var for timeline gutter * tests and snapshots update * fix type + review + re-arrange code * adding feature flag + fix route issue * fix type with unit test * Removing unused translation
This commit is contained in:
parent
7927447c70
commit
14579af0c5
98 changed files with 4901 additions and 840 deletions
|
@ -5,16 +5,16 @@
|
|||
*/
|
||||
|
||||
/** Top-level (global) navigation link to the `Hosts` page */
|
||||
export const NAVIGATION_HOSTS = '[data-test-subj="navigation-link-hosts"]';
|
||||
export const NAVIGATION_HOSTS = '[data-test-subj="navigation-hosts"]';
|
||||
|
||||
/** Top-level (global) navigation link to the `Network` page */
|
||||
export const NAVIGATION_NETWORK = '[data-test-subj="navigation-link-network"]';
|
||||
export const NAVIGATION_NETWORK = '[data-test-subj="navigation-network"]';
|
||||
|
||||
/** Top-level (global) navigation link to the `Overview` page */
|
||||
export const NAVIGATION_OVERVIEW = '[data-test-subj="navigation-link-overview"]';
|
||||
export const NAVIGATION_OVERVIEW = '[data-test-subj="navigation-overview"]';
|
||||
|
||||
/** Top-level (global) navigation link to the `Timelines` page */
|
||||
export const NAVIGATION_TIMELINES = '[data-test-subj="navigation-link-timelines"]';
|
||||
export const NAVIGATION_TIMELINES = '[data-test-subj="navigation-timelines"]';
|
||||
|
||||
export const HOSTS_PAGE_TABS = {
|
||||
allHosts: '[data-test-subj="navigation-allHosts"]',
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UtilityBar it renders 1`] = `
|
||||
<Component>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>
|
||||
Test text
|
||||
</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction
|
||||
iconType=""
|
||||
popoverContent={
|
||||
<p>
|
||||
Test popover
|
||||
</p>
|
||||
}
|
||||
>
|
||||
Test action
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction
|
||||
iconType="cross"
|
||||
>
|
||||
Test action
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UtilityBarAction it renders 1`] = `
|
||||
<Component>
|
||||
<UtilityBarAction
|
||||
iconType="alert"
|
||||
>
|
||||
Test action
|
||||
</UtilityBarAction>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UtilityBarGroup it renders 1`] = `
|
||||
<Component>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>
|
||||
Test text
|
||||
</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UtilityBarSection it renders 1`] = `
|
||||
<Component>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>
|
||||
Test text
|
||||
</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UtilityBarText it renders 1`] = `
|
||||
<Component>
|
||||
<UtilityBarText>
|
||||
Test text
|
||||
</UtilityBarText>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { UtilityBar } from './utility_bar';
|
||||
export { UtilityBarAction } from './utility_bar_action';
|
||||
export { UtilityBarGroup } from './utility_bar_group';
|
||||
export { UtilityBarSection } from './utility_bar_section';
|
||||
export { UtilityBarText } from './utility_bar_text';
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 styled, { css } from 'styled-components';
|
||||
|
||||
/**
|
||||
* UTILITY BAR
|
||||
*/
|
||||
|
||||
export interface BarProps {
|
||||
border?: boolean;
|
||||
}
|
||||
|
||||
export const Bar = styled.aside.attrs({
|
||||
className: 'siemUtilityBar',
|
||||
})<BarProps>`
|
||||
${({ border, theme }) => css`
|
||||
${border &&
|
||||
css`
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
padding-bottom: ${theme.eui.paddingSizes.s};
|
||||
`}
|
||||
|
||||
@media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
Bar.displayName = 'Bar';
|
||||
|
||||
export const BarSection = styled.div.attrs({
|
||||
className: 'siemUtilityBar__section',
|
||||
})`
|
||||
${({ theme }) => css`
|
||||
& + & {
|
||||
margin-top: ${theme.eui.euiSizeS};
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) {
|
||||
& + & {
|
||||
margin-top: 0;
|
||||
margin-left: ${theme.eui.euiSize};
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
BarSection.displayName = 'BarSection';
|
||||
|
||||
export const BarGroup = styled.div.attrs({
|
||||
className: 'siemUtilityBar__group',
|
||||
})`
|
||||
${({ theme }) => css`
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& + & {
|
||||
margin-top: ${theme.eui.euiSizeS};
|
||||
}
|
||||
|
||||
@media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) {
|
||||
border-right: ${theme.eui.euiBorderThin};
|
||||
flex-wrap: nowrap;
|
||||
margin-right: ${theme.eui.paddingSizes.m};
|
||||
padding-right: ${theme.eui.paddingSizes.m};
|
||||
|
||||
& + & {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-right: ${theme.eui.euiSize};
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
BarGroup.displayName = 'BarGroup';
|
||||
|
||||
export const BarText = styled.p.attrs({
|
||||
className: 'siemUtilityBar__text',
|
||||
})`
|
||||
${({ theme }) => css`
|
||||
color: ${theme.eui.textColors.subdued};
|
||||
font-size: ${theme.eui.euiFontSizeXS};
|
||||
line-height: ${theme.eui.euiLineHeight};
|
||||
white-space: nowrap;
|
||||
`}
|
||||
`;
|
||||
BarText.displayName = 'BarText';
|
||||
|
||||
export const BarAction = styled.div.attrs({
|
||||
className: 'siemUtilityBar__action',
|
||||
})`
|
||||
${({ theme }) => css`
|
||||
font-size: ${theme.eui.euiFontSizeXS};
|
||||
line-height: ${theme.eui.euiLineHeight};
|
||||
`}
|
||||
`;
|
||||
BarAction.displayName = 'BarAction';
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import 'jest-styled-components';
|
||||
import React from 'react';
|
||||
|
||||
import '../../../mock/ui_settings';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import {
|
||||
UtilityBar,
|
||||
UtilityBarAction,
|
||||
UtilityBarGroup,
|
||||
UtilityBarSection,
|
||||
UtilityBarText,
|
||||
} from './index';
|
||||
|
||||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('UtilityBar', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Test text'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="" popoverContent={<p>{'Test popover'}</p>}>
|
||||
{'Test action'}
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="cross">{'Test action'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it applies border styles when border is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<UtilityBar border>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Test text'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="" popoverContent={<p>{'Test popover'}</p>}>
|
||||
{'Test action'}
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="cross">{'Test action'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
</TestProviders>
|
||||
);
|
||||
const siemUtilityBar = wrapper.find('.siemUtilityBar').first();
|
||||
|
||||
expect(siemUtilityBar).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemUtilityBar).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.s);
|
||||
});
|
||||
|
||||
test('it DOES NOT apply border styles when border is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Test text'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="" popoverContent={<p>{'Test popover'}</p>}>
|
||||
{'Test action'}
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="cross">{'Test action'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
</TestProviders>
|
||||
);
|
||||
const siemUtilityBar = wrapper.find('.siemUtilityBar').first();
|
||||
|
||||
expect(siemUtilityBar).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemUtilityBar).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.s);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Bar, BarProps } from './styles';
|
||||
|
||||
export interface UtilityBarProps extends BarProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const UtilityBar = React.memo<UtilityBarProps>(({ border, children }) => (
|
||||
<Bar border={border}>{children}</Bar>
|
||||
));
|
||||
UtilityBar.displayName = 'UtilityBar';
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 'jest-styled-components';
|
||||
import React from 'react';
|
||||
|
||||
import '../../../mock/ui_settings';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import { UtilityBarAction } from './index';
|
||||
|
||||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('UtilityBarAction', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<UtilityBarAction iconType="alert">{'Test action'}</UtilityBarAction>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders a popover', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<UtilityBarAction iconType="alert" popoverContent={<p>{'Test popover'}</p>}>
|
||||
{'Test action'}
|
||||
</UtilityBarAction>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiPopover')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { EuiPopover } from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { LinkIcon, LinkIconProps } from '../../link_icon';
|
||||
import { BarAction } from './styles';
|
||||
|
||||
const Popover = React.memo<UtilityBarActionProps>(
|
||||
({ children, color, iconSide, iconSize, iconType, popoverContent }) => {
|
||||
const [popoverState, setPopoverState] = useState(false);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<LinkIcon
|
||||
color={color}
|
||||
iconSide={iconSide}
|
||||
iconSize={iconSize}
|
||||
iconType={iconType}
|
||||
onClick={() => setPopoverState(!popoverState)}
|
||||
>
|
||||
{children}
|
||||
</LinkIcon>
|
||||
}
|
||||
closePopover={() => setPopoverState(false)}
|
||||
isOpen={popoverState}
|
||||
>
|
||||
{popoverContent}
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
);
|
||||
Popover.displayName = 'Popover';
|
||||
|
||||
export interface UtilityBarActionProps extends LinkIconProps {
|
||||
popoverContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const UtilityBarAction = React.memo<UtilityBarActionProps>(
|
||||
({ children, color, href, iconSide, iconSize, iconType, onClick, popoverContent }) => (
|
||||
<BarAction>
|
||||
{popoverContent ? (
|
||||
<Popover
|
||||
color={color}
|
||||
iconSide={iconSide}
|
||||
iconSize={iconSize}
|
||||
iconType={iconType}
|
||||
popoverContent={popoverContent}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
) : (
|
||||
<LinkIcon
|
||||
color={color}
|
||||
href={href}
|
||||
iconSide={iconSide}
|
||||
iconSize={iconSize}
|
||||
iconType={iconType}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</LinkIcon>
|
||||
)}
|
||||
</BarAction>
|
||||
)
|
||||
);
|
||||
UtilityBarAction.displayName = 'UtilityBarAction';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import React from 'react';
|
||||
|
||||
import '../../../mock/ui_settings';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import { UtilityBarGroup, UtilityBarText } from './index';
|
||||
|
||||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('UtilityBarGroup', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Test text'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { BarGroup } from './styles';
|
||||
|
||||
export interface UtilityBarGroupProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const UtilityBarGroup = React.memo<UtilityBarGroupProps>(({ children }) => (
|
||||
<BarGroup>{children}</BarGroup>
|
||||
));
|
||||
UtilityBarGroup.displayName = 'UtilityBarGroup';
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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/ui_settings';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import { UtilityBarGroup, UtilityBarSection, UtilityBarText } from './index';
|
||||
|
||||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('UtilityBarSection', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Test text'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { BarSection } from './styles';
|
||||
|
||||
export interface UtilityBarSectionProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const UtilityBarSection = React.memo<UtilityBarSectionProps>(({ children }) => (
|
||||
<BarSection>{children}</BarSection>
|
||||
));
|
||||
UtilityBarSection.displayName = 'UtilityBarSection';
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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/ui_settings';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import { UtilityBarText } from './index';
|
||||
|
||||
jest.mock('../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('UtilityBarText', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<UtilityBarText>{'Test text'}</UtilityBarText>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { BarText } from './styles';
|
||||
|
||||
export interface UtilityBarTextProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
export const UtilityBarText = React.memo<UtilityBarTextProps>(({ children }) => (
|
||||
<BarText>{children}</BarText>
|
||||
));
|
||||
UtilityBarText.displayName = 'UtilityBarText';
|
|
@ -52,7 +52,7 @@ describe('EventsViewer', () => {
|
|||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="header-panel-subtitle"]`)
|
||||
.find(`[data-test-subj="header-section-subtitle"]`)
|
||||
.first()
|
||||
.text()
|
||||
).toEqual('Showing: 12 events');
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Direction } from '../../graphql/types';
|
|||
import { useKibanaCore } from '../../lib/compose/kibana_core';
|
||||
import { KqlMode } from '../../store/timeline/model';
|
||||
import { AutoSizer } from '../auto_sizer';
|
||||
import { HeaderPanel } from '../header_panel';
|
||||
import { HeaderSection } from '../header_section';
|
||||
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
|
||||
import { defaultHeaders } from '../timeline/body/column_headers/default_headers';
|
||||
import { Sort } from '../timeline/body/sort';
|
||||
|
@ -130,7 +130,7 @@ export const EventsViewer = React.memo<Props>(
|
|||
totalCount = 0,
|
||||
}) => (
|
||||
<>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
id={id}
|
||||
showInspect={showInspect}
|
||||
subtitle={`${i18n.SHOWING}: ${totalCount.toLocaleString()} ${i18n.UNIT(
|
||||
|
|
|
@ -10,23 +10,22 @@ import { Sticky } from 'react-sticky';
|
|||
import { pure } from 'recompose';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { gutterTimeline } from '../../lib/helpers';
|
||||
|
||||
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 }>`
|
||||
const Wrapper = styled.aside<{ isSticky?: boolean }>`
|
||||
${props => css`
|
||||
position: relative;
|
||||
z-index: ${props.theme.eui.euiZNavigation};
|
||||
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
|
||||
};
|
||||
padding: ${props.theme.eui.paddingSizes.m} ${gutterTimeline} ${
|
||||
props.theme.eui.paddingSizes.m
|
||||
} ${props.theme.eui.paddingSizes.l};
|
||||
|
||||
${props.isSticky &&
|
||||
`
|
||||
|
@ -39,8 +38,7 @@ const Aside = styled.aside<{ isSticky?: boolean }>`
|
|||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
Aside.displayName = 'Aside';
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
export interface FiltersGlobalProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -49,11 +47,10 @@ export interface FiltersGlobalProps {
|
|||
export const FiltersGlobal = pure<FiltersGlobalProps>(({ children }) => (
|
||||
<Sticky disableCompensation={disableStickyMq.matches} topOffset={-offsetChrome}>
|
||||
{({ style, isSticky }) => (
|
||||
<Aside isSticky={isSticky} style={style}>
|
||||
<Wrapper className="siemFiltersGlobal" isSticky={isSticky} style={style}>
|
||||
{children}
|
||||
</Aside>
|
||||
</Wrapper>
|
||||
)}
|
||||
</Sticky>
|
||||
));
|
||||
|
||||
FiltersGlobal.displayName = 'FiltersGlobal';
|
||||
|
|
7
x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap
generated
Normal file
7
x-pack/legacy/plugins/siem/public/components/header_global/__snapshots__/index.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HeaderGlobal it renders 1`] = `
|
||||
<Component>
|
||||
<HeaderGlobal />
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { TestProviders } from '../../mock';
|
||||
import '../../mock/match_media';
|
||||
import '../../mock/ui_settings';
|
||||
import { HeaderGlobal } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar
|
||||
jest.mock('../search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
}));
|
||||
|
||||
describe('HeaderGlobal', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<HeaderGlobal />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import { pickBy } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { gutterTimeline } from '../../lib/helpers';
|
||||
import { navTabs } from '../../pages/home/home_navigations';
|
||||
import { SiemPageName } from '../../pages/home/types';
|
||||
import { getOverviewUrl } from '../link_to';
|
||||
import { MlPopover } from '../ml_popover/ml_popover';
|
||||
import { SiemNavigation } from '../navigation';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const Wrapper = styled.header`
|
||||
${({ theme }) => css`
|
||||
background: ${theme.eui.euiColorEmptyShade};
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
padding: ${theme.eui.paddingSizes.m} ${gutterTimeline} ${theme.eui.paddingSizes.m}
|
||||
${theme.eui.paddingSizes.l};
|
||||
`}
|
||||
`;
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
const FlexItem = styled(EuiFlexItem)`
|
||||
min-width: 0;
|
||||
`;
|
||||
FlexItem.displayName = 'FlexItem';
|
||||
|
||||
interface HeaderGlobalProps {
|
||||
hideDetectionEngine?: boolean;
|
||||
}
|
||||
export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine = true }) => (
|
||||
<Wrapper className="siemHeaderGlobal">
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" wrap>
|
||||
<FlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<FlexItem grow={false}>
|
||||
<EuiLink href={getOverviewUrl()}>
|
||||
<EuiIcon aria-label={i18n.SIEM} type="securityAnalyticsApp" size="l" />
|
||||
</EuiLink>
|
||||
</FlexItem>
|
||||
|
||||
<FlexItem component="nav">
|
||||
<SiemNavigation
|
||||
display="condensed"
|
||||
navTabs={
|
||||
hideDetectionEngine
|
||||
? pickBy((value, key) => key !== SiemPageName.detectionEngine, navTabs)
|
||||
: navTabs
|
||||
}
|
||||
/>
|
||||
</FlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlexItem>
|
||||
|
||||
<FlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap>
|
||||
<FlexItem grow={false}>
|
||||
<MlPopover />
|
||||
</FlexItem>
|
||||
|
||||
<FlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="add-data"
|
||||
href="kibana#home/tutorial_directory/siem"
|
||||
iconType="plusInCircle"
|
||||
>
|
||||
{i18n.BUTTON_ADD_DATA}
|
||||
</EuiButtonEmpty>
|
||||
</FlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Wrapper>
|
||||
));
|
||||
HeaderGlobal.displayName = 'HeaderGlobal';
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const SIEM = i18n.translate('xpack.siem.headerGlobal.siem', {
|
||||
defaultMessage: 'SIEM',
|
||||
});
|
||||
|
||||
export const BUTTON_ADD_DATA = i18n.translate('xpack.siem.headerGlobal.buttonAddData', {
|
||||
defaultMessage: 'Add data',
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rendering renders correctly 1`] = `
|
||||
<Component
|
||||
badgeLabel="Beta"
|
||||
badgeTooltip="My test tooltip."
|
||||
subtitle="My Test Subtitle"
|
||||
title="My Test Title"
|
||||
>
|
||||
<p>
|
||||
My test supplement.
|
||||
</p>
|
||||
</Component>
|
||||
`;
|
23
x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap
generated
Normal file
23
x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HeaderPage it renders 1`] = `
|
||||
<Component>
|
||||
<HeaderPage
|
||||
badgeOptions={
|
||||
Object {
|
||||
"beta": true,
|
||||
"text": "Beta",
|
||||
"tooltip": "Test tooltip",
|
||||
}
|
||||
}
|
||||
border={true}
|
||||
subtitle="Test subtitle"
|
||||
subtitle2="Test subtitle 2"
|
||||
title="Test title"
|
||||
>
|
||||
<p>
|
||||
Test supplement
|
||||
</p>
|
||||
</HeaderPage>
|
||||
</Component>
|
||||
`;
|
|
@ -1,42 +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 toJson from 'enzyme-to-json';
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderPage } from './index';
|
||||
|
||||
describe('rendering', () => {
|
||||
test('renders correctly', () => {
|
||||
const wrapper = shallow(
|
||||
<HeaderPage
|
||||
badgeLabel="Beta"
|
||||
badgeTooltip="My test tooltip."
|
||||
subtitle="My Test Subtitle"
|
||||
title="My Test Title"
|
||||
>
|
||||
<p>{'My test supplement.'}</p>
|
||||
</HeaderPage>
|
||||
);
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
test('renders as a draggable when provided arguments', () => {
|
||||
const wrapper = shallow(
|
||||
<HeaderPage
|
||||
badgeLabel="Beta"
|
||||
badgeTooltip="My test tooltip."
|
||||
subtitle="My Test Subtitle"
|
||||
title="My Test Title"
|
||||
draggableArguments={{ field: 'neat', value: 'cool' }}
|
||||
>
|
||||
<p>{'My test supplement.'}</p>
|
||||
</HeaderPage>
|
||||
);
|
||||
const draggableHeader = wrapper.dive().find('[data-test-subj="page_headline_draggable"]');
|
||||
expect(draggableHeader.exists()).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,78 +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 { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
import { DefaultDraggable } from '../draggables';
|
||||
|
||||
const Header = styled.header`
|
||||
${({ theme }) => `
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
padding-bottom: ${theme.eui.euiSizeL};
|
||||
margin: ${theme.eui.euiSizeL} 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
Header.displayName = 'Header';
|
||||
|
||||
interface DraggableArguments {
|
||||
field: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface HeaderPageProps {
|
||||
badgeLabel?: string;
|
||||
badgeTooltip?: string;
|
||||
children?: React.ReactNode;
|
||||
draggableArguments?: DraggableArguments;
|
||||
subtitle?: string | React.ReactNode;
|
||||
title: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export const HeaderPage = pure<HeaderPageProps>(
|
||||
({ badgeLabel, badgeTooltip, children, draggableArguments, subtitle, title, ...rest }) => (
|
||||
<Header {...rest}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="page_headline_title">
|
||||
{!draggableArguments ? (
|
||||
title
|
||||
) : (
|
||||
<DefaultDraggable
|
||||
data-test-subj="page_headline_draggable"
|
||||
id={`header-page-draggable-${draggableArguments.field}-${draggableArguments.value}`}
|
||||
field={draggableArguments.field}
|
||||
value={`${draggableArguments.value}`}
|
||||
/>
|
||||
)}
|
||||
{badgeLabel && (
|
||||
<>
|
||||
{' '}
|
||||
<EuiBetaBadge
|
||||
label={badgeLabel}
|
||||
tooltipContent={badgeTooltip}
|
||||
tooltipPosition="bottom"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiText color="subdued" size="xs">
|
||||
{subtitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && <EuiFlexItem grow={false}>{children}</EuiFlexItem>}
|
||||
</EuiFlexGroup>
|
||||
</Header>
|
||||
)
|
||||
);
|
||||
|
||||
HeaderPage.displayName = 'HeaderPage';
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import 'jest-styled-components';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../mock';
|
||||
import '../../mock/ui_settings';
|
||||
import { HeaderPage } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('HeaderPage', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<HeaderPage
|
||||
badgeOptions={{ beta: true, text: 'Beta', tooltip: 'Test tooltip' }}
|
||||
border
|
||||
subtitle="Test subtitle"
|
||||
subtitle2="Test subtitle 2"
|
||||
title="Test title"
|
||||
>
|
||||
<p>{'Test supplement'}</p>
|
||||
</HeaderPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders the title', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-title"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the back link when provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage backOptions={{ href: '#', text: 'Test link' }} title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.siemHeaderPage__linkBack')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render the back link when not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.siemHeaderPage__linkBack')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('it renders the first subtitle when provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage subtitle="Test subtitle" title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-subtitle"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render the first subtitle when not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-section-subtitle"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('it renders the second subtitle when provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage subtitle2="Test subtitle 2" title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-subtitle-2"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render the second subtitle when not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-section-subtitle-2"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('it renders supplements when children provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title">
|
||||
<p>{'Test supplement'}</p>
|
||||
</HeaderPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-supplements"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render supplements when children not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-supplements"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('it applies border styles when border is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage border title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
const siemHeaderPage = wrapper.find('.siemHeaderPage').first();
|
||||
|
||||
expect(siemHeaderPage).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemHeaderPage).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l);
|
||||
});
|
||||
|
||||
test('it DOES NOT apply border styles when border is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
const siemHeaderPage = wrapper.find('.siemHeaderPage').first();
|
||||
|
||||
expect(siemHeaderPage).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemHeaderPage).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l);
|
||||
});
|
||||
|
||||
test('it renders as a draggable when arguments provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage draggableArguments={{ field: 'neat', value: 'cool' }} title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-draggable"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render as a draggable when arguments not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPage title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-page-draggable"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
|
@ -4,4 +4,143 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { HeaderPage } from './header_page';
|
||||
import { EuiBetaBadge, EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { DefaultDraggable } from '../draggables';
|
||||
import { LinkIcon, LinkIconProps } from '../link_icon';
|
||||
import { Subtitle, SubtitleProps } from '../subtitle';
|
||||
|
||||
interface HeaderProps {
|
||||
border?: boolean;
|
||||
}
|
||||
|
||||
const Header = styled.header.attrs({
|
||||
className: 'siemHeaderPage',
|
||||
})<HeaderProps>`
|
||||
${({ border, theme }) => css`
|
||||
margin-bottom: ${theme.eui.euiSizeL};
|
||||
|
||||
${border &&
|
||||
css`
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
padding-bottom: ${theme.eui.paddingSizes.l};
|
||||
`}
|
||||
`}
|
||||
`;
|
||||
Header.displayName = 'Header';
|
||||
|
||||
const FlexItem = styled(EuiFlexItem)`
|
||||
display: block;
|
||||
`;
|
||||
FlexItem.displayName = 'FlexItem';
|
||||
|
||||
const LinkBack = styled.div.attrs({
|
||||
className: 'siemHeaderPage__linkBack',
|
||||
})`
|
||||
${({ theme }) => css`
|
||||
font-size: ${theme.eui.euiFontSizeXS};
|
||||
line-height: ${theme.eui.euiLineHeight};
|
||||
margin-bottom: ${theme.eui.euiSizeS};
|
||||
`}
|
||||
`;
|
||||
LinkBack.displayName = 'LinkBack';
|
||||
|
||||
const Badge = styled(EuiBadge)`
|
||||
letter-spacing: 0;
|
||||
`;
|
||||
Badge.displayName = 'Badge';
|
||||
|
||||
interface BackOptions {
|
||||
href: LinkIconProps['href'];
|
||||
text: LinkIconProps['children'];
|
||||
}
|
||||
|
||||
interface BadgeOptions {
|
||||
beta?: boolean;
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
interface DraggableArguments {
|
||||
field: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface HeaderPageProps extends HeaderProps {
|
||||
backOptions?: BackOptions;
|
||||
badgeOptions?: BadgeOptions;
|
||||
children?: React.ReactNode;
|
||||
draggableArguments?: DraggableArguments;
|
||||
subtitle?: SubtitleProps['items'];
|
||||
subtitle2?: SubtitleProps['items'];
|
||||
title: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export const HeaderPage = React.memo<HeaderPageProps>(
|
||||
({
|
||||
backOptions,
|
||||
badgeOptions,
|
||||
border,
|
||||
children,
|
||||
draggableArguments,
|
||||
subtitle,
|
||||
subtitle2,
|
||||
title,
|
||||
...rest
|
||||
}) => (
|
||||
<Header border={border} {...rest}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<FlexItem>
|
||||
{backOptions && (
|
||||
<LinkBack>
|
||||
<LinkIcon href={backOptions.href} iconType="arrowLeft">
|
||||
{backOptions.text}
|
||||
</LinkIcon>
|
||||
</LinkBack>
|
||||
)}
|
||||
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="header-page-title">
|
||||
{!draggableArguments ? (
|
||||
title
|
||||
) : (
|
||||
<DefaultDraggable
|
||||
data-test-subj="header-page-draggable"
|
||||
id={`header-page-draggable-${draggableArguments.field}-${draggableArguments.value}`}
|
||||
field={draggableArguments.field}
|
||||
value={`${draggableArguments.value}`}
|
||||
/>
|
||||
)}
|
||||
{badgeOptions && (
|
||||
<>
|
||||
{' '}
|
||||
{badgeOptions.beta ? (
|
||||
<EuiBetaBadge
|
||||
label={badgeOptions.text}
|
||||
tooltipContent={badgeOptions.tooltip}
|
||||
tooltipPosition="bottom"
|
||||
/>
|
||||
) : (
|
||||
<Badge color="hollow">{badgeOptions.text}</Badge>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
|
||||
{subtitle && <Subtitle data-test-subj="header-page-subtitle" items={subtitle} />}
|
||||
{subtitle2 && <Subtitle data-test-subj="header-page-subtitle-2" items={subtitle2} />}
|
||||
</FlexItem>
|
||||
|
||||
{children && (
|
||||
<FlexItem data-test-subj="header-page-supplements" grow={false}>
|
||||
{children}
|
||||
</FlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</Header>
|
||||
)
|
||||
);
|
||||
HeaderPage.displayName = 'HeaderPage';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HeaderPanel it renders 1`] = `
|
||||
exports[`HeaderSection it renders 1`] = `
|
||||
<Component>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
title="Test title"
|
||||
/>
|
||||
</Component>
|
|
@ -10,17 +10,17 @@ import toJson from 'enzyme-to-json';
|
|||
import 'jest-styled-components';
|
||||
import React from 'react';
|
||||
|
||||
import '../../mock/ui_settings';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { HeaderPanel } from './index';
|
||||
import '../../mock/ui_settings';
|
||||
import { HeaderSection } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('HeaderPanel', () => {
|
||||
describe('HeaderSection', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" />
|
||||
<HeaderSection title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -30,13 +30,13 @@ describe('HeaderPanel', () => {
|
|||
test('it renders the title', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" />
|
||||
<HeaderSection title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-panel-title"]')
|
||||
.find('[data-test-subj="header-section-title"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
@ -45,13 +45,13 @@ describe('HeaderPanel', () => {
|
|||
test('it renders the subtitle when provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel subtitle="Test subtitle" title="Test title" />
|
||||
<HeaderSection subtitle="Test subtitle" title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="header-panel-subtitle"]`)
|
||||
.find('[data-test-subj="header-section-subtitle"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
@ -60,13 +60,13 @@ describe('HeaderPanel', () => {
|
|||
test('it DOES NOT render the subtitle when not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" />
|
||||
<HeaderSection title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="header-panel-subtitle"]`)
|
||||
.find('[data-test-subj="header-section-subtitle"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
|
@ -75,13 +75,13 @@ describe('HeaderPanel', () => {
|
|||
test('it renders a transparent inspect button when showInspect is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" id="test" showInspect={false} />
|
||||
<HeaderSection title="Test title" id="test" showInspect={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="transparent-inspect-container"]`)
|
||||
.find('[data-test-subj="transparent-inspect-container"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
@ -90,13 +90,13 @@ describe('HeaderPanel', () => {
|
|||
test('it renders an opaque inspect button when showInspect is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" id="test" showInspect={true} />
|
||||
<HeaderSection title="Test title" id="test" showInspect={true} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="opaque-inspect-container"]`)
|
||||
.find('[data-test-subj="opaque-inspect-container"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
@ -105,15 +105,15 @@ describe('HeaderPanel', () => {
|
|||
test('it renders supplements when children provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title">
|
||||
<HeaderSection title="Test title">
|
||||
<p>{'Test children'}</p>
|
||||
</HeaderPanel>
|
||||
</HeaderSection>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-panel-supplements"]')
|
||||
.find('[data-test-subj="header-section-supplements"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
@ -122,13 +122,13 @@ describe('HeaderPanel', () => {
|
|||
test('it DOES NOT render supplements when children not provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" />
|
||||
<HeaderSection title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-panel-supplements"]')
|
||||
.find('[data-test-subj="header-section-supplements"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
|
@ -137,24 +137,58 @@ describe('HeaderPanel', () => {
|
|||
test('it applies border styles when border is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel border title="Test title" />
|
||||
<HeaderSection border title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
const siemHeaderPanel = wrapper.find('.siemHeaderPanel').first();
|
||||
const siemHeaderSection = wrapper.find('.siemHeaderSection').first();
|
||||
|
||||
expect(siemHeaderPanel).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemHeaderPanel).toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeL);
|
||||
expect(siemHeaderSection).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemHeaderSection).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l);
|
||||
});
|
||||
|
||||
test('it DOES NOT apply border styles when border is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderPanel title="Test title" />
|
||||
<HeaderSection title="Test title" />
|
||||
</TestProviders>
|
||||
);
|
||||
const siemHeaderPanel = wrapper.find('.siemHeaderPanel').first();
|
||||
const siemHeaderSection = wrapper.find('.siemHeaderSection').first();
|
||||
|
||||
expect(siemHeaderPanel).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemHeaderPanel).not.toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeL);
|
||||
expect(siemHeaderSection).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin);
|
||||
expect(siemHeaderSection).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l);
|
||||
});
|
||||
|
||||
test('it splits the title and supplement areas evenly when split is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderSection split title="Test title">
|
||||
<p>{'Test children'}</p>
|
||||
</HeaderSection>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiFlexItem--flexGrowZero[data-test-subj="header-section-supplements"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('it DOES NOT split the title and supplement areas evenly when split is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderSection title="Test title">
|
||||
<p>{'Test children'}</p>
|
||||
</HeaderSection>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiFlexItem--flexGrowZero[data-test-subj="header-section-supplements"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
|
@ -4,51 +4,52 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { InspectButton } from '../inspect';
|
||||
import { Subtitle } from '../subtitle';
|
||||
|
||||
interface HeaderProps {
|
||||
border?: boolean;
|
||||
}
|
||||
|
||||
const Header = styled.header.attrs({
|
||||
className: 'siemHeaderPanel',
|
||||
className: 'siemHeaderSection',
|
||||
})<HeaderProps>`
|
||||
${props => css`
|
||||
margin-bottom: ${props.theme.eui.euiSizeL};
|
||||
${({ border, theme }) => css`
|
||||
margin-bottom: ${theme.eui.euiSizeL};
|
||||
user-select: text;
|
||||
|
||||
${props.border &&
|
||||
`
|
||||
border-bottom: ${props.theme.eui.euiBorderThin};
|
||||
padding-bottom: ${props.theme.eui.euiSizeL};
|
||||
`}
|
||||
${border &&
|
||||
css`
|
||||
border-bottom: ${theme.eui.euiBorderThin};
|
||||
padding-bottom: ${theme.eui.paddingSizes.l};
|
||||
`}
|
||||
`}
|
||||
`;
|
||||
|
||||
Header.displayName = 'Header';
|
||||
|
||||
export interface HeaderPanelProps extends HeaderProps {
|
||||
export interface HeaderSectionProps extends HeaderProps {
|
||||
children?: React.ReactNode;
|
||||
id?: string;
|
||||
split?: boolean;
|
||||
subtitle?: string | React.ReactNode;
|
||||
showInspect?: boolean;
|
||||
title: string | React.ReactNode;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const HeaderPanel = React.memo<HeaderPanelProps>(
|
||||
({ border, children, id, showInspect = false, subtitle, title, tooltip }) => (
|
||||
export const HeaderSection = React.memo<HeaderSectionProps>(
|
||||
({ border, children, id, showInspect = false, split, subtitle, title, tooltip }) => (
|
||||
<Header border={border}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="header-panel-title">
|
||||
<h2 data-test-subj="header-section-title">
|
||||
{title}
|
||||
{tooltip && (
|
||||
<>
|
||||
|
@ -59,11 +60,7 @@ export const HeaderPanel = React.memo<HeaderPanelProps>(
|
|||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
{subtitle && (
|
||||
<EuiText color="subdued" data-test-subj="header-panel-subtitle" size="xs">
|
||||
<p>{subtitle}</p>
|
||||
</EuiText>
|
||||
)}
|
||||
{subtitle && <Subtitle data-test-subj="header-section-subtitle" items={subtitle} />}
|
||||
</EuiFlexItem>
|
||||
|
||||
{id && (
|
||||
|
@ -75,7 +72,7 @@ export const HeaderPanel = React.memo<HeaderPanelProps>(
|
|||
</EuiFlexItem>
|
||||
|
||||
{children && (
|
||||
<EuiFlexItem data-test-subj="header-panel-supplements" grow={false}>
|
||||
<EuiFlexItem data-test-subj="header-section-supplements" grow={split ? true : false}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -83,5 +80,4 @@ export const HeaderPanel = React.memo<HeaderPanelProps>(
|
|||
</Header>
|
||||
)
|
||||
);
|
||||
|
||||
HeaderPanel.displayName = 'HeaderPanel';
|
||||
HeaderSection.displayName = 'HeaderSection';
|
14
x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap
generated
Normal file
14
x-pack/legacy/plugins/siem/public/components/link_icon/__snapshots__/index.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LinkIcon it renders 1`] = `
|
||||
<Component>
|
||||
<LinkIcon
|
||||
href="#"
|
||||
iconSide="right"
|
||||
iconSize="xxl"
|
||||
iconType="alert"
|
||||
>
|
||||
Test link
|
||||
</LinkIcon>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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 'jest-styled-components';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../mock';
|
||||
import '../../mock/ui_settings';
|
||||
import { LinkIcon } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('LinkIcon', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<LinkIcon href="#" iconSide="right" iconSize="xxl" iconType="alert">
|
||||
{'Test link'}
|
||||
</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders an action button when onClick is provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<LinkIcon iconType="alert" onClick={() => alert('Test alert')}>
|
||||
{'Test link'}
|
||||
</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders an action link when href is provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<LinkIcon href="#" iconType="alert">
|
||||
{'Test link'}
|
||||
</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('a')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders an icon', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<LinkIcon iconType="alert">{'Test link'}</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.euiIcon')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it positions the icon to the right when iconSide is right', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<LinkIcon iconSide="right" iconType="alert">
|
||||
{'Test link'}
|
||||
</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemLinkIcon').first()).toHaveStyleRule('flex-direction', 'row-reverse');
|
||||
});
|
||||
|
||||
test('it positions the icon to the left when iconSide is left (or not provided)', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<LinkIcon iconSide="left" iconType="alert">
|
||||
{'Test link'}
|
||||
</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemLinkIcon').first()).not.toHaveStyleRule(
|
||||
'flex-direction',
|
||||
'row-reverse'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders a label', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<LinkIcon iconType="alert">{'Test link'}</LinkIcon>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('.siemLinkIcon__label')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { EuiIcon, EuiLink, IconSize, IconType } from '@elastic/eui';
|
||||
import { LinkAnchorProps } from '@elastic/eui/src/components/link/link';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
interface LinkProps {
|
||||
color?: LinkAnchorProps['color'];
|
||||
href?: string;
|
||||
iconSide?: 'left' | 'right';
|
||||
onClick?: Function;
|
||||
}
|
||||
|
||||
const Link = styled(({ iconSide, children, ...rest }) => <EuiLink {...rest}>{children}</EuiLink>)<
|
||||
LinkProps
|
||||
>`
|
||||
${({ iconSide, theme }) => css`
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
|
||||
${iconSide === 'left' &&
|
||||
css`
|
||||
.euiIcon {
|
||||
margin-right: ${theme.eui.euiSizeXS};
|
||||
}
|
||||
`}
|
||||
|
||||
${iconSide === 'right' &&
|
||||
css`
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.euiIcon {
|
||||
margin-left: ${theme.eui.euiSizeXS};
|
||||
}
|
||||
`}
|
||||
`}
|
||||
`;
|
||||
Link.displayName = 'Link';
|
||||
|
||||
export interface LinkIconProps extends LinkProps {
|
||||
children: string;
|
||||
iconSize?: IconSize;
|
||||
iconType: IconType;
|
||||
}
|
||||
|
||||
export const LinkIcon = React.memo<LinkIconProps>(
|
||||
({ children, color, href, iconSide = 'left', iconSize = 's', iconType, onClick }) => (
|
||||
<Link className="siemLinkIcon" color={color} href={href} iconSide={iconSide} onClick={onClick}>
|
||||
<EuiIcon size={iconSize} type={iconType} />
|
||||
<span className="siemLinkIcon__label">{children}</span>
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
LinkIcon.displayName = 'LinkIcon';
|
|
@ -5,6 +5,10 @@
|
|||
*/
|
||||
|
||||
export { LinkToPage } from './link_to';
|
||||
export {
|
||||
getDetectionEngineUrl,
|
||||
RedirectToDetectionEnginePage,
|
||||
} from './redirect_to_detection_engine';
|
||||
export { getOverviewUrl, RedirectToOverviewPage } from './redirect_to_overview';
|
||||
export { getHostsUrl, getHostDetailsUrl } from './redirect_to_hosts';
|
||||
export { getNetworkUrl, getIPDetailsUrl, RedirectToNetworkPage } from './redirect_to_network';
|
||||
|
|
|
@ -8,12 +8,19 @@ import React from 'react';
|
|||
import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { SiemPageName } from '../../pages/home/types';
|
||||
import { HostsTableType } from '../../store/hosts/model';
|
||||
import {
|
||||
RedirectToCreateRulePage,
|
||||
RedirectToDetectionEnginePage,
|
||||
RedirectToEditRulePage,
|
||||
RedirectToRuleDetailsPage,
|
||||
RedirectToRulesPage,
|
||||
} from './redirect_to_detection_engine';
|
||||
import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_hosts';
|
||||
import { RedirectToNetworkPage } from './redirect_to_network';
|
||||
import { RedirectToOverviewPage } from './redirect_to_overview';
|
||||
import { RedirectToTimelinesPage } from './redirect_to_timelines';
|
||||
import { HostsTableType } from '../../store/hosts/model';
|
||||
import { SiemPageName } from '../../pages/home/types';
|
||||
|
||||
interface LinkToPageProps {
|
||||
match: RouteMatch<{}>;
|
||||
|
@ -22,39 +29,62 @@ interface LinkToPageProps {
|
|||
export const LinkToPage = pure<LinkToPageProps>(({ match }) => (
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.url}/:pageName(${SiemPageName.overview})`}
|
||||
component={RedirectToOverviewPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.overview})`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToHostsPage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})`}
|
||||
component={RedirectToHostsPage}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToHostsPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:tabName(${HostsTableType.hosts}|${HostsTableType.authentications}|${HostsTableType.uncommonProcesses}|${HostsTableType.anomalies}|${HostsTableType.events})`}
|
||||
component={RedirectToHostsPage}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToHostDetailsPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:detailName/:tabName(${HostsTableType.authentications}|${HostsTableType.uncommonProcesses}|${HostsTableType.anomalies}|${HostsTableType.events})`}
|
||||
component={RedirectToHostDetailsPage}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToHostDetailsPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.hosts})/:detailName`}
|
||||
component={RedirectToHostDetailsPage}
|
||||
/>
|
||||
|
||||
<Route
|
||||
component={RedirectToNetworkPage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.network})`}
|
||||
component={RedirectToNetworkPage}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToNetworkPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.network})/ip/:detailName`}
|
||||
component={RedirectToNetworkPage}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/:pageName(${SiemPageName.timelines})`}
|
||||
component={RedirectToDetectionEnginePage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`}
|
||||
strict
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToRulesPage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToCreateRulePage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules/create-rule`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToRuleDetailsPage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules/rule-details`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToEditRulePage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules/rule-details/edit-rule`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToTimelinesPage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.timelines})`}
|
||||
/>
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { RedirectWrapper } from './redirect_wrapper';
|
||||
|
||||
export type DetectionEngineComponentProps = RouteComponentProps<{
|
||||
search: string;
|
||||
}>;
|
||||
|
||||
export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine';
|
||||
|
||||
export const RedirectToDetectionEnginePage = ({
|
||||
location: { search },
|
||||
}: DetectionEngineComponentProps) => (
|
||||
<RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}${search}`} />
|
||||
);
|
||||
|
||||
export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => {
|
||||
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules${search}`} />;
|
||||
};
|
||||
|
||||
export const RedirectToCreateRulePage = ({
|
||||
location: { search },
|
||||
}: DetectionEngineComponentProps) => {
|
||||
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule${search}`} />;
|
||||
};
|
||||
|
||||
export const RedirectToRuleDetailsPage = ({
|
||||
location: { search },
|
||||
}: DetectionEngineComponentProps) => {
|
||||
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details${search}`} />;
|
||||
};
|
||||
|
||||
export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngineComponentProps) => {
|
||||
return (
|
||||
<RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details/edit-rule${search}`} />
|
||||
);
|
||||
};
|
||||
|
||||
export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`;
|
||||
export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`;
|
||||
export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`;
|
||||
export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`;
|
||||
export const getEditRuleUrl = () =>
|
||||
`#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details/edit-rule`;
|
|
@ -26,9 +26,9 @@ jest.mock('../../lib/settings/use_kibana_ui_setting', () => {
|
|||
return { useKibanaUiSetting: () => [false] };
|
||||
});
|
||||
|
||||
jest.mock('../header_panel', () => {
|
||||
jest.mock('../header_section', () => {
|
||||
return {
|
||||
HeaderPanel: () => <div className="headerPanel" />,
|
||||
HeaderSection: () => <div className="headerSection" />,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
|||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { EuiLoadingContent } from '@elastic/eui';
|
||||
import { BarChart } from '../charts/barchart';
|
||||
import { HeaderPanel } from '../header_panel';
|
||||
import { HeaderSection } from '../header_section';
|
||||
import { ChartSeriesData, UpdateDateRange } from '../charts/common';
|
||||
import { MatrixOverTimeHistogramData } from '../../graphql/types';
|
||||
import { DEFAULT_DARK_MODE } from '../../../common/constants';
|
||||
|
@ -113,7 +113,7 @@ export const MatrixOverTimeHistogram = ({
|
|||
onMouseEnter={() => setShowInspect(true)}
|
||||
onMouseLeave={() => setShowInspect(false)}
|
||||
>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
id={id}
|
||||
title={title}
|
||||
showInspect={!loadingInitial && showInspect}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useContext } from 'react';
|
||||
|
||||
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
||||
import { HeaderPanel } from '../../header_panel';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns';
|
||||
|
@ -62,7 +62,7 @@ export const AnomaliesHostTable = React.memo<AnomaliesHostTableProps>(
|
|||
} else {
|
||||
return (
|
||||
<Panel loading={loading}>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
|
||||
pagination.totalItemCount
|
||||
)}`}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import React, { useContext } from 'react';
|
||||
import { useAnomaliesTableData } from '../anomaly/use_anomalies_table_data';
|
||||
import { HeaderPanel } from '../../header_panel';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { convertAnomaliesToNetwork } from './convert_anomalies_to_network';
|
||||
|
@ -60,7 +60,7 @@ export const AnomaliesNetworkTable = React.memo<AnomaliesNetworkTableProps>(
|
|||
} else {
|
||||
return (
|
||||
<Panel loading={loading}>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
subtitle={`${i18n.SHOWING}: ${pagination.totalItemCount.toLocaleString()} ${i18n.UNIT(
|
||||
pagination.totalItemCount
|
||||
)}`}
|
||||
|
|
|
@ -4,29 +4,30 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiCallOut, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiButtonEmpty, EuiCallOut, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment from 'moment';
|
||||
import React, { useContext, useReducer, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import moment from 'moment';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } from 'ui/documentation_links';
|
||||
import * as i18n from './translations';
|
||||
import { JobsFilters, JobSummary, SiemJob } from './types';
|
||||
|
||||
import { DEFAULT_KBN_VERSION } from '../../../common/constants';
|
||||
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
|
||||
import { METRIC_TYPE, TELEMETRY_EVENT, trackUiAction as track } from '../../lib/track_usage';
|
||||
import { errorToToaster } from '../ml/api/error_to_toaster';
|
||||
import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions';
|
||||
import { MlCapabilitiesContext } from '../ml/permissions/ml_capabilities_provider';
|
||||
import { JobsTable } from './jobs_table/jobs_table';
|
||||
import { useStateToaster } from '../toasters';
|
||||
import { setupMlJob, startDatafeeds, stopDatafeeds } from './api';
|
||||
import { UpgradeContents } from './upgrade_contents';
|
||||
import { filterJobs } from './helpers';
|
||||
import { useSiemJobs } from './hooks/use_siem_jobs';
|
||||
import { JobsTableFilters } from './jobs_table/filters/jobs_table_filters';
|
||||
import { JobsTable } from './jobs_table/jobs_table';
|
||||
import { ShowingCount } from './jobs_table/showing_count';
|
||||
import { PopoverDescription } from './popover_description';
|
||||
import { useStateToaster } from '../toasters';
|
||||
import { errorToToaster } from '../ml/api/error_to_toaster';
|
||||
import { METRIC_TYPE, TELEMETRY_EVENT, trackUiAction as track } from '../../lib/track_usage';
|
||||
import { useSiemJobs } from './hooks/use_siem_jobs';
|
||||
import { filterJobs } from './helpers';
|
||||
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
|
||||
import { DEFAULT_KBN_VERSION } from '../../../common/constants';
|
||||
import * as i18n from './translations';
|
||||
import { JobsFilters, JobSummary, SiemJob } from './types';
|
||||
import { UpgradeContents } from './upgrade_contents';
|
||||
|
||||
const PopoverContentsDiv = styled.div`
|
||||
max-width: 684px;
|
||||
|
@ -161,14 +162,14 @@ export const MlPopover = React.memo(() => {
|
|||
anchorPosition="downRight"
|
||||
id="integrations-popover"
|
||||
button={
|
||||
<EuiButton
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="integrations-button"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
>
|
||||
{i18n.ANOMALY_DETECTION}
|
||||
</EuiButton>
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
|
@ -183,7 +184,7 @@ export const MlPopover = React.memo(() => {
|
|||
anchorPosition="downRight"
|
||||
id="integrations-popover"
|
||||
button={
|
||||
<EuiButton
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="integrations-button"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
|
@ -193,7 +194,7 @@ export const MlPopover = React.memo(() => {
|
|||
}}
|
||||
>
|
||||
{i18n.ANOMALY_DETECTION}
|
||||
</EuiButton>
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
|
|
|
@ -61,6 +61,13 @@ describe('SIEM Navigation', () => {
|
|||
expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, {
|
||||
detailName: undefined,
|
||||
navTabs: {
|
||||
'detection-engine': {
|
||||
disabled: false,
|
||||
href: '#/link-to/detection-engine',
|
||||
id: 'detection-engine',
|
||||
name: 'Detection engine',
|
||||
urlKey: 'detection-engine',
|
||||
},
|
||||
hosts: {
|
||||
disabled: false,
|
||||
href: '#/link-to/hosts',
|
||||
|
@ -132,9 +139,17 @@ describe('SIEM Navigation', () => {
|
|||
tabName: undefined,
|
||||
});
|
||||
wrapper.update();
|
||||
expect(setBreadcrumbs).toHaveBeenNthCalledWith(2, {
|
||||
expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, {
|
||||
detailName: undefined,
|
||||
filters: [],
|
||||
navTabs: {
|
||||
'detection-engine': {
|
||||
disabled: false,
|
||||
href: '#/link-to/detection-engine',
|
||||
id: 'detection-engine',
|
||||
name: 'Detection engine',
|
||||
urlKey: 'detection-engine',
|
||||
},
|
||||
hosts: {
|
||||
disabled: false,
|
||||
href: '#/link-to/hosts',
|
||||
|
@ -164,17 +179,13 @@ describe('SIEM Navigation', () => {
|
|||
urlKey: 'timeline',
|
||||
},
|
||||
},
|
||||
pageName: 'network',
|
||||
pathName: '/network',
|
||||
search: '',
|
||||
tabName: undefined,
|
||||
query: { query: '', language: 'kuery' },
|
||||
filters: [],
|
||||
pageName: 'hosts',
|
||||
pathName: '/hosts',
|
||||
query: { language: 'kuery', query: '' },
|
||||
savedQuery: undefined,
|
||||
timeline: {
|
||||
id: '',
|
||||
isOpen: false,
|
||||
},
|
||||
search: '',
|
||||
tabName: 'authentications',
|
||||
timeline: { id: '', isOpen: false },
|
||||
timerange: {
|
||||
global: {
|
||||
linkTo: ['timeline'],
|
||||
|
|
|
@ -6,17 +6,16 @@
|
|||
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import React, { useEffect } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'redux';
|
||||
|
||||
import { RouteSpyState } from '../../utils/route/types';
|
||||
import { useRouteSpy } from '../../utils/route/use_route_spy';
|
||||
|
||||
import { makeMapStateToProps } from '../url_state/helpers';
|
||||
import { setBreadcrumbs } from './breadcrumbs';
|
||||
import { TabNavigation } from './tab_navigation';
|
||||
import { TabNavigationProps } from './tab_navigation/types';
|
||||
import { SiemNavigationComponentProps } from './types';
|
||||
import { makeMapStateToProps } from '../url_state/helpers';
|
||||
|
||||
export const SiemNavigationComponent = React.memo<TabNavigationProps & RouteSpyState>(
|
||||
({
|
||||
|
@ -29,7 +28,6 @@ export const SiemNavigationComponent = React.memo<TabNavigationProps & RouteSpyS
|
|||
pathName,
|
||||
savedQuery,
|
||||
search,
|
||||
showBorder,
|
||||
tabName,
|
||||
timeline,
|
||||
timerange,
|
||||
|
@ -61,7 +59,6 @@ export const SiemNavigationComponent = React.memo<TabNavigationProps & RouteSpyS
|
|||
pageName={pageName}
|
||||
pathName={pathName}
|
||||
savedQuery={savedQuery}
|
||||
showBorder={showBorder}
|
||||
tabName={tabName}
|
||||
timeline={timeline}
|
||||
timerange={timerange}
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
import { mount, shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { TabNavigation } from './';
|
||||
import { TabNavigationProps } from './types';
|
||||
import { navTabs } from '../../../pages/home/home_navigations';
|
||||
import { SiemPageName } from '../../../pages/home/types';
|
||||
import { HostsTableType } from '../../../store/hosts/model';
|
||||
import { navTabsHostDetails } from '../../../pages/hosts/details/nav_tabs';
|
||||
import { CONSTANTS } from '../../url_state/constants';
|
||||
import { HostsTableType } from '../../../store/hosts/model';
|
||||
import { RouteSpyState } from '../../../utils/route/types';
|
||||
import { CONSTANTS } from '../../url_state/constants';
|
||||
import { TabNavigation } from './';
|
||||
import { TabNavigationProps } from './types';
|
||||
|
||||
describe('Tab Navigation', () => {
|
||||
const pageName = SiemPageName.hosts;
|
||||
|
@ -78,7 +78,7 @@ describe('Tab Navigation', () => {
|
|||
});
|
||||
test('it carries the url state in the link', () => {
|
||||
const wrapper = shallow(<TabNavigation {...mockProps} />);
|
||||
const firstTab = wrapper.find('[data-test-subj="navigation-link-network"]');
|
||||
const firstTab = wrapper.find('[data-test-subj="navigation-network"]');
|
||||
expect(firstTab.props().href).toBe(
|
||||
"#/link-to/network?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))"
|
||||
);
|
||||
|
@ -147,7 +147,7 @@ describe('Tab Navigation', () => {
|
|||
test('it carries the url state in the link', () => {
|
||||
const wrapper = shallow(<TabNavigation {...mockProps} />);
|
||||
const firstTab = wrapper.find(
|
||||
`[data-test-subj="navigation-link-${HostsTableType.authentications}"]`
|
||||
`[data-test-subj="navigation-${HostsTableType.authentications}"]`
|
||||
);
|
||||
expect(firstTab.props().href).toBe(
|
||||
`#/${pageName}/${hostName}/${HostsTableType.authentications}?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`
|
||||
|
|
|
@ -3,40 +3,17 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { EuiTab, EuiTabs, EuiLink } from '@elastic/eui';
|
||||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import { getOr } from 'lodash/fp';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { trackUiAction as track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/track_usage';
|
||||
import { getSearch } from '../helpers';
|
||||
import { TabNavigationProps } from './types';
|
||||
|
||||
const TabContainer = styled.div`
|
||||
.euiLink {
|
||||
color: inherit !important;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.euiTab.euiTab-isSelected {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.showBorder {
|
||||
padding: 8px 8px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
TabContainer.displayName = 'TabContainer';
|
||||
|
||||
export const TabNavigation = React.memo<TabNavigationProps>(props => {
|
||||
const { display = 'condensed', navTabs, pageName, showBorder, tabName } = props;
|
||||
const { display, navTabs, pageName, tabName } = props;
|
||||
|
||||
const mapLocationToTab = (): string => {
|
||||
return getOr(
|
||||
'',
|
||||
|
@ -44,6 +21,7 @@ export const TabNavigation = React.memo<TabNavigationProps>(props => {
|
|||
Object.values(navTabs).find(item => tabName === item.id || pageName === item.id)
|
||||
);
|
||||
};
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
|
||||
useEffect(() => {
|
||||
const currentTabSelected = mapLocationToTab();
|
||||
|
@ -57,31 +35,21 @@ export const TabNavigation = React.memo<TabNavigationProps>(props => {
|
|||
|
||||
const renderTabs = (): JSX.Element[] =>
|
||||
Object.values(navTabs).map(tab => (
|
||||
<TabContainer
|
||||
className={classnames({ euiTab: true, showBorder })}
|
||||
<EuiTab
|
||||
data-href={tab.href}
|
||||
data-test-subj={`navigation-${tab.id}`}
|
||||
disabled={tab.disabled}
|
||||
href={tab.href + getSearch(tab, props)}
|
||||
isSelected={selectedTabId === tab.id}
|
||||
key={`navigation-${tab.id}`}
|
||||
onClick={() => {
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${tab.id}`);
|
||||
}}
|
||||
>
|
||||
<EuiLink
|
||||
data-test-subj={`navigation-link-${tab.id}`}
|
||||
href={tab.href + getSearch(tab, props)}
|
||||
>
|
||||
<EuiTab
|
||||
data-href={tab.href}
|
||||
data-test-subj={`navigation-${tab.id}`}
|
||||
disabled={tab.disabled}
|
||||
isSelected={selectedTabId === tab.id}
|
||||
onClick={() => {
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${tab.id}`);
|
||||
}}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
</EuiLink>
|
||||
</TabContainer>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
));
|
||||
return (
|
||||
<EuiTabs display={display} size="m">
|
||||
{renderTabs()}
|
||||
</EuiTabs>
|
||||
);
|
||||
|
||||
return <EuiTabs display={display}>{renderTabs()}</EuiTabs>;
|
||||
});
|
||||
TabNavigation.displayName = 'TabNavigation';
|
||||
|
|
|
@ -9,7 +9,6 @@ import { UrlStateType } from '../url_state/constants';
|
|||
export interface SiemNavigationComponentProps {
|
||||
display?: 'default' | 'condensed';
|
||||
navTabs: Record<string, NavTab>;
|
||||
showBorder?: boolean;
|
||||
}
|
||||
|
||||
export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean };
|
||||
|
|
|
@ -492,7 +492,7 @@ describe('StatefulOpenTimeline', () => {
|
|||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-panel-title"]')
|
||||
.find('[data-test-subj="header-section-title"]')
|
||||
.first()
|
||||
.text()
|
||||
).toEqual(title);
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('TitleRow', () => {
|
|||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="header-panel-title"]')
|
||||
.find('[data-test-subj="header-section-title"]')
|
||||
.first()
|
||||
.text()
|
||||
).toEqual(title);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { pure } from 'recompose';
|
|||
|
||||
import * as i18n from '../translations';
|
||||
import { OpenTimelineProps } from '../types';
|
||||
import { HeaderPanel } from '../../header_panel';
|
||||
import { HeaderSection } from '../../header_section';
|
||||
|
||||
type Props = Pick<OpenTimelineProps, 'onAddTimelinesToFavorites' | 'onDeleteSelected' | 'title'> & {
|
||||
/** The number of timelines currently selected */
|
||||
|
@ -23,7 +23,7 @@ type Props = Pick<OpenTimelineProps, 'onAddTimelinesToFavorites' | 'onDeleteSele
|
|||
*/
|
||||
export const TitleRow = pure<Props>(
|
||||
({ onAddTimelinesToFavorites, onDeleteSelected, selectedTimelinesCount, title }) => (
|
||||
<HeaderPanel title={title}>
|
||||
<HeaderSection title={title}>
|
||||
{(onAddTimelinesToFavorites || onDeleteSelected) && (
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
{onAddTimelinesToFavorites && (
|
||||
|
@ -55,7 +55,7 @@ export const TitleRow = pure<Props>(
|
|||
)}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</HeaderPanel>
|
||||
</HeaderSection>
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HistogramSignals it renders 1`] = `
|
||||
<Component>
|
||||
<HistogramSignals />
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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/ui_settings';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
import { HistogramSignals } from './index';
|
||||
|
||||
jest.mock('../../../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('HistogramSignals', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<HistogramSignals />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 {
|
||||
Axis,
|
||||
Chart,
|
||||
HistogramBarSeries,
|
||||
Settings,
|
||||
getAxisId,
|
||||
getSpecId,
|
||||
niceTimeFormatByDay,
|
||||
timeFormatter,
|
||||
} from '@elastic/charts';
|
||||
import React from 'react';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
|
||||
export const HistogramSignals = React.memo(() => {
|
||||
const sampleChartData = [
|
||||
{ x: 1571090784000, y: 2, a: 'a' },
|
||||
{ x: 1571090784000, y: 2, b: 'b' },
|
||||
{ x: 1571093484000, y: 7, a: 'a' },
|
||||
{ x: 1571096184000, y: 3, a: 'a' },
|
||||
{ x: 1571098884000, y: 2, a: 'a' },
|
||||
{ x: 1571101584000, y: 7, a: 'a' },
|
||||
{ x: 1571104284000, y: 3, a: 'a' },
|
||||
{ x: 1571106984000, y: 2, a: 'a' },
|
||||
{ x: 1571109684000, y: 7, a: 'a' },
|
||||
{ x: 1571112384000, y: 3, a: 'a' },
|
||||
{ x: 1571115084000, y: 2, a: 'a' },
|
||||
{ x: 1571117784000, y: 7, a: 'a' },
|
||||
{ x: 1571120484000, y: 3, a: 'a' },
|
||||
{ x: 1571123184000, y: 2, a: 'a' },
|
||||
{ x: 1571125884000, y: 7, a: 'a' },
|
||||
{ x: 1571128584000, y: 3, a: 'a' },
|
||||
{ x: 1571131284000, y: 2, a: 'a' },
|
||||
{ x: 1571133984000, y: 7, a: 'a' },
|
||||
{ x: 1571136684000, y: 3, a: 'a' },
|
||||
{ x: 1571139384000, y: 2, a: 'a' },
|
||||
{ x: 1571142084000, y: 7, a: 'a' },
|
||||
{ x: 1571144784000, y: 3, a: 'a' },
|
||||
{ x: 1571147484000, y: 2, a: 'a' },
|
||||
{ x: 1571150184000, y: 7, a: 'a' },
|
||||
{ x: 1571152884000, y: 3, a: 'a' },
|
||||
{ x: 1571155584000, y: 2, a: 'a' },
|
||||
{ x: 1571158284000, y: 7, a: 'a' },
|
||||
{ x: 1571160984000, y: 3, a: 'a' },
|
||||
{ x: 1571163684000, y: 2, a: 'a' },
|
||||
{ x: 1571166384000, y: 7, a: 'a' },
|
||||
{ x: 1571169084000, y: 3, a: 'a' },
|
||||
{ x: 1571171784000, y: 2, a: 'a' },
|
||||
{ x: 1571174484000, y: 7, a: 'a' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Chart size={['100%', 259]}>
|
||||
<Settings
|
||||
legendPosition="bottom"
|
||||
showLegend
|
||||
theme={npStart.plugins.eui_utils.useChartsTheme()}
|
||||
/>
|
||||
|
||||
<Axis
|
||||
id={getAxisId('signalAxisX')}
|
||||
position="bottom"
|
||||
tickFormat={timeFormatter(niceTimeFormatByDay(1))}
|
||||
/>
|
||||
|
||||
<Axis id={getAxisId('signalAxisY')} position="left" />
|
||||
|
||||
<HistogramBarSeries
|
||||
id={getSpecId('signalBar')}
|
||||
xScaleType="time"
|
||||
yScaleType="linear"
|
||||
xAccessor="x"
|
||||
yAccessors={['y']}
|
||||
splitSeriesAccessors={['a', 'b']}
|
||||
data={sampleChartData}
|
||||
/>
|
||||
</Chart>
|
||||
);
|
||||
});
|
||||
HistogramSignals.displayName = 'HistogramSignals';
|
|
@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import React, { useState } from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { HeaderPanel } from '../../../header_panel';
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import {
|
||||
ID as OverviewHostQueryId,
|
||||
|
@ -42,7 +42,7 @@ export const OverviewHost = pure<OverviewHostProps>(({ endDate, startDate, setQu
|
|||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewHostQueryId}
|
||||
showInspect={isHover}
|
||||
|
@ -59,7 +59,7 @@ export const OverviewHost = pure<OverviewHostProps>(({ endDate, startDate, setQu
|
|||
<EuiButton href={getHostsUrl()}>
|
||||
<FormattedMessage id="xpack.siem.overview.hostsAction" defaultMessage="View hosts" />
|
||||
</EuiButton>
|
||||
</HeaderPanel>
|
||||
</HeaderSection>
|
||||
|
||||
<OverviewHostQuery endDate={endDate} sourceId="default" startDate={startDate}>
|
||||
{({ overviewHost, loading, id, inspect, refetch }) => (
|
||||
|
|
|
@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import React, { useState } from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { HeaderPanel } from '../../../header_panel';
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
import {
|
||||
ID as OverviewNetworkQueryId,
|
||||
|
@ -42,7 +42,7 @@ export const OverviewNetwork = pure<OwnProps>(({ endDate, startDate, setQuery })
|
|||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewNetworkQueryId}
|
||||
showInspect={isHover}
|
||||
|
@ -65,7 +65,7 @@ export const OverviewNetwork = pure<OwnProps>(({ endDate, startDate, setQuery })
|
|||
defaultMessage="View network"
|
||||
/>
|
||||
</EuiButton>
|
||||
</HeaderPanel>
|
||||
</HeaderSection>
|
||||
|
||||
<OverviewNetworkQuery endDate={endDate} sourceId="default" startDate={startDate}>
|
||||
{({ overviewNetwork, loading, id, inspect, refetch }) => (
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
import { TlsColumns } from '../page/network/tls_table/columns';
|
||||
import { UncommonProcessTableColumns } from '../page/hosts/uncommon_process_table';
|
||||
import { UsersColumns } from '../page/network/users_table/columns';
|
||||
import { HeaderPanel } from '../header_panel';
|
||||
import { HeaderSection } from '../header_section';
|
||||
import { Loader } from '../loader';
|
||||
import { useStateToaster } from '../toasters';
|
||||
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants';
|
||||
|
@ -234,7 +234,7 @@ export const PaginatedTable = memo<SiemTables>(
|
|||
onMouseEnter={() => setShowInspect(true)}
|
||||
onMouseLeave={() => setShowInspect(false)}
|
||||
>
|
||||
<HeaderPanel
|
||||
<HeaderSection
|
||||
id={id}
|
||||
showInspect={!loadingInitial && showInspect}
|
||||
subtitle={
|
||||
|
@ -245,7 +245,7 @@ export const PaginatedTable = memo<SiemTables>(
|
|||
tooltip={headerTooltip}
|
||||
>
|
||||
{!loadingInitial && headerSupplement}
|
||||
</HeaderPanel>
|
||||
</HeaderSection>
|
||||
|
||||
{loadingInitial ? (
|
||||
<EuiLoadingContent data-test-subj="initialLoadingPanelPaginatedTable" lines={10} />
|
||||
|
|
13
x-pack/legacy/plugins/siem/public/components/progress_inline/__snapshots__/index.test.tsx.snap
generated
Normal file
13
x-pack/legacy/plugins/siem/public/components/progress_inline/__snapshots__/index.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ProgressInline it renders 1`] = `
|
||||
<Component>
|
||||
<ProgressInline
|
||||
current={50}
|
||||
max={100}
|
||||
unit="tests"
|
||||
>
|
||||
Test progress
|
||||
</ProgressInline>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../mock';
|
||||
import '../../mock/ui_settings';
|
||||
import { ProgressInline } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('ProgressInline', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<ProgressInline current={50} max={100} unit="tests">
|
||||
{'Test progress'}
|
||||
</ProgressInline>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { EuiProgress } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const Wrapper = styled.dl`
|
||||
${({ theme }) => css`
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
|
||||
& > * + * {
|
||||
margin-left: ${theme.eui.euiSizeS};
|
||||
}
|
||||
|
||||
.siemProgressInline__bar {
|
||||
width: 100px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
export interface ProgressInlineProps {
|
||||
children: string;
|
||||
current: number;
|
||||
max: number;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export const ProgressInline = React.memo<ProgressInlineProps>(
|
||||
({ children, current, max, unit }) => (
|
||||
<Wrapper className="siemProgressInline">
|
||||
<dt className="siemProgressInline__title">{children}</dt>
|
||||
|
||||
<dd className="siemProgressInline__bar">
|
||||
<EuiProgress color="secondary" max={max} value={current} />
|
||||
</dd>
|
||||
|
||||
<dd className="siemProgressInline__ratio">
|
||||
{current.toLocaleString()}
|
||||
{'/'}
|
||||
{max.toLocaleString()} {unit}
|
||||
</dd>
|
||||
</Wrapper>
|
||||
)
|
||||
);
|
||||
ProgressInline.displayName = 'ProgressInline';
|
9
x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap
generated
Normal file
9
x-pack/legacy/plugins/siem/public/components/subtitle/__snapshots__/index.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Subtitle it renders 1`] = `
|
||||
<Component>
|
||||
<Subtitle
|
||||
items="Test subtitle"
|
||||
/>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import '../../mock/ui_settings';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { Subtitle } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('Subtitle', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<Subtitle items="Test subtitle" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it renders one subtitle string item', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Subtitle items="Test subtitle" />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemSubtitle__item--text').length).toEqual(1);
|
||||
});
|
||||
|
||||
test('it renders multiple subtitle string items', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Subtitle items={['Test subtitle 1', 'Test subtitle 2']} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemSubtitle__item--text').length).toEqual(2);
|
||||
});
|
||||
|
||||
test('it renders one subtitle React.ReactNode item', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Subtitle items={<span>{'Test subtitle'}</span>} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemSubtitle__item--node').length).toEqual(1);
|
||||
});
|
||||
|
||||
test('it renders multiple subtitle React.ReactNode items', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Subtitle items={[<span>{'Test subtitle 1'}</span>, <span>{'Test subtitle 2'}</span>]} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemSubtitle__item--node').length).toEqual(2);
|
||||
});
|
||||
|
||||
test('it renders multiple subtitle items of mixed type', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Subtitle items={['Test subtitle 1', <span>{'Test subtitle 2'}</span>]} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('.siemSubtitle__item').length).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
${({ theme }) => css`
|
||||
margin-top: ${theme.eui.euiSizeS};
|
||||
|
||||
.siemSubtitle__item {
|
||||
color: ${theme.eui.textColors.subdued};
|
||||
font-size: ${theme.eui.euiFontSizeXS};
|
||||
line-height: ${theme.eui.euiLineHeight};
|
||||
|
||||
@media only screen and (min-width: ${theme.eui.euiBreakpoints.s}) {
|
||||
display: inline-block;
|
||||
margin-right: ${theme.eui.euiSize};
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
interface SubtitleItemProps {
|
||||
children: string | React.ReactNode;
|
||||
}
|
||||
|
||||
const SubtitleItem = React.memo<SubtitleItemProps>(({ children }) => {
|
||||
if (typeof children === 'string') {
|
||||
return <p className="siemSubtitle__item siemSubtitle__item--text">{children}</p>;
|
||||
} else {
|
||||
return <div className="siemSubtitle__item siemSubtitle__item--node">{children}</div>;
|
||||
}
|
||||
});
|
||||
SubtitleItem.displayName = 'SubtitleItem';
|
||||
|
||||
export interface SubtitleProps {
|
||||
items: string | React.ReactNode | Array<string | React.ReactNode>;
|
||||
}
|
||||
|
||||
export const Subtitle = React.memo<SubtitleProps>(({ items }) => {
|
||||
return (
|
||||
<Wrapper className="siemSubtitle">
|
||||
{Array.isArray(items) ? (
|
||||
items.map((item, i) => <SubtitleItem key={i}>{item}</SubtitleItem>)
|
||||
) : (
|
||||
<SubtitleItem>{items}</SubtitleItem>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
});
|
||||
Subtitle.displayName = 'Subtitle';
|
|
@ -6,17 +6,18 @@
|
|||
|
||||
export enum CONSTANTS {
|
||||
appQuery = 'query',
|
||||
detectionEnginePage = 'detectionEngine.page',
|
||||
filters = 'filters',
|
||||
savedQuery = 'savedQuery',
|
||||
hostsDetails = 'hosts.details',
|
||||
hostsPage = 'hosts.page',
|
||||
networkDetails = 'network.details',
|
||||
networkPage = 'network.page',
|
||||
overviewPage = 'overview.page',
|
||||
savedQuery = 'savedQuery',
|
||||
timelinePage = 'timeline.page',
|
||||
timerange = 'timerange',
|
||||
timeline = 'timeline',
|
||||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
export type UrlStateType = 'host' | 'network' | 'overview' | 'timeline';
|
||||
export type UrlStateType = 'detection-engine' | 'host' | 'network' | 'overview' | 'timeline';
|
||||
|
|
|
@ -72,12 +72,14 @@ export const replaceQueryStringInLocation = (location: Location, queryString: st
|
|||
};
|
||||
|
||||
export const getUrlType = (pageName: string): UrlStateType => {
|
||||
if (pageName === SiemPageName.hosts) {
|
||||
if (pageName === SiemPageName.overview) {
|
||||
return 'overview';
|
||||
} else if (pageName === SiemPageName.hosts) {
|
||||
return 'host';
|
||||
} else if (pageName === SiemPageName.network) {
|
||||
return 'network';
|
||||
} else if (pageName === SiemPageName.overview) {
|
||||
return 'overview';
|
||||
} else if (pageName === SiemPageName.detectionEngine) {
|
||||
return 'detection-engine';
|
||||
} else if (pageName === SiemPageName.timelines) {
|
||||
return 'timeline';
|
||||
}
|
||||
|
@ -97,7 +99,9 @@ export const getCurrentLocation = (
|
|||
pageName: string,
|
||||
detailName: string | undefined
|
||||
): LocationTypes => {
|
||||
if (pageName === SiemPageName.hosts) {
|
||||
if (pageName === SiemPageName.overview) {
|
||||
return CONSTANTS.overviewPage;
|
||||
} else if (pageName === SiemPageName.hosts) {
|
||||
if (detailName != null) {
|
||||
return CONSTANTS.hostsDetails;
|
||||
}
|
||||
|
@ -107,8 +111,8 @@ export const getCurrentLocation = (
|
|||
return CONSTANTS.networkDetails;
|
||||
}
|
||||
return CONSTANTS.networkPage;
|
||||
} else if (pageName === SiemPageName.overview) {
|
||||
return CONSTANTS.overviewPage;
|
||||
} else if (pageName === SiemPageName.detectionEngine) {
|
||||
return CONSTANTS.detectionEnginePage;
|
||||
} else if (pageName === SiemPageName.timelines) {
|
||||
return CONSTANTS.timelinePage;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
|
|||
];
|
||||
|
||||
export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
||||
'detection-engine': [],
|
||||
host: [
|
||||
CONSTANTS.appQuery,
|
||||
CONSTANTS.filters,
|
||||
|
@ -39,15 +40,16 @@ export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
|||
CONSTANTS.timerange,
|
||||
CONSTANTS.timeline,
|
||||
],
|
||||
timeline: [CONSTANTS.timeline, CONSTANTS.timerange],
|
||||
overview: [CONSTANTS.timeline, CONSTANTS.timerange],
|
||||
timeline: [CONSTANTS.timeline, CONSTANTS.timerange],
|
||||
};
|
||||
|
||||
export type LocationTypes =
|
||||
| CONSTANTS.networkDetails
|
||||
| CONSTANTS.networkPage
|
||||
| CONSTANTS.detectionEnginePage
|
||||
| CONSTANTS.hostsDetails
|
||||
| CONSTANTS.hostsPage
|
||||
| CONSTANTS.networkDetails
|
||||
| CONSTANTS.networkPage
|
||||
| CONSTANTS.overviewPage
|
||||
| CONSTANTS.timelinePage
|
||||
| CONSTANTS.unknown;
|
||||
|
|
47
x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap
generated
Normal file
47
x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`WrapperPage it renders 1`] = `
|
||||
<Component>
|
||||
<WrapperPage>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</WrapperPage>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
exports[`WrapperPage restrict width custom max width when restrictWidth is number 1`] = `
|
||||
<Component>
|
||||
<WrapperPage
|
||||
restrictWidth={600}
|
||||
>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</WrapperPage>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
exports[`WrapperPage restrict width custom max width when restrictWidth is string 1`] = `
|
||||
<Component>
|
||||
<WrapperPage
|
||||
restrictWidth="600px"
|
||||
>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</WrapperPage>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
exports[`WrapperPage restrict width default max width when restrictWidth is true 1`] = `
|
||||
<Component>
|
||||
<WrapperPage
|
||||
restrictWidth={true}
|
||||
>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</WrapperPage>
|
||||
</Component>
|
||||
`;
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { TestProviders } from '../../mock';
|
||||
import '../../mock/ui_settings';
|
||||
import { WrapperPage } from './index';
|
||||
|
||||
jest.mock('../../lib/settings/use_kibana_ui_setting');
|
||||
|
||||
describe('WrapperPage', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage>
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('restrict width', () => {
|
||||
test('default max width when restrictWidth is true', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage restrictWidth>
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('custom max width when restrictWidth is number', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage restrictWidth={600}>
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('custom max width when restrictWidth is string', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage restrictWidth="600px">
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { gutterTimeline } from '../../lib/helpers';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
${({ theme }) => css`
|
||||
padding: ${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l}
|
||||
${theme.eui.paddingSizes.l};
|
||||
|
||||
&.siemWrapperPage--restrictWidthDefault,
|
||||
&.siemWrapperPage--restrictWidthCustom {
|
||||
box-sizing: content-box;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.siemWrapperPage--restrictWidthDefault {
|
||||
max-width: 1000px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
export interface WrapperPageProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
restrictWidth?: boolean | number | string;
|
||||
style?: Record<string, string>;
|
||||
}
|
||||
|
||||
export const WrapperPage = React.memo<WrapperPageProps>(
|
||||
({ children, className, restrictWidth, style }) => {
|
||||
const classes = classNames(className, {
|
||||
siemWrapperPage: true,
|
||||
'siemWrapperPage--restrictWidthDefault':
|
||||
restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true,
|
||||
'siemWrapperPage--restrictWidthCustom': restrictWidth && typeof restrictWidth !== 'boolean',
|
||||
});
|
||||
|
||||
let customStyle: WrapperPageProps['style'];
|
||||
|
||||
if (restrictWidth && typeof restrictWidth !== 'boolean') {
|
||||
const value = typeof restrictWidth === 'number' ? `${restrictWidth}px` : restrictWidth;
|
||||
customStyle = { ...style, maxWidth: value };
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper className={classes} style={customStyle || style}>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
WrapperPage.displayName = 'WrapperPage';
|
|
@ -42,3 +42,9 @@ export const assertUnreachable = (
|
|||
): never => {
|
||||
throw new Error(`${message}: ${x}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Global variables
|
||||
*/
|
||||
|
||||
export const gutterTimeline = '70px'; // Michael: Temporary until timeline is moved.
|
||||
|
|
|
@ -8,13 +8,14 @@ import React from 'react';
|
|||
import { pure } from 'recompose';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { WrapperPage } from '../components/wrapper_page';
|
||||
|
||||
export const NotFoundPage = pure(() => (
|
||||
<div>
|
||||
<WrapperPage>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.pages.fourohfour.noContentFoundDescription"
|
||||
defaultMessage="No content found"
|
||||
/>
|
||||
</div>
|
||||
</WrapperPage>
|
||||
));
|
||||
|
||||
NotFoundPage.displayName = 'NotFoundPage';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderPage } from '../../../components/header_page';
|
||||
import { WrapperPage } from '../../../components/wrapper_page';
|
||||
import { SpyRoute } from '../../../utils/route/spy_routes';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const CreateRuleComponent = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<WrapperPage restrictWidth>
|
||||
<HeaderPage
|
||||
backOptions={{ href: '#detection-engine/rules', text: 'Back to rules' }}
|
||||
border
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
</WrapperPage>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
});
|
||||
CreateRuleComponent.displayName = 'CreateRuleComponent';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.pageTitle', {
|
||||
defaultMessage: 'Create new rule',
|
||||
});
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiPanel,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { HeaderSection } from '../../components/header_section';
|
||||
import { HistogramSignals } from '../../components/page/detection_engine/histogram_signals';
|
||||
import { SiemSearchBar } from '../../components/search_bar';
|
||||
import {
|
||||
UtilityBar,
|
||||
UtilityBarAction,
|
||||
UtilityBarGroup,
|
||||
UtilityBarSection,
|
||||
UtilityBarText,
|
||||
} from '../../components/detection_engine/utility_bar';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
import { DetectionEngineEmptyPage } from './detection_engine_empty_page';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const OpenSignals = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{`${i18n.PANEL_SUBTITLE_SHOWING}: 7,712 signals`}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Selected: 20 signals'}</UtilityBarText>
|
||||
|
||||
<UtilityBarAction
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
popoverContent={<p>{'Batch actions context menu here.'}</p>}
|
||||
>
|
||||
{'Batch actions'}
|
||||
</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="listAdd">
|
||||
{'Select all signals on all pages'}
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="cross">{'Clear 7 filters'}</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="cross">{'Clear aggregation'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
popoverContent={<p>{'Customize columns context menu here.'}</p>}
|
||||
>
|
||||
{'Customize columns'}
|
||||
</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="indexMapping">{'Aggregate data'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
|
||||
{/* Michael: Open signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const ClosedSignals = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{`${i18n.PANEL_SUBTITLE_SHOWING}: 7,712 signals`}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
popoverContent={<p>{'Customize columns context menu here.'}</p>}
|
||||
>
|
||||
{'Customize columns'}
|
||||
</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="indexMapping">{'Aggregate data'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
|
||||
{/* Michael: Closed signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const DetectionEngineComponent = React.memo(() => {
|
||||
const sampleChartOptions = [
|
||||
{ text: 'Risk scores', value: 'risk_scores' },
|
||||
{ text: 'Severities', value: 'severities' },
|
||||
{ text: 'Top destination IPs', value: 'destination_ips' },
|
||||
{ text: 'Top event actions', value: 'event_actions' },
|
||||
{ text: 'Top event categories', value: 'event_categories' },
|
||||
{ text: 'Top host names', value: 'host_names' },
|
||||
{ text: 'Top rule types', value: 'rule_types' },
|
||||
{ text: 'Top rules', value: 'rules' },
|
||||
{ text: 'Top source IPs', value: 'source_ips' },
|
||||
{ text: 'Top users', value: 'users' },
|
||||
];
|
||||
|
||||
const filterGroupOptions = ['open', 'closed'];
|
||||
const [filterGroupState, setFilterGroupState] = useState(filterGroupOptions[0]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist, indexPattern }) => {
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar id="global" indexPattern={indexPattern} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<WrapperPage>
|
||||
<HeaderPage border subtitle={i18n.PAGE_SUBTITLE} title={i18n.PAGE_TITLE}>
|
||||
<EuiButton fill href="#/detection-engine/rules" iconType="gear">
|
||||
{i18n.BUTTON_MANAGE_RULES}
|
||||
</EuiButton>
|
||||
</HeaderPage>
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Signal detection frequency">
|
||||
<EuiSelect
|
||||
options={sampleChartOptions}
|
||||
onChange={() => {}}
|
||||
prepend="Stack by"
|
||||
value={sampleChartOptions[0].value}
|
||||
/>
|
||||
</HeaderSection>
|
||||
|
||||
<HistogramSignals />
|
||||
</EuiPanel>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="All signals">
|
||||
<EuiFilterGroup>
|
||||
<EuiFilterButton
|
||||
hasActiveFilters={filterGroupState === filterGroupOptions[0]}
|
||||
onClick={() => setFilterGroupState(filterGroupOptions[0])}
|
||||
withNext
|
||||
>
|
||||
{'Open signals'}
|
||||
</EuiFilterButton>
|
||||
|
||||
<EuiFilterButton
|
||||
hasActiveFilters={filterGroupState === filterGroupOptions[1]}
|
||||
onClick={() => setFilterGroupState(filterGroupOptions[1])}
|
||||
>
|
||||
{'Closed signals'}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</HeaderSection>
|
||||
|
||||
{filterGroupState === filterGroupOptions[0] ? <OpenSignals /> : <ClosedSignals />}
|
||||
</EuiPanel>
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={i18n.PAGE_TITLE} />
|
||||
|
||||
<DetectionEngineEmptyPage />
|
||||
</WrapperPage>
|
||||
);
|
||||
}}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
});
|
||||
DetectionEngineComponent.displayName = 'DetectionEngineComponent';
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import chrome from 'ui/chrome';
|
||||
import { documentationLinks } from 'ui/documentation_links';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
export const DetectionEngineEmptyPage = React.memo(() => (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="gear"
|
||||
actionPrimaryLabel={i18n.EMPTY_ACTION_PRIMARY}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={documentationLinks.siem}
|
||||
data-test-subj="empty-page"
|
||||
title={i18n.EMPTY_TITLE}
|
||||
/>
|
||||
));
|
||||
DetectionEngineEmptyPage.displayName = 'DetectionEngineEmptyPage';
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTabbedContent,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderPage } from '../../../components/header_page';
|
||||
import { HeaderSection } from '../../../components/header_section';
|
||||
import { WrapperPage } from '../../../components/wrapper_page';
|
||||
import { SpyRoute } from '../../../utils/route/spy_routes';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const Define = React.memo(() => (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Define rule" />
|
||||
</EuiPanel>
|
||||
</>
|
||||
));
|
||||
Define.displayName = 'Define';
|
||||
|
||||
const About = React.memo(() => (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="About rule" />
|
||||
</EuiPanel>
|
||||
</>
|
||||
));
|
||||
About.displayName = 'About';
|
||||
|
||||
const Schedule = React.memo(() => (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Schedule rule" />
|
||||
</EuiPanel>
|
||||
</>
|
||||
));
|
||||
Schedule.displayName = 'Schedule';
|
||||
|
||||
export const EditRuleComponent = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<WrapperPage restrictWidth>
|
||||
<HeaderPage
|
||||
backOptions={{
|
||||
href: '#detection-engine/rules/rule-details',
|
||||
text: 'Back to automated exfiltration',
|
||||
}}
|
||||
title={i18n.PAGE_TITLE}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton href="#/detection-engine/rules/rule-details" iconType="cross">
|
||||
{'Cancel'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill href="#/detection-engine/rules/rule-details" iconType="save">
|
||||
{'Save changes'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</HeaderPage>
|
||||
|
||||
<EuiTabbedContent
|
||||
tabs={[
|
||||
{
|
||||
id: 'tabDefine',
|
||||
name: 'Define',
|
||||
content: <Define />,
|
||||
},
|
||||
{
|
||||
id: 'tabAbout',
|
||||
name: 'About',
|
||||
content: <About />,
|
||||
},
|
||||
{
|
||||
id: 'tabSchedule',
|
||||
name: 'Schedule',
|
||||
content: <Schedule />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
justifyContent="flexEnd"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton href="#/detection-engine/rules/rule-details" iconType="cross">
|
||||
{'Cancel'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill href="#/detection-engine/rules/rule-details" iconType="save">
|
||||
{'Save changes'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</WrapperPage>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
});
|
||||
EditRuleComponent.displayName = 'EditRuleComponent';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.editRule.pageTitle', {
|
||||
defaultMessage: 'Edit rule settings',
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { CreateRuleComponent } from './create_rule';
|
||||
import { DetectionEngineComponent } from './detection_engine';
|
||||
import { EditRuleComponent } from './edit_rule';
|
||||
import { RuleDetailsComponent } from './rule_details';
|
||||
import { RulesComponent } from './rules';
|
||||
|
||||
const detectionEnginePath = `/:pageName(detection-engine)`;
|
||||
|
||||
type Props = Partial<RouteComponentProps<{}>> & { url: string };
|
||||
|
||||
export const DetectionEngineContainer = React.memo<Props>(() => (
|
||||
<Switch>
|
||||
<Route exact path={detectionEnginePath} render={() => <DetectionEngineComponent />} strict />
|
||||
<Route exact path={`${detectionEnginePath}/rules`} render={() => <RulesComponent />} />
|
||||
<Route
|
||||
path={`${detectionEnginePath}/rules/create-rule`}
|
||||
render={() => <CreateRuleComponent />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${detectionEnginePath}/rules/rule-details`}
|
||||
render={() => <RuleDetailsComponent />}
|
||||
/>
|
||||
<Route
|
||||
path={`${detectionEnginePath}/rules/rule-details/edit-rule`}
|
||||
render={() => <EditRuleComponent />}
|
||||
/>
|
||||
<Route
|
||||
path="/detection-engine/"
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect from="/detection-engine/" to={`/detection-engine${search}`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
));
|
||||
DetectionEngineContainer.displayName = 'DetectionEngineContainer';
|
|
@ -0,0 +1,660 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiBasicTable,
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiCallOut,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIconTip,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiTabbedContent,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import React, { useState } from 'react';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
|
||||
import { getEmptyTagValue } from '../../../components/empty_value';
|
||||
import { FiltersGlobal } from '../../../components/filters_global';
|
||||
import { HeaderPage } from '../../../components/header_page';
|
||||
import { HeaderSection } from '../../../components/header_section';
|
||||
import { HistogramSignals } from '../../../components/page/detection_engine/histogram_signals';
|
||||
import { ProgressInline } from '../../../components/progress_inline';
|
||||
import { SiemSearchBar } from '../../../components/search_bar';
|
||||
import {
|
||||
UtilityBar,
|
||||
UtilityBarAction,
|
||||
UtilityBarGroup,
|
||||
UtilityBarSection,
|
||||
UtilityBarText,
|
||||
} from '../../../components/detection_engine/utility_bar';
|
||||
import { WrapperPage } from '../../../components/wrapper_page';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source';
|
||||
import { SpyRoute } from '../../../utils/route/spy_routes';
|
||||
import { DetectionEngineEmptyPage } from '../detection_engine_empty_page';
|
||||
import * as i18n from './translations';
|
||||
|
||||
// Michael: Will need to change this to get the current datetime format from Kibana settings.
|
||||
const dateTimeFormat = (value: string) => {
|
||||
return moment(value).format('M/D/YYYY, h:mm A');
|
||||
};
|
||||
|
||||
const OpenSignals = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Showing: 439 signals'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Selected: 20 signals'}</UtilityBarText>
|
||||
|
||||
<UtilityBarAction
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
popoverContent={<p>{'Batch actions context menu here.'}</p>}
|
||||
>
|
||||
{'Batch actions'}
|
||||
</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="listAdd">
|
||||
{'Select all signals on all pages'}
|
||||
</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="cross">{'Clear 7 filters'}</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="cross">{'Clear aggregation'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
popoverContent={<p>{'Customize columns context menu here.'}</p>}
|
||||
>
|
||||
{'Customize columns'}
|
||||
</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="indexMapping">{'Aggregate data'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
|
||||
{/* Michael: Open signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const ClosedSignals = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<UtilityBar>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Showing: 439 signals'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
popoverContent={<p>{'Customize columns context menu here.'}</p>}
|
||||
>
|
||||
{'Customize columns'}
|
||||
</UtilityBarAction>
|
||||
|
||||
<UtilityBarAction iconType="indexMapping">{'Aggregate data'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
|
||||
{/* Michael: Closed signals datagrid here. Talk to Chandler Prall about possibility of early access. If not possible, use basic table. */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const Signals = React.memo(() => {
|
||||
const sampleChartOptions = [
|
||||
{ text: 'Risk scores', value: 'risk_scores' },
|
||||
{ text: 'Severities', value: 'severities' },
|
||||
{ text: 'Top destination IPs', value: 'destination_ips' },
|
||||
{ text: 'Top event actions', value: 'event_actions' },
|
||||
{ text: 'Top event categories', value: 'event_categories' },
|
||||
{ text: 'Top host names', value: 'host_names' },
|
||||
{ text: 'Top source IPs', value: 'source_ips' },
|
||||
{ text: 'Top users', value: 'users' },
|
||||
];
|
||||
|
||||
const filterGroupOptions = ['open', 'closed'];
|
||||
const [filterGroupState, setFilterGroupState] = useState(filterGroupOptions[0]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Signal detection frequency">
|
||||
<EuiSelect
|
||||
options={sampleChartOptions}
|
||||
onChange={() => {}}
|
||||
prepend="Stack by"
|
||||
value={sampleChartOptions[0].value}
|
||||
/>
|
||||
</HeaderSection>
|
||||
|
||||
<HistogramSignals />
|
||||
</EuiPanel>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Signals">
|
||||
<EuiFilterGroup>
|
||||
<EuiFilterButton
|
||||
hasActiveFilters={filterGroupState === filterGroupOptions[0]}
|
||||
onClick={() => setFilterGroupState(filterGroupOptions[0])}
|
||||
withNext
|
||||
>
|
||||
{'Open signals'}
|
||||
</EuiFilterButton>
|
||||
|
||||
<EuiFilterButton
|
||||
hasActiveFilters={filterGroupState === filterGroupOptions[1]}
|
||||
onClick={() => setFilterGroupState(filterGroupOptions[1])}
|
||||
>
|
||||
{'Closed signals'}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</HeaderSection>
|
||||
|
||||
{filterGroupState === filterGroupOptions[0] ? <OpenSignals /> : <ClosedSignals />}
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
});
|
||||
Signals.displayName = 'Signals';
|
||||
|
||||
const ActivityMonitor = React.memo(() => {
|
||||
interface ColumnTypes {
|
||||
id: number;
|
||||
ran: string;
|
||||
lookedBackTo: string;
|
||||
status: string;
|
||||
response: string | undefined;
|
||||
}
|
||||
|
||||
interface PageTypes {
|
||||
index: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface SortTypes {
|
||||
field: string;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
const actions = [
|
||||
{
|
||||
available: (item: ColumnTypes) => item.status === 'Running',
|
||||
description: 'Stop',
|
||||
icon: 'stop',
|
||||
isPrimary: true,
|
||||
name: 'Stop',
|
||||
onClick: () => {},
|
||||
type: 'icon',
|
||||
},
|
||||
{
|
||||
available: (item: ColumnTypes) => item.status === 'Stopped',
|
||||
description: 'Resume',
|
||||
icon: 'play',
|
||||
isPrimary: true,
|
||||
name: 'Resume',
|
||||
onClick: () => {},
|
||||
type: 'icon',
|
||||
},
|
||||
];
|
||||
|
||||
// Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes?
|
||||
const columns = [
|
||||
{
|
||||
field: 'ran',
|
||||
name: 'Ran',
|
||||
render: (value: ColumnTypes['ran']) => <time dateTime={value}>{dateTimeFormat(value)}</time>,
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'lookedBackTo',
|
||||
name: 'Looked back to',
|
||||
render: (value: ColumnTypes['lookedBackTo']) => (
|
||||
<time dateTime={value}>{dateTimeFormat(value)}</time>
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: 'Status',
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'response',
|
||||
name: 'Response',
|
||||
render: (value: ColumnTypes['response']) => {
|
||||
return value === undefined ? (
|
||||
getEmptyTagValue()
|
||||
) : (
|
||||
<>
|
||||
{value === 'Fail' ? (
|
||||
<EuiTextColor color="danger">
|
||||
{value} <EuiIconTip content="Full fail message here." type="iInCircle" />
|
||||
</EuiTextColor>
|
||||
) : (
|
||||
<EuiTextColor color="secondary">{value}</EuiTextColor>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
actions,
|
||||
width: '40px',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleTableData = [
|
||||
{
|
||||
id: 1,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Running',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Stopped',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Fail',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
lookedBackTo: '2019-12-28 00:00:00.000-05:00',
|
||||
status: 'Completed',
|
||||
response: 'Success',
|
||||
},
|
||||
];
|
||||
|
||||
const [itemsTotalState] = useState<number>(sampleTableData.length);
|
||||
const [pageState, setPageState] = useState<PageTypes>({ index: 0, size: 20 });
|
||||
// const [selectedState, setSelectedState] = useState<ColumnTypes[]>([]);
|
||||
const [sortState, setSortState] = useState<SortTypes>({ field: 'ran', direction: 'desc' });
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Activity monitor" />
|
||||
|
||||
<UtilityBar border>
|
||||
<UtilityBarSection>
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Showing: 39 activites'}</UtilityBarText>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarText>{'Selected: 2 activities'}</UtilityBarText>
|
||||
|
||||
<UtilityBarAction iconType="stop">{'Stop selected'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
|
||||
<UtilityBarGroup>
|
||||
<UtilityBarAction iconType="cross">{'Clear 7 filters'}</UtilityBarAction>
|
||||
</UtilityBarGroup>
|
||||
</UtilityBarSection>
|
||||
</UtilityBar>
|
||||
|
||||
<EuiBasicTable
|
||||
columns={columns}
|
||||
isSelectable
|
||||
itemId="id"
|
||||
items={sampleTableData}
|
||||
onChange={({ page, sort }: { page: PageTypes; sort: SortTypes }) => {
|
||||
setPageState(page);
|
||||
setSortState(sort);
|
||||
}}
|
||||
pagination={{
|
||||
pageIndex: pageState.index,
|
||||
pageSize: pageState.size,
|
||||
totalItemCount: itemsTotalState,
|
||||
pageSizeOptions: [5, 10, 20],
|
||||
}}
|
||||
selection={{
|
||||
selectable: (item: ColumnTypes) => item.status !== 'Completed',
|
||||
selectableMessage: (selectable: boolean) =>
|
||||
selectable ? undefined : 'Completed runs cannot be acted upon',
|
||||
onSelectionChange: (selectedItems: ColumnTypes[]) => {
|
||||
// setSelectedState(selectedItems);
|
||||
},
|
||||
}}
|
||||
sorting={{
|
||||
sort: sortState,
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
});
|
||||
ActivityMonitor.displayName = 'ActivityMonitor';
|
||||
|
||||
export const RuleDetailsComponent = React.memo(() => {
|
||||
const [popoverState, setPopoverState] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist, indexPattern }) => {
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar id="global" indexPattern={indexPattern} />
|
||||
</FiltersGlobal>
|
||||
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
backOptions={{ href: '#detection-engine/rules', text: 'Back to rules' }}
|
||||
badgeOptions={{ text: 'Experimental' }}
|
||||
border
|
||||
subtitle={[
|
||||
'Created by: mmarcialis on 12/28/2019, 12:00 PM',
|
||||
'Updated by: agoldstein on 12/28/2019, 12:00 PM',
|
||||
]}
|
||||
subtitle2={[
|
||||
'Last signal: 23 minutes ago',
|
||||
<ProgressInline current={95000} max={105000} unit="events">
|
||||
{'Status: Running'}
|
||||
</ProgressInline>,
|
||||
]}
|
||||
title="Automated exfiltration"
|
||||
>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch checked={true} label="Activate rule" onChange={() => {}} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href="#detection-engine/rules/rule-details/edit-rule"
|
||||
iconType="visControls"
|
||||
>
|
||||
{'Edit rule settings'}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="Additional actions"
|
||||
iconType="boxesHorizontal"
|
||||
onClick={() => setPopoverState(!popoverState)}
|
||||
/>
|
||||
}
|
||||
closePopover={() => setPopoverState(false)}
|
||||
isOpen={popoverState}
|
||||
>
|
||||
<p>{'Overflow context menu here.'}</p>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</HeaderPage>
|
||||
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
size="s"
|
||||
title="Rule failed to run on 12/28/2019, 12:00 PM"
|
||||
>
|
||||
<p>{'Full fail message here.'}</p>
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem component="section" grow={1}>
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Definition" />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem component="section" grow={2}>
|
||||
<EuiPanel>
|
||||
<HeaderSection title="About" />
|
||||
|
||||
{/* <p>{'Description'}</p> */}
|
||||
|
||||
{/* <EuiFlexGrid columns={2}>
|
||||
<EuiFlexItem style={{ flex: '0 0 calc(100% - 24px)' }}>
|
||||
<p>{'Description'}</p>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<p>{'Severity'}</p>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<p>{'Risk score boost'}</p>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<p>{'References'}</p>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<p>{'False positives'}</p>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<p>{'Mitre ATT&CK types'}</p>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<p>{'Tags'}</p>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid> */}
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem component="section" grow={1}>
|
||||
<EuiPanel>
|
||||
<HeaderSection title="Schedule" />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiTabbedContent
|
||||
tabs={[
|
||||
{
|
||||
id: 'tabSignals',
|
||||
name: 'Signals',
|
||||
content: <Signals />,
|
||||
},
|
||||
{
|
||||
id: 'tabActivityMonitor',
|
||||
name: 'Activity monitor',
|
||||
content: <ActivityMonitor />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={i18n.PAGE_TITLE} />
|
||||
|
||||
<DetectionEngineEmptyPage />
|
||||
</WrapperPage>
|
||||
);
|
||||
}}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
});
|
||||
RuleDetailsComponent.displayName = 'RuleDetailsComponent';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails.pageTitle', {
|
||||
defaultMessage: 'Rule details',
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', {
|
||||
defaultMessage: 'Rules',
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', {
|
||||
defaultMessage: 'Detection engine',
|
||||
});
|
||||
|
||||
export const PAGE_SUBTITLE = i18n.translate('xpack.siem.detectionEngine.pageSubtitle', {
|
||||
defaultMessage: 'Last signal: X minutes ago',
|
||||
});
|
||||
|
||||
export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', {
|
||||
defaultMessage: 'Manage rules',
|
||||
});
|
||||
|
||||
export const PANEL_SUBTITLE_SHOWING = i18n.translate(
|
||||
'xpack.siem.detectionEngine.panelSubtitleShowing',
|
||||
{
|
||||
defaultMessage: 'Showing',
|
||||
}
|
||||
);
|
||||
|
||||
export const EMPTY_TITLE = i18n.translate('xpack.siem.detectionEngine.emptyTitle', {
|
||||
defaultMessage:
|
||||
'It looks like you don’t have any indices relevant to the detction engine in the SIEM application',
|
||||
});
|
||||
|
||||
export const EMPTY_ACTION_PRIMARY = i18n.translate(
|
||||
'xpack.siem.detectionEngine.emptyActionPrimary',
|
||||
{
|
||||
defaultMessage: 'View setup instructions',
|
||||
}
|
||||
);
|
||||
|
||||
export const EMPTY_ACTION_SECONDARY = i18n.translate(
|
||||
'xpack.siem.detectionEngine.emptyActionSecondary',
|
||||
{
|
||||
defaultMessage: 'Go to documentation',
|
||||
}
|
||||
);
|
|
@ -3,14 +3,16 @@
|
|||
* 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 i18n from './translations';
|
||||
import { SiemPageName, SiemNavTab } from './types';
|
||||
|
||||
import {
|
||||
getDetectionEngineUrl,
|
||||
getOverviewUrl,
|
||||
getNetworkUrl,
|
||||
getTimelinesUrl,
|
||||
getHostsUrl,
|
||||
} from '../../components/link_to';
|
||||
import * as i18n from './translations';
|
||||
import { SiemPageName, SiemNavTab } from './types';
|
||||
|
||||
export const navTabs: SiemNavTab = {
|
||||
[SiemPageName.overview]: {
|
||||
|
@ -34,6 +36,13 @@ export const navTabs: SiemNavTab = {
|
|||
disabled: false,
|
||||
urlKey: 'network',
|
||||
},
|
||||
[SiemPageName.detectionEngine]: {
|
||||
id: SiemPageName.detectionEngine,
|
||||
name: i18n.DETECTION_ENGINE,
|
||||
href: getDetectionEngineUrl(),
|
||||
disabled: false,
|
||||
urlKey: 'detection-engine',
|
||||
},
|
||||
[SiemPageName.timelines]: {
|
||||
id: SiemPageName.timelines,
|
||||
name: i18n.TIMELINES,
|
||||
|
|
|
@ -4,35 +4,32 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
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 { i18n } from '@kbn/i18n';
|
||||
|
||||
import { AutoSizer } from '../../components/auto_sizer';
|
||||
import { DragDropContextWrapper } from '../../components/drag_and_drop/drag_drop_context_wrapper';
|
||||
import { Flyout, flyoutHeaderHeight } from '../../components/flyout';
|
||||
import { HeaderGlobal } from '../../components/header_global';
|
||||
import { HelpMenu } from '../../components/help_menu';
|
||||
import { LinkToPage } from '../../components/link_to';
|
||||
import { SiemNavigation } from '../../components/navigation';
|
||||
import { MlHostConditionalContainer } from '../../components/ml/conditional_links/ml_host_conditional_container';
|
||||
import { MlNetworkConditionalContainer } from '../../components/ml/conditional_links/ml_network_conditional_container';
|
||||
import { StatefulTimeline } from '../../components/timeline';
|
||||
import { AutoSaveWarningMsg } from '../../components/timeline/auto_save_warning';
|
||||
import { UseUrlState } from '../../components/url_state';
|
||||
import { WithSource } from '../../containers/source';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
import { NotFoundPage } from '../404';
|
||||
import { DetectionEngineContainer } from '../detection_engine';
|
||||
import { HostsContainer } from '../hosts';
|
||||
import { NetworkContainer } from '../network';
|
||||
import { Overview } from '../overview';
|
||||
import { Timelines } from '../timelines';
|
||||
import { WithSource } from '../../containers/source';
|
||||
import { MlPopover } from '../../components/ml_popover/ml_popover';
|
||||
import { MlHostConditionalContainer } from '../../components/ml/conditional_links/ml_host_conditional_container';
|
||||
import { MlNetworkConditionalContainer } from '../../components/ml/conditional_links/ml_network_conditional_container';
|
||||
import { navTabs } from './home_navigations';
|
||||
import { SiemPageName } from './types';
|
||||
import { UseUrlState } from '../../components/url_state';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
|
||||
/*
|
||||
* This is import is important to keep because if we do not have it
|
||||
|
@ -44,30 +41,8 @@ import 'uiExports/embeddableFactories';
|
|||
const WrappedByAutoSizer = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
WrappedByAutoSizer.displayName = 'WrappedByAutoSizer';
|
||||
|
||||
const gutterTimeline = '70px'; // Temporary until timeline is moved - MichaelMarcialis
|
||||
|
||||
const Page = styled(EuiPage)`
|
||||
${({ theme }) => `
|
||||
padding: 0 ${gutterTimeline} ${theme.eui.euiSizeL} ${theme.eui.euiSizeL};
|
||||
`}
|
||||
`;
|
||||
|
||||
Page.displayName = 'Page';
|
||||
|
||||
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};
|
||||
`}
|
||||
`;
|
||||
|
||||
NavGlobal.displayName = 'NavGlobal';
|
||||
|
||||
const usersViewing = ['elastic']; // TODO: get the users viewing this timeline from Elasticsearch (persistance)
|
||||
|
||||
/** the global Kibana navigation at the top of every page */
|
||||
|
@ -85,8 +60,9 @@ export const HomePage = pure(() => (
|
|||
<AutoSizer detectAnyWindowResize={true} content>
|
||||
{({ measureRef, windowMeasurement: { height: windowHeight = 0 } }) => (
|
||||
<WrappedByAutoSizer data-test-subj="wrapped-by-auto-sizer" innerRef={measureRef}>
|
||||
<Page data-test-subj="pageContainer">
|
||||
<HelpMenu />
|
||||
<HeaderGlobal />
|
||||
|
||||
<main data-test-subj="pageContainer">
|
||||
<WithSource sourceId="default">
|
||||
{({ browserFields, indexPattern }) => (
|
||||
<DragDropContextWrapper browserFields={browserFields}>
|
||||
|
@ -111,90 +87,59 @@ export const HomePage = pure(() => (
|
|||
/>
|
||||
</Flyout>
|
||||
|
||||
<EuiPageBody>
|
||||
<NavGlobal
|
||||
aria-label={i18n.translate('xpack.siem.global.navigationLabel', {
|
||||
defaultMessage: 'SIEM app',
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<SiemNavigation navTabs={navTabs} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
wrap={true}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<MlPopover />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="add-data"
|
||||
href="kibana#home/tutorial_directory/siem"
|
||||
iconType="plusInCircle"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.siem.global.addData"
|
||||
defaultMessage="Add data"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</NavGlobal>
|
||||
<Switch>
|
||||
<Redirect from="/" exact={true} to={`/${SiemPageName.overview}`} />
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.overview})`}
|
||||
render={() => <Overview />}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.hosts})`}
|
||||
render={({ match, location }) => (
|
||||
<HostsContainer url={match.url} location={location} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.network})`}
|
||||
render={({ match, location }) => (
|
||||
<NetworkContainer url={match.url} location={location} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.timelines})`}
|
||||
render={() => <Timelines />}
|
||||
/>
|
||||
<Route path="/link-to" component={LinkToPage} />
|
||||
<Route
|
||||
path="/ml-hosts"
|
||||
render={({ match, location }) => (
|
||||
<MlHostConditionalContainer url={match.url} location={location} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/ml-network"
|
||||
render={({ match, location }) => (
|
||||
<MlNetworkConditionalContainer url={match.url} location={location} />
|
||||
)}
|
||||
/>
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</EuiPageBody>
|
||||
<Switch>
|
||||
<Redirect exact from="/" to={`/${SiemPageName.overview}`} />
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.overview})`}
|
||||
render={() => <Overview />}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.hosts})`}
|
||||
render={({ location, match }) => (
|
||||
<HostsContainer location={location} url={match.url} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.network})`}
|
||||
render={({ location, match }) => (
|
||||
<NetworkContainer location={location} url={match.url} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.detectionEngine})`}
|
||||
render={({ location, match }) => (
|
||||
<DetectionEngineContainer location={location} url={match.url} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.timelines})`}
|
||||
render={() => <Timelines />}
|
||||
/>
|
||||
<Route path="/link-to" component={LinkToPage} />
|
||||
<Route
|
||||
path="/ml-hosts"
|
||||
render={({ location, match }) => (
|
||||
<MlHostConditionalContainer location={location} url={match.url} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/ml-network"
|
||||
render={({ location, match }) => (
|
||||
<MlNetworkConditionalContainer location={location} url={match.url} />
|
||||
)}
|
||||
/>
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</DragDropContextWrapper>
|
||||
)}
|
||||
</WithSource>
|
||||
</Page>
|
||||
</main>
|
||||
|
||||
<HelpMenu />
|
||||
|
||||
<SpyRoute />
|
||||
</WrappedByAutoSizer>
|
||||
)}
|
||||
</AutoSizer>
|
||||
));
|
||||
|
||||
HomePage.displayName = 'HomePage';
|
||||
|
|
|
@ -18,6 +18,10 @@ export const NETWORK = i18n.translate('xpack.siem.navigation.network', {
|
|||
defaultMessage: 'Network',
|
||||
});
|
||||
|
||||
export const DETECTION_ENGINE = i18n.translate('xpack.siem.navigation.detectionEngine', {
|
||||
defaultMessage: 'Detection engine',
|
||||
});
|
||||
|
||||
export const TIMELINES = i18n.translate('xpack.siem.navigation.timelines', {
|
||||
defaultMessage: 'Timelines',
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ export enum SiemPageName {
|
|||
overview = 'overview',
|
||||
hosts = 'hosts',
|
||||
network = 'network',
|
||||
detectionEngine = 'detection-engine',
|
||||
timelines = 'timelines',
|
||||
}
|
||||
|
||||
|
@ -17,6 +18,7 @@ export type SiemNavTabKey =
|
|||
| SiemPageName.overview
|
||||
| SiemPageName.hosts
|
||||
| SiemPageName.network
|
||||
| SiemPageName.detectionEngine
|
||||
| SiemPageName.timelines;
|
||||
|
||||
export type SiemNavTab = Record<SiemNavTabKey, NavTab>;
|
||||
|
|
|
@ -6,39 +6,40 @@
|
|||
|
||||
import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { compose } from 'redux';
|
||||
|
||||
import { inputsSelectors, State } from '../../../store';
|
||||
import { FiltersGlobal } from '../../../components/filters_global';
|
||||
import { HeaderPage } from '../../../components/header_page';
|
||||
import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details';
|
||||
import { LastEventTime } from '../../../components/last_event_time';
|
||||
import { hostToCriteria } from '../../../components/ml/criteria/host_to_criteria';
|
||||
import { MlCapabilitiesContext } from '../../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { hasMlUserPermissions } from '../../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { AnomalyTableProvider } from '../../../components/ml/anomaly/anomaly_table_provider';
|
||||
import { hostToCriteria } from '../../../components/ml/criteria/host_to_criteria';
|
||||
import { hasMlUserPermissions } from '../../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime';
|
||||
import { setHostDetailsTablesActivePageToZero as dispatchHostDetailsTablesActivePageToZero } from '../../../store/hosts/actions';
|
||||
import { SiemNavigation } from '../../../components/navigation';
|
||||
import { manageQuery } from '../../../components/page/manage_query';
|
||||
import { HostOverview } from '../../../components/page/hosts/host_overview';
|
||||
import { KpiHostsComponent } from '../../../components/page/hosts';
|
||||
import { HostOverview } from '../../../components/page/hosts/host_overview';
|
||||
import { manageQuery } from '../../../components/page/manage_query';
|
||||
import { SiemSearchBar } from '../../../components/search_bar';
|
||||
import { WrapperPage } from '../../../components/wrapper_page';
|
||||
import { HostOverviewByNameQuery } from '../../../containers/hosts/overview';
|
||||
import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source';
|
||||
import { LastEventIndexKey } from '../../../graphql/types';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { inputsSelectors, State } from '../../../store';
|
||||
import { setHostDetailsTablesActivePageToZero as dispatchHostDetailsTablesActivePageToZero } from '../../../store/hosts/actions';
|
||||
import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions';
|
||||
import { SpyRoute } from '../../../utils/route/spy_routes';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { esQuery } from '../../../../../../../../src/plugins/data/public';
|
||||
|
||||
import { HostsEmptyPage } from '../hosts_empty_page';
|
||||
import { HostDetailsTabs } from './details_tabs';
|
||||
import { navTabsHostDetails } from './nav_tabs';
|
||||
import { HostDetailsComponentProps, HostDetailsProps } from './types';
|
||||
import { HostDetailsTabs } from './details_tabs';
|
||||
import { type } from './utils';
|
||||
|
||||
const HostOverviewManage = manageQuery(HostOverview);
|
||||
|
@ -63,6 +64,7 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
|
|||
}, [detailName]);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const core = useKibanaCore();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithSource sourceId="default">
|
||||
|
@ -96,14 +98,16 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
|
|||
...filters,
|
||||
],
|
||||
});
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<>
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
border
|
||||
subtitle={
|
||||
<LastEventTime
|
||||
indexKey={LastEventIndexKey.hostDetails}
|
||||
|
@ -112,6 +116,7 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
|
|||
}
|
||||
title={detailName}
|
||||
/>
|
||||
|
||||
<HostOverviewByNameQuery
|
||||
sourceId="default"
|
||||
hostName={detailName}
|
||||
|
@ -182,40 +187,40 @@ const HostDetailsComponent = React.memo<HostDetailsComponentProps>(
|
|||
|
||||
<SiemNavigation
|
||||
navTabs={navTabsHostDetails(detailName, hasMlUserPermissions(capabilities))}
|
||||
display="default"
|
||||
showBorder={true}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
</StickyContainer>
|
||||
<HostDetailsTabs
|
||||
isInitializing={isInitializing}
|
||||
deleteQuery={deleteQuery}
|
||||
to={to}
|
||||
from={from}
|
||||
detailName={detailName}
|
||||
type={type}
|
||||
setQuery={setQuery}
|
||||
filterQuery={filterQuery}
|
||||
hostDetailsPagePath={hostDetailsPagePath}
|
||||
indexPattern={indexPattern}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
|
||||
/>
|
||||
</>
|
||||
|
||||
<HostDetailsTabs
|
||||
isInitializing={isInitializing}
|
||||
deleteQuery={deleteQuery}
|
||||
to={to}
|
||||
from={from}
|
||||
detailName={detailName}
|
||||
type={type}
|
||||
setQuery={setQuery}
|
||||
filterQuery={filterQuery}
|
||||
hostDetailsPagePath={hostDetailsPagePath}
|
||||
indexPattern={indexPattern}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
|
||||
/>
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<>
|
||||
<HeaderPage title={detailName} />
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={detailName} />
|
||||
|
||||
<HostsEmptyPage />
|
||||
</>
|
||||
</WrapperPage>
|
||||
);
|
||||
}}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
HostDetailsComponent.displayName = 'HostDetailsComponent';
|
||||
|
||||
export const makeMapStateToProps = () => {
|
||||
|
|
|
@ -6,35 +6,35 @@
|
|||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
import { compose } from 'redux';
|
||||
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { GlobalTimeArgs } from '../../containers/global_time';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { KpiHostsQuery } from '../../containers/kpi_hosts';
|
||||
import { LastEventTime } from '../../components/last_event_time';
|
||||
import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { SiemNavigation } from '../../components/navigation';
|
||||
import { KpiHostsComponent } from '../../components/page/hosts';
|
||||
import { manageQuery } from '../../components/page/manage_query';
|
||||
import { hasMlUserPermissions } from '../../components/ml/permissions/has_ml_user_permissions';
|
||||
import { MlCapabilitiesContext } from '../../components/ml/permissions/ml_capabilities_provider';
|
||||
import { SiemSearchBar } from '../../components/search_bar';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { GlobalTimeArgs } from '../../containers/global_time';
|
||||
import { KpiHostsQuery } from '../../containers/kpi_hosts';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { LastEventIndexKey } from '../../graphql/types';
|
||||
import { useKibanaCore } from '../../lib/compose/kibana_core';
|
||||
import { convertToBuildEsQuery } from '../../lib/keury';
|
||||
import { inputsSelectors, State, hostsModel } from '../../store';
|
||||
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
import { useKibanaCore } from '../../lib/compose/kibana_core';
|
||||
|
||||
import { esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { HostsEmptyPage } from './hosts_empty_page';
|
||||
import { HostsTabs } from './hosts_tabs';
|
||||
import { navTabsHosts } from './nav_tabs';
|
||||
import * as i18n from './translations';
|
||||
import { HostsComponentProps, HostsComponentReduxProps } from './types';
|
||||
import { HostsTabs } from './hosts_tabs';
|
||||
|
||||
const KpiHostsComponentManage = manageQuery(KpiHostsComponent);
|
||||
|
||||
|
@ -64,76 +64,77 @@ const HostsComponent = React.memo<HostsComponentProps>(
|
|||
filters,
|
||||
});
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<>
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
border
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.hosts} />}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
<>
|
||||
<KpiHostsQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiHosts, loading, id, inspect, refetch }) => (
|
||||
<KpiHostsComponentManage
|
||||
data={kpiHosts}
|
||||
from={from}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
narrowDateRange={(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</KpiHostsQuery>
|
||||
<EuiSpacer />
|
||||
<SiemNavigation
|
||||
navTabs={navTabsHosts(hasMlUserPermissions(capabilities))}
|
||||
display="default"
|
||||
showBorder={true}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
</StickyContainer>
|
||||
<HostsTabs
|
||||
deleteQuery={deleteQuery}
|
||||
to={to}
|
||||
filterQuery={filterQuery}
|
||||
isInitializing={isInitializing}
|
||||
setQuery={setQuery}
|
||||
from={from}
|
||||
type={hostsModel.HostsType.page}
|
||||
indexPattern={indexPattern}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
|
||||
hostsPagePath={hostsPagePath}
|
||||
/>
|
||||
</>
|
||||
|
||||
<KpiHostsQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiHosts, loading, id, inspect, refetch }) => (
|
||||
<KpiHostsComponentManage
|
||||
data={kpiHosts}
|
||||
from={from}
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
narrowDateRange={(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</KpiHostsQuery>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<SiemNavigation navTabs={navTabsHosts(hasMlUserPermissions(capabilities))} />
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<HostsTabs
|
||||
deleteQuery={deleteQuery}
|
||||
to={to}
|
||||
filterQuery={filterQuery}
|
||||
isInitializing={isInitializing}
|
||||
setQuery={setQuery}
|
||||
from={from}
|
||||
type={hostsModel.HostsType.page}
|
||||
indexPattern={indexPattern}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
|
||||
hostsPagePath={hostsPagePath}
|
||||
/>
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<>
|
||||
<HeaderPage title={i18n.PAGE_TITLE} />
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={i18n.PAGE_TITLE} />
|
||||
|
||||
<HostsEmptyPage />
|
||||
</>
|
||||
</WrapperPage>
|
||||
);
|
||||
}}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
HostsComponent.displayName = 'HostsComponent';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
|
|
@ -163,7 +163,7 @@ describe('Ip Details', () => {
|
|||
wrapper.update();
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="ip-details-headline"] [data-test-subj="page_headline_title"]')
|
||||
.find('[data-test-subj="ip-details-headline"] [data-test-subj="header-page-title"]')
|
||||
.text()
|
||||
).toEqual('fe80::24ce:f7ff:fede:a571');
|
||||
});
|
||||
|
|
|
@ -20,9 +20,11 @@ import { manageQuery } from '../../../components/page/manage_query';
|
|||
import { FlowTargetSelectConnected } from '../../../components/page/network/flow_target_select_connected';
|
||||
import { IpOverview } from '../../../components/page/network/ip_overview';
|
||||
import { SiemSearchBar } from '../../../components/search_bar';
|
||||
import { WrapperPage } from '../../../components/wrapper_page';
|
||||
import { IpOverviewQuery } from '../../../containers/ip_overview';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source';
|
||||
import { FlowTargetSourceDest, LastEventIndexKey } from '../../../graphql/types';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
import { decodeIpv6 } from '../../../lib/helpers';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { ConditionalFlexGroup } from '../../../pages/network/navigation/conditional_flex_group';
|
||||
|
@ -31,16 +33,15 @@ import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '.
|
|||
import { setIpDetailsTablesActivePageToZero as dispatchIpDetailsTablesActivePageToZero } from '../../../store/network/actions';
|
||||
import { SpyRoute } from '../../../utils/route/spy_routes';
|
||||
import { NetworkEmptyPage } from '../network_empty_page';
|
||||
|
||||
import { IPDetailsComponentProps } from './types';
|
||||
export { getBreadcrumbs } from './utils';
|
||||
import { TlsQueryTable } from './tls_query_table';
|
||||
import { UsersQueryTable } from './users_query_table';
|
||||
import { NetworkTopNFlowQueryTable } from './network_top_n_flow_query_table';
|
||||
import { NetworkHttpQueryTable } from './network_http_query_table';
|
||||
import { NetworkTopCountriesQueryTable } from './network_top_countries_query_table';
|
||||
import { NetworkTopNFlowQueryTable } from './network_top_n_flow_query_table';
|
||||
import { TlsQueryTable } from './tls_query_table';
|
||||
import { IPDetailsComponentProps } from './types';
|
||||
import { UsersQueryTable } from './users_query_table';
|
||||
import { esQuery } from '../../../../../../../../src/plugins/data/public';
|
||||
import { useKibanaCore } from '../../../lib/compose/kibana_core';
|
||||
|
||||
export { getBreadcrumbs } from './utils';
|
||||
|
||||
const IpOverviewManage = manageQuery(IpOverview);
|
||||
|
||||
|
@ -85,193 +86,197 @@ export const IPDetailsComponent = React.memo<IPDetailsComponentProps>(
|
|||
queries: [query],
|
||||
filters,
|
||||
});
|
||||
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
|
||||
<HeaderPage
|
||||
data-test-subj="ip-details-headline"
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.ipDetails} ip={ip} />}
|
||||
title={ip}
|
||||
draggableArguments={{ field: `${flowTarget}.ip`, value: ip }}
|
||||
>
|
||||
<FlowTargetSelectConnected />
|
||||
</HeaderPage>
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
border
|
||||
data-test-subj="ip-details-headline"
|
||||
draggableArguments={{ field: `${flowTarget}.ip`, value: ip }}
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.ipDetails} ip={ip} />}
|
||||
title={ip}
|
||||
>
|
||||
<FlowTargetSelectConnected />
|
||||
</HeaderPage>
|
||||
|
||||
<IpOverviewQuery
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
filterQuery={filterQuery}
|
||||
type={networkModel.NetworkType.details}
|
||||
ip={ip}
|
||||
>
|
||||
{({ id, inspect, ipOverviewData, loading, refetch }) => (
|
||||
<AnomalyTableProvider
|
||||
criteriaFields={networkToCriteria(detailName, flowTarget)}
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
<IpOverviewManage
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
ip={ip}
|
||||
data={ipOverviewData}
|
||||
anomaliesData={anomaliesData}
|
||||
loading={loading}
|
||||
isLoadingAnomaliesData={isLoadingAnomaliesData}
|
||||
type={networkModel.NetworkType.details}
|
||||
flowTarget={flowTarget}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
id: 'global',
|
||||
from: fromTo.from,
|
||||
to: fromTo.to,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
)}
|
||||
</IpOverviewQuery>
|
||||
<IpOverviewQuery
|
||||
skip={isInitializing}
|
||||
sourceId="default"
|
||||
filterQuery={filterQuery}
|
||||
type={networkModel.NetworkType.details}
|
||||
ip={ip}
|
||||
>
|
||||
{({ id, inspect, ipOverviewData, loading, refetch }) => (
|
||||
<AnomalyTableProvider
|
||||
criteriaFields={networkToCriteria(detailName, flowTarget)}
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
>
|
||||
{({ isLoadingAnomaliesData, anomaliesData }) => (
|
||||
<IpOverviewManage
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
ip={ip}
|
||||
data={ipOverviewData}
|
||||
anomaliesData={anomaliesData}
|
||||
loading={loading}
|
||||
isLoadingAnomaliesData={isLoadingAnomaliesData}
|
||||
type={networkModel.NetworkType.details}
|
||||
flowTarget={flowTarget}
|
||||
refetch={refetch}
|
||||
setQuery={setQuery}
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
narrowDateRange={(score, interval) => {
|
||||
const fromTo = scoreIntervalToDateTime(score, interval);
|
||||
setAbsoluteRangeDatePicker({
|
||||
id: 'global',
|
||||
from: fromTo.from,
|
||||
to: fromTo.to,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnomalyTableProvider>
|
||||
)}
|
||||
</IpOverviewQuery>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<ConditionalFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<NetworkTopNFlowQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={FlowTargetSourceDest.source}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<ConditionalFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<NetworkTopNFlowQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={FlowTargetSourceDest.source}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<NetworkTopNFlowQueryTable
|
||||
endDate={to}
|
||||
flowTarget={FlowTargetSourceDest.destination}
|
||||
filterQuery={filterQuery}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ConditionalFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<NetworkTopNFlowQueryTable
|
||||
endDate={to}
|
||||
flowTarget={FlowTargetSourceDest.destination}
|
||||
filterQuery={filterQuery}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ConditionalFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<ConditionalFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<NetworkTopCountriesQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={FlowTargetSourceDest.source}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<ConditionalFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<NetworkTopCountriesQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={FlowTargetSourceDest.source}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<NetworkTopCountriesQueryTable
|
||||
endDate={to}
|
||||
flowTarget={FlowTargetSourceDest.destination}
|
||||
filterQuery={filterQuery}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ConditionalFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<NetworkTopCountriesQueryTable
|
||||
endDate={to}
|
||||
flowTarget={FlowTargetSourceDest.destination}
|
||||
filterQuery={filterQuery}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</ConditionalFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<UsersQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={flowTarget}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
<UsersQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={flowTarget}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<NetworkHttpQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
<NetworkHttpQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
ip={ip}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<TlsQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={(flowTarget as unknown) as FlowTargetSourceDest}
|
||||
ip={ip}
|
||||
setQuery={setQuery}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
/>
|
||||
<TlsQueryTable
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
flowTarget={(flowTarget as unknown) as FlowTargetSourceDest}
|
||||
ip={ip}
|
||||
setQuery={setQuery}
|
||||
skip={isInitializing}
|
||||
startDate={from}
|
||||
type={networkModel.NetworkType.details}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<AnomaliesNetworkTable
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
ip={ip}
|
||||
type={networkModel.NetworkType.details}
|
||||
flowTarget={flowTarget}
|
||||
narrowDateRange={narrowDateRange}
|
||||
/>
|
||||
<AnomaliesNetworkTable
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
skip={isInitializing}
|
||||
ip={ip}
|
||||
type={networkModel.NetworkType.details}
|
||||
flowTarget={flowTarget}
|
||||
narrowDateRange={narrowDateRange}
|
||||
/>
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<>
|
||||
<HeaderPage title={ip} />
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={ip} />
|
||||
|
||||
<NetworkEmptyPage />
|
||||
</>
|
||||
</WrapperPage>
|
||||
);
|
||||
}}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
IPDetailsComponent.displayName = 'IPDetailsComponent';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import { SiemNavigation } from '../../components/navigation';
|
|||
import { manageQuery } from '../../components/page/manage_query';
|
||||
import { KpiNetworkComponent } from '../../components/page/network';
|
||||
import { SiemSearchBar } from '../../components/search_bar';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { KpiNetworkQuery } from '../../containers/kpi_network';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { LastEventIndexKey } from '../../graphql/types';
|
||||
|
@ -48,6 +49,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
|
|||
capabilitiesFetched,
|
||||
}) => {
|
||||
const core = useKibanaCore();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithSource sourceId={sourceId}>
|
||||
|
@ -58,95 +60,95 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
|
|||
queries: [query],
|
||||
filters,
|
||||
});
|
||||
|
||||
return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<StickyContainer>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar indexPattern={indexPattern} id="global" />
|
||||
</FiltersGlobal>
|
||||
|
||||
<HeaderPage
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
border
|
||||
subtitle={<LastEventTime indexKey={LastEventIndexKey.network} />}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
|
||||
<EmbeddedMap
|
||||
query={query}
|
||||
filters={filters}
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
<EmbeddedMap
|
||||
query={query}
|
||||
filters={filters}
|
||||
startDate={from}
|
||||
endDate={to}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
|
||||
<KpiNetworkQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId={sourceId}
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiNetwork, loading, id, inspect, refetch }) => (
|
||||
<KpiNetworkComponentManage
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
data={kpiNetwork}
|
||||
loading={loading}
|
||||
from={from}
|
||||
to={to}
|
||||
narrowDateRange={(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
|
||||
}}
|
||||
/>
|
||||
<KpiNetworkQuery
|
||||
endDate={to}
|
||||
filterQuery={filterQuery}
|
||||
skip={isInitializing}
|
||||
sourceId={sourceId}
|
||||
startDate={from}
|
||||
>
|
||||
{({ kpiNetwork, loading, id, inspect, refetch }) => (
|
||||
<KpiNetworkComponentManage
|
||||
id={id}
|
||||
inspect={inspect}
|
||||
setQuery={setQuery}
|
||||
refetch={refetch}
|
||||
data={kpiNetwork}
|
||||
loading={loading}
|
||||
from={from}
|
||||
to={to}
|
||||
narrowDateRange={(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</KpiNetworkQuery>
|
||||
|
||||
{capabilitiesFetched && !isInitializing ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<SiemNavigation navTabs={navTabsNetwork(hasMlUserPermissions)} />
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<NetworkRoutes
|
||||
to={to}
|
||||
filterQuery={filterQuery}
|
||||
isInitializing={isInitializing}
|
||||
from={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
indexPattern={indexPattern}
|
||||
setQuery={setQuery}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
|
||||
networkPagePath={networkPagePath}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<NetworkRoutesLoading />
|
||||
)}
|
||||
</KpiNetworkQuery>
|
||||
|
||||
{capabilitiesFetched && !isInitializing ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<SiemNavigation
|
||||
navTabs={navTabsNetwork(hasMlUserPermissions)}
|
||||
display={sourceId}
|
||||
showBorder={true}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<NetworkRoutes
|
||||
to={to}
|
||||
filterQuery={filterQuery}
|
||||
isInitializing={isInitializing}
|
||||
from={from}
|
||||
type={networkModel.NetworkType.page}
|
||||
indexPattern={indexPattern}
|
||||
setQuery={setQuery}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
|
||||
networkPagePath={networkPagePath}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<NetworkRoutesLoading />
|
||||
)}
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiSpacer />
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
) : (
|
||||
<>
|
||||
<HeaderPage title={i18n.PAGE_TITLE} />
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={i18n.PAGE_TITLE} />
|
||||
<NetworkEmptyPage />
|
||||
</>
|
||||
</WrapperPage>
|
||||
);
|
||||
}}
|
||||
</WithSource>
|
||||
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
NetworkComponent.displayName = 'NetworkComponent';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
|
|
@ -7,68 +7,72 @@
|
|||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import chrome from 'ui/chrome';
|
||||
import { documentationLinks } from 'ui/documentation_links';
|
||||
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { OverviewHost } from '../../components/page/overview/overview_host';
|
||||
import { OverviewNetwork } from '../../components/page/overview/overview_network';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { GlobalTime } from '../../containers/global_time';
|
||||
|
||||
import { Summary } from './summary';
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { WithSource, indicesExistOrDataTemporarilyUnavailable } from '../../containers/source';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
|
||||
import { Summary } from './summary';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
export const OverviewComponent = pure(() => {
|
||||
export const OverviewComponent = React.memo(() => {
|
||||
const dateEnd = Date.now();
|
||||
const dateRange = moment.duration(24, 'hours').asMilliseconds();
|
||||
const dateStart = dateEnd - dateRange;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderPage
|
||||
badgeLabel={i18n.PAGE_BADGE_LABEL}
|
||||
badgeTooltip={i18n.PAGE_BADGE_TOOLTIP}
|
||||
subtitle={i18n.PAGE_SUBTITLE}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
badgeOptions={{
|
||||
beta: true,
|
||||
text: i18n.PAGE_BADGE_LABEL,
|
||||
tooltip: i18n.PAGE_BADGE_TOOLTIP,
|
||||
}}
|
||||
border
|
||||
subtitle={i18n.PAGE_SUBTITLE}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<GlobalTime>
|
||||
{({ setQuery }) => (
|
||||
<EuiFlexGroup>
|
||||
<Summary />
|
||||
<OverviewHost endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
<OverviewNetwork endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</GlobalTime>
|
||||
) : (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="gear"
|
||||
actionPrimaryLabel={i18n.EMPTY_ACTION_PRIMARY}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={documentationLinks.siem}
|
||||
data-test-subj="empty-page"
|
||||
title={i18n.EMPTY_TITLE}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
</WrapperPage>
|
||||
|
||||
<WithSource sourceId="default">
|
||||
{({ indicesExist }) =>
|
||||
indicesExistOrDataTemporarilyUnavailable(indicesExist) ? (
|
||||
<GlobalTime>
|
||||
{({ setQuery }) => (
|
||||
<EuiFlexGroup>
|
||||
<Summary />
|
||||
<OverviewHost endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
<OverviewNetwork endDate={dateEnd} startDate={dateStart} setQuery={setQuery} />
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</GlobalTime>
|
||||
) : (
|
||||
<EmptyPage
|
||||
actionPrimaryIcon="gear"
|
||||
actionPrimaryLabel={i18n.EMPTY_ACTION_PRIMARY}
|
||||
actionPrimaryUrl={`${basePath}/app/kibana#/home/tutorial_directory/siem`}
|
||||
actionSecondaryIcon="popout"
|
||||
actionSecondaryLabel={i18n.EMPTY_ACTION_SECONDARY}
|
||||
actionSecondaryTarget="_blank"
|
||||
actionSecondaryUrl={documentationLinks.siem}
|
||||
data-test-subj="empty-page"
|
||||
title={i18n.EMPTY_TITLE}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
<SpyRoute />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
OverviewComponent.displayName = 'OverviewComponent';
|
||||
|
|
|
@ -10,14 +10,13 @@ import styled from 'styled-components';
|
|||
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { StatefulOpenTimeline } from '../../components/open_timeline';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
const TimelinesContainer = styled.div`
|
||||
width: 100%:
|
||||
`;
|
||||
|
||||
TimelinesContainer.displayName = 'TimelinesContainer';
|
||||
|
||||
interface TimelinesProps<TCache = object> {
|
||||
|
@ -30,16 +29,19 @@ export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10;
|
|||
|
||||
export const TimelinesPage = React.memo<OwnProps>(({ apolloClient }) => (
|
||||
<>
|
||||
<HeaderPage title={i18n.PAGE_TITLE} />
|
||||
<WrapperPage>
|
||||
<HeaderPage border title={i18n.PAGE_TITLE} />
|
||||
|
||||
<TimelinesContainer>
|
||||
<StatefulOpenTimeline
|
||||
apolloClient={apolloClient}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
isModal={false}
|
||||
title={i18n.ALL_TIMELINES_PANEL_TITLE}
|
||||
/>
|
||||
</TimelinesContainer>
|
||||
</WrapperPage>
|
||||
|
||||
<TimelinesContainer>
|
||||
<StatefulOpenTimeline
|
||||
apolloClient={apolloClient}
|
||||
defaultPageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE}
|
||||
isModal={false}
|
||||
title={i18n.ALL_TIMELINES_PANEL_TITLE}
|
||||
/>
|
||||
</TimelinesContainer>
|
||||
<SpyRoute />
|
||||
</>
|
||||
));
|
||||
|
|
|
@ -10511,7 +10511,6 @@
|
|||
"xpack.siem.formatted.duration.noDurationTooltip": "期間がありません",
|
||||
"xpack.siem.formatted.duration.zeroNanosecondsTooltip": "0ナノ秒",
|
||||
"xpack.siem.formattedDuration.tooltipLabel": "生",
|
||||
"xpack.siem.global.addData": "データの投入",
|
||||
"xpack.siem.headerPage.pageSubtitle": "前回のイベント: {beat}",
|
||||
"xpack.siem.host.details.architectureLabel": "アーキテクチャー",
|
||||
"xpack.siem.host.details.firstSeenTitle": "初回の認識",
|
||||
|
|
|
@ -10601,7 +10601,6 @@
|
|||
"xpack.siem.formatted.duration.noDurationTooltip": "无持续时间",
|
||||
"xpack.siem.formatted.duration.zeroNanosecondsTooltip": "零纳秒",
|
||||
"xpack.siem.formattedDuration.tooltipLabel": "原始",
|
||||
"xpack.siem.global.addData": "添加数据",
|
||||
"xpack.siem.headerPage.pageSubtitle": "最后事件:{beat}",
|
||||
"xpack.siem.host.details.architectureLabel": "架构",
|
||||
"xpack.siem.host.details.firstSeenTitle": "首次看到时间",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue