mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Add widgets on Hosts Page (#25404)
* get date picker popover in the right place * add two widget for hosts pages * fix spelling mistake * fix type * add pr review
This commit is contained in:
parent
34558a1e01
commit
b671b9df3f
13 changed files with 554 additions and 38 deletions
|
@ -7,7 +7,11 @@
|
|||
declare module '@elastic/eui/lib/experimental' {
|
||||
import { CommonProps } from '@elastic/eui/src/components/common';
|
||||
export type EuiSeriesChartProps = CommonProps & {
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
orientation?: string;
|
||||
xType?: string;
|
||||
yType?: string;
|
||||
stackBy?: string;
|
||||
statusText?: string;
|
||||
yDomain?: number[];
|
||||
|
@ -21,7 +25,7 @@ declare module '@elastic/eui/lib/experimental' {
|
|||
export const EuiSeriesChart: React.SFC<EuiSeriesChartProps>;
|
||||
|
||||
type EuiSeriesProps = CommonProps & {
|
||||
data: Array<{ x: number; y: number; y0?: number }>;
|
||||
data: Array<{ x: number; y: number | string; y0?: number }>;
|
||||
lineSize?: number;
|
||||
name: string;
|
||||
color?: string;
|
||||
|
|
|
@ -524,6 +524,98 @@ export namespace SayMyNameResolvers {
|
|||
>;
|
||||
}
|
||||
|
||||
export namespace GetEventsQuery {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
timerange: TimerangeInput;
|
||||
filterQuery?: string | null;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'Source';
|
||||
getEvents?: GetEvents | null;
|
||||
};
|
||||
|
||||
export type GetEvents = {
|
||||
__typename?: 'EventsData';
|
||||
events: Events[];
|
||||
kpiEventType: KpiEventType[];
|
||||
};
|
||||
|
||||
export type Events = {
|
||||
__typename?: 'EventItem';
|
||||
timestamp?: string | null;
|
||||
event?: Event | null;
|
||||
host?: Host | null;
|
||||
source?: _Source | null;
|
||||
destination?: Destination | null;
|
||||
geo?: Geo | null;
|
||||
suricata?: Suricata | null;
|
||||
};
|
||||
|
||||
export type Event = {
|
||||
__typename?: 'EventEcsFields';
|
||||
type?: string | null;
|
||||
severity?: number | null;
|
||||
module?: string | null;
|
||||
category?: string | null;
|
||||
id?: number | null;
|
||||
};
|
||||
|
||||
export type Host = {
|
||||
__typename?: 'HostEcsFields';
|
||||
hostname?: string | null;
|
||||
ip?: string | null;
|
||||
};
|
||||
|
||||
export type _Source = {
|
||||
__typename?: 'SourceEcsFields';
|
||||
ip?: string | null;
|
||||
port?: number | null;
|
||||
};
|
||||
|
||||
export type Destination = {
|
||||
__typename?: 'DestinationEcsFields';
|
||||
ip?: string | null;
|
||||
port?: number | null;
|
||||
};
|
||||
|
||||
export type Geo = {
|
||||
__typename?: 'GeoEcsFields';
|
||||
region_name?: string | null;
|
||||
country_iso_code?: string | null;
|
||||
};
|
||||
|
||||
export type Suricata = {
|
||||
__typename?: 'SuricataEcsFields';
|
||||
eve?: Eve | null;
|
||||
};
|
||||
|
||||
export type Eve = {
|
||||
__typename?: 'SuricataEveData';
|
||||
proto?: string | null;
|
||||
flow_id?: number | null;
|
||||
alert?: Alert | null;
|
||||
};
|
||||
|
||||
export type Alert = {
|
||||
__typename?: 'SuricataAlertData';
|
||||
signature?: string | null;
|
||||
signature_id?: number | null;
|
||||
};
|
||||
|
||||
export type KpiEventType = {
|
||||
__typename?: 'KpiItem';
|
||||
value: string;
|
||||
count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace WhoAmIQuery {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
|
|
131
x-pack/plugins/secops/public/components/basic_table/index.tsx
Normal file
131
x-pack/plugins/secops/public/components/basic_table/index.tsx
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { EventItem } from '../../../common/graphql/types';
|
||||
import { LoadingPanel } from '../loading';
|
||||
|
||||
export interface HoryzontalBarChartData {
|
||||
x: number;
|
||||
y: string;
|
||||
}
|
||||
|
||||
interface BasicTableProps {
|
||||
sortField: string;
|
||||
// tslint:disable-next-line:no-any
|
||||
pageOfItems: any[];
|
||||
columns: Columns[];
|
||||
title: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface BasicTableState {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
sortField: string;
|
||||
sortDirection: string;
|
||||
}
|
||||
|
||||
export interface Columns {
|
||||
field?: string;
|
||||
name: string;
|
||||
isMobileHeader?: boolean;
|
||||
sortable?: boolean;
|
||||
truncateText?: boolean;
|
||||
hideForMobile?: boolean;
|
||||
render?: (item: EventItem) => void;
|
||||
}
|
||||
|
||||
interface Page {
|
||||
index: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface Sort {
|
||||
field: string;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
interface TableChange {
|
||||
page: Page;
|
||||
sort: Sort;
|
||||
}
|
||||
|
||||
export class BasicTable extends React.PureComponent<BasicTableProps, BasicTableState> {
|
||||
public readonly state = {
|
||||
pageIndex: 0,
|
||||
pageSize: 3,
|
||||
sortField: this.props.sortField,
|
||||
sortDirection: 'asc',
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { pageIndex, pageSize, sortField, sortDirection } = this.state;
|
||||
const { columns, title, loading } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <LoadingPanel height="100%" width="100%" text={`Loading ${title}`} />;
|
||||
}
|
||||
|
||||
const pagination = {
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount: this.props.pageOfItems.length,
|
||||
pageSizeOptions: [3, 5, 8],
|
||||
hidePerPageOptions: false,
|
||||
};
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortDirection,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<BasicTableContainer>
|
||||
<EuiTitle size="s">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiBasicTable
|
||||
items={this.getCurrentItems()}
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
onChange={this.onTableChange}
|
||||
/>
|
||||
</BasicTableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
private getCurrentItems = () => {
|
||||
const { pageOfItems } = this.props;
|
||||
const { pageIndex, pageSize } = this.state;
|
||||
return pageOfItems.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
||||
};
|
||||
|
||||
private onTableChange = ({ page, sort }: TableChange) => {
|
||||
const { index: pageIndex, size: pageSize } = page;
|
||||
const { field: sortField, direction: sortDirection } = sort;
|
||||
|
||||
this.setState({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
sortField,
|
||||
sortDirection,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const BasicTableContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
`;
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { pure } from 'recompose';
|
||||
|
||||
import { EuiTitle } from '@elastic/eui';
|
||||
import {
|
||||
// @ts-ignore
|
||||
EuiBarSeries,
|
||||
// @ts-ignore
|
||||
EuiSeriesChart,
|
||||
} from '@elastic/eui/lib/experimental';
|
||||
|
||||
import { LoadingPanel } from '../loading';
|
||||
|
||||
export interface HorizontalBarChartData {
|
||||
x: number;
|
||||
y: string;
|
||||
}
|
||||
|
||||
interface HorizontalBarChartProps {
|
||||
barChartdata: HorizontalBarChartData[];
|
||||
width?: number;
|
||||
height?: number;
|
||||
title: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const HorizontalBarChart = pure<HorizontalBarChartProps>(
|
||||
({ barChartdata, width, height, title, loading }) => {
|
||||
return loading ? (
|
||||
<LoadingPanel height="100%" width="100%" text="Loading data" />
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<EuiTitle size="s">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiSeriesChart width={width} height={height} yType="ordinal" orientation="horizontal">
|
||||
<EuiBarSeries name="Tag counts" data={barChartdata} />
|
||||
</EuiSeriesChart>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
);
|
43
x-pack/plugins/secops/public/components/loading/index.tsx
Normal file
43
x-pack/plugins/secops/public/components/loading/index.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { EuiLoadingChart, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface LoadingProps {
|
||||
text: string;
|
||||
height: number | string;
|
||||
width: number | string;
|
||||
}
|
||||
|
||||
export const LoadingPanel = pure<LoadingProps>(({ height, text, width }) => (
|
||||
<InfraLoadingStaticPanel style={{ height, width }}>
|
||||
<InfraLoadingStaticContentPanel>
|
||||
<EuiPanel>
|
||||
<EuiLoadingChart size="m" />
|
||||
<EuiText>
|
||||
<p>{text}</p>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</InfraLoadingStaticContentPanel>
|
||||
</InfraLoadingStaticPanel>
|
||||
));
|
||||
|
||||
export const InfraLoadingStaticPanel = styled.div`
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const InfraLoadingStaticContentPanel = styled.div`
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
`;
|
|
@ -39,6 +39,7 @@ export const DatePicker = pure<DatePickerProps>(
|
|||
isInvalid={false}
|
||||
aria-label="Start date"
|
||||
showTimeSelect
|
||||
popperPlacement="top-end"
|
||||
/>
|
||||
}
|
||||
endDateControl={
|
||||
|
@ -48,6 +49,7 @@ export const DatePicker = pure<DatePickerProps>(
|
|||
isInvalid={false}
|
||||
aria-label="End date"
|
||||
showTimeSelect
|
||||
popperPlacement="top-end"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -28,48 +28,48 @@ export interface OwnProps {
|
|||
width: number;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
dataProviders: DataProvider[];
|
||||
data: ECS[];
|
||||
range: Range;
|
||||
sort: Sort;
|
||||
interface StateReduxProps {
|
||||
dataProviders?: DataProvider[];
|
||||
data?: ECS[];
|
||||
range?: Range;
|
||||
sort?: Sort;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
createTimeline: ActionCreator<{ id: string }>;
|
||||
addProvider: ActionCreator<{
|
||||
createTimeline?: ActionCreator<{ id: string }>;
|
||||
addProvider?: ActionCreator<{
|
||||
id: string;
|
||||
provider: DataProvider;
|
||||
}>;
|
||||
updateData: ActionCreator<{
|
||||
updateData?: ActionCreator<{
|
||||
id: string;
|
||||
data: ECS[];
|
||||
}>;
|
||||
updateProviders: ActionCreator<{
|
||||
updateProviders?: ActionCreator<{
|
||||
id: string;
|
||||
providers: DataProvider[];
|
||||
}>;
|
||||
updateRange: ActionCreator<{
|
||||
updateRange?: ActionCreator<{
|
||||
id: string;
|
||||
range: Range;
|
||||
}>;
|
||||
updateSort: ActionCreator<{
|
||||
updateSort?: ActionCreator<{
|
||||
id: string;
|
||||
sort: Sort;
|
||||
}>;
|
||||
removeProvider: ActionCreator<{
|
||||
removeProvider?: ActionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps & DispatchProps;
|
||||
type Props = OwnProps & StateReduxProps & DispatchProps;
|
||||
|
||||
class StatefulTimelineComponent extends React.PureComponent<Props> {
|
||||
public componentDidMount() {
|
||||
const { createTimeline, id } = this.props;
|
||||
|
||||
createTimeline({ id });
|
||||
createTimeline!({ id });
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -87,30 +87,30 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
|
|||
} = this.props;
|
||||
|
||||
const onColumnSorted: OnColumnSorted = sorted => {
|
||||
updateSort({ id, sort: sorted });
|
||||
updateSort!({ id, sort: sorted });
|
||||
};
|
||||
|
||||
const onDataProviderRemoved: OnDataProviderRemoved = dataProvider => {
|
||||
removeProvider({ id, providerId: dataProvider.id });
|
||||
removeProvider!({ id, providerId: dataProvider.id });
|
||||
};
|
||||
|
||||
const onRangeSelected: OnRangeSelected = selectedRange => {
|
||||
updateRange({ id, range: selectedRange });
|
||||
updateRange!({ id, range: selectedRange });
|
||||
};
|
||||
|
||||
return (
|
||||
<Timeline
|
||||
columnHeaders={headers}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={dataProviders}
|
||||
data={data}
|
||||
dataProviders={dataProviders!}
|
||||
data={data!}
|
||||
onColumnSorted={onColumnSorted}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onFilterChange={noop} // TODO: this is the callback for column filters, which is out scope for this phase of delivery
|
||||
onRangeSelected={onRangeSelected}
|
||||
range={range}
|
||||
range={range!}
|
||||
rowRenderers={rowRenderers}
|
||||
sort={sort}
|
||||
sort={sort!}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 gql from 'graphql-tag';
|
||||
|
||||
export const eventsQuery = gql`
|
||||
query GetEventsQuery($sourceId: ID!, $timerange: TimerangeInput!, $filterQuery: String) {
|
||||
source(id: $sourceId) {
|
||||
getEvents(timerange: $timerange, filterQuery: $filterQuery) {
|
||||
events {
|
||||
timestamp
|
||||
event {
|
||||
type
|
||||
severity
|
||||
module
|
||||
category
|
||||
id
|
||||
}
|
||||
host {
|
||||
hostname
|
||||
ip
|
||||
}
|
||||
source {
|
||||
ip
|
||||
port
|
||||
}
|
||||
destination {
|
||||
ip
|
||||
port
|
||||
}
|
||||
geo {
|
||||
region_name
|
||||
country_iso_code
|
||||
}
|
||||
suricata {
|
||||
eve {
|
||||
proto
|
||||
flow_id
|
||||
alert {
|
||||
signature
|
||||
signature_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
kpiEventType {
|
||||
value
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
55
x-pack/plugins/secops/public/containers/events/index.tsx
Normal file
55
x-pack/plugins/secops/public/containers/events/index.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { getOr } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { Query } from 'react-apollo';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { EventItem, GetEventsQuery, KpiItem } from '../../../common/graphql/types';
|
||||
|
||||
import { eventsQuery } from './events.gql_query';
|
||||
|
||||
interface EventsArgs {
|
||||
events: EventItem[];
|
||||
kpiEventType: KpiItem[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface EventsProps {
|
||||
children: (args: EventsArgs) => React.ReactNode;
|
||||
sourceId: string;
|
||||
startDate: number;
|
||||
endDate: number;
|
||||
filterQuery?: string;
|
||||
}
|
||||
|
||||
export const EventsQuery = pure<EventsProps>(
|
||||
({ children, filterQuery, sourceId, startDate, endDate }) => (
|
||||
<Query<GetEventsQuery.Query, GetEventsQuery.Variables>
|
||||
query={eventsQuery}
|
||||
fetchPolicy="no-cache"
|
||||
notifyOnNetworkStatusChange
|
||||
variables={{
|
||||
filterQuery,
|
||||
sourceId,
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
from: endDate,
|
||||
to: startDate,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{({ data, loading }) =>
|
||||
children({
|
||||
loading,
|
||||
events: getOr([], 'source.getEvents.events', data),
|
||||
kpiEventType: getOr([], 'source.getEvents.kpiEventType', data),
|
||||
})
|
||||
}
|
||||
</Query>
|
||||
)
|
||||
);
|
|
@ -21,7 +21,6 @@ import {
|
|||
PageContent,
|
||||
PageHeader,
|
||||
Pane1,
|
||||
Pane1FlexContent,
|
||||
Pane1Header,
|
||||
Pane1Style,
|
||||
Pane2,
|
||||
|
@ -74,16 +73,14 @@ export const HomePage = pure(() => (
|
|||
<EuiSearchBar onChange={noop} />
|
||||
</Pane1Header>
|
||||
<PaneScrollContainer data-test-subj="pane1ScrollContainer">
|
||||
<Pane1FlexContent data-test-subj="pane1FlexContent">
|
||||
<Switch>
|
||||
<Redirect from="/" exact={true} to="/overview" />
|
||||
<Route path="/overview" component={Overview} />
|
||||
<Route path="/hosts" component={Hosts} />
|
||||
<Route path="/network" component={Network} />
|
||||
<Route path="/link-to" component={LinkToPage} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Pane1FlexContent>
|
||||
<Switch>
|
||||
<Redirect from="/" exact={true} to="/overview" />
|
||||
<Route path="/overview" component={Overview} />
|
||||
<Route path="/hosts" component={Hosts} />
|
||||
<Route path="/network" component={Network} />
|
||||
<Route path="/link-to" component={LinkToPage} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</PaneScrollContainer>
|
||||
</Pane1>
|
||||
|
||||
|
|
|
@ -3,12 +3,94 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { Placeholders } from '../../components/visualization_placeholder';
|
||||
import { EventItem, KpiItem } from '../../../common/graphql/types';
|
||||
import { BasicTable, Columns } from '../../components/basic_table';
|
||||
import { HorizontalBarChart, HorizontalBarChartData } from '../../components/horizontal_bar_chart';
|
||||
import { Pane1FlexContent } from '../../components/page';
|
||||
import { Placeholders, VisualizationPlaceholder } from '../../components/visualization_placeholder';
|
||||
import { EventsQuery } from '../../containers/events';
|
||||
|
||||
export const Hosts = pure(() => (
|
||||
<Placeholders timelineId="pane2-timeline" count={10} myRoute="Hosts" />
|
||||
<EventsQuery sourceId="default" startDate={1541044800000} endDate={1543640399999}>
|
||||
{({ events, kpiEventType, loading }) => (
|
||||
<Pane1FlexContent data-test-subj="pane1FlexContent">
|
||||
<VisualizationPlaceholder>
|
||||
<HorizontalBarChart
|
||||
loading={loading}
|
||||
title="KPI event types"
|
||||
width={490}
|
||||
height={279}
|
||||
barChartdata={
|
||||
kpiEventType.map((i: KpiItem) => ({
|
||||
x: i.count,
|
||||
y: i.value,
|
||||
})) as HorizontalBarChartData[]
|
||||
}
|
||||
/>
|
||||
</VisualizationPlaceholder>
|
||||
<VisualizationPlaceholder>
|
||||
<BasicTable
|
||||
columns={eventsColumns}
|
||||
loading={loading}
|
||||
pageOfItems={events}
|
||||
sortField="host.hostname"
|
||||
title="Events"
|
||||
/>
|
||||
</VisualizationPlaceholder>
|
||||
<Placeholders timelineId="pane2-timeline" count={8} myRoute="Hosts" />
|
||||
</Pane1FlexContent>
|
||||
)}
|
||||
</EventsQuery>
|
||||
));
|
||||
|
||||
const eventsColumns: Columns[] = [
|
||||
{
|
||||
name: 'Host name',
|
||||
sortable: true,
|
||||
truncateText: false,
|
||||
hideForMobile: false,
|
||||
render: (item: EventItem) => (
|
||||
<React.Fragment>{getOr('--', 'host.hostname', item)}</React.Fragment>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Event type',
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
hideForMobile: true,
|
||||
render: (item: EventItem) => <React.Fragment>{getOr('--', 'event.type', item)}</React.Fragment>,
|
||||
},
|
||||
{
|
||||
name: 'Source',
|
||||
truncateText: true,
|
||||
render: (item: EventItem) => (
|
||||
<React.Fragment>
|
||||
{getOr('--', 'source.ip', item).slice(0, 12)} : {getOr('--', 'source.port', item)}
|
||||
</React.Fragment>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Destination',
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
render: (item: EventItem) => (
|
||||
<React.Fragment>
|
||||
{getOr('--', 'destination.ip', item).slice(0, 12)} : {getOr('--', 'destination.port', item)}
|
||||
</React.Fragment>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Location',
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
render: (item: EventItem) => (
|
||||
<React.Fragment>
|
||||
{getOr('--', 'geo.region_name', item)} - {getOr('--', 'geo.country_iso_code', item)}
|
||||
</React.Fragment>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { Pane1FlexContent } from '../../components/page';
|
||||
import { Placeholders } from '../../components/visualization_placeholder';
|
||||
|
||||
export const Network = pure(() => (
|
||||
<Placeholders timelineId="pane2-timeline" count={10} myRoute="Network" />
|
||||
<Pane1FlexContent>
|
||||
<Placeholders timelineId="pane2-timeline" count={10} myRoute="Network" />
|
||||
</Pane1FlexContent>
|
||||
));
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { Pane1FlexContent } from '../../components/page';
|
||||
import { Placeholders } from '../../components/visualization_placeholder';
|
||||
|
||||
export const Overview = pure(() => (
|
||||
<Placeholders timelineId="pane2-timeline" count={10} myRoute="Overview" />
|
||||
<Pane1FlexContent>
|
||||
<Placeholders timelineId="pane2-timeline" count={10} myRoute="Overview" />
|
||||
</Pane1FlexContent>
|
||||
));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue