mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
This commit is contained in:
parent
1924f78471
commit
666af5d332
82 changed files with 2548 additions and 1078 deletions
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
/* eslint-disable @typescript-eslint/camelcase, @typescript-eslint/no-empty-interface */
|
||||
|
||||
import * as runtimeTypes from 'io-ts';
|
||||
import { SavedObjectsClient } from 'kibana/server';
|
||||
|
||||
import { unionWithNullType } from '../../utility_types';
|
||||
import { stringEnum, unionWithNullType } from '../../utility_types';
|
||||
import { NoteSavedObject, NoteSavedObjectToReturnRuntimeType } from './note';
|
||||
import { PinnedEventToReturnSavedObjectRuntimeType, PinnedEventSavedObject } from './pinned_event';
|
||||
|
||||
|
@ -164,6 +164,24 @@ export type TimelineStatusLiteralWithNull = runtimeTypes.TypeOf<
|
|||
typeof TimelineStatusLiteralWithNullRt
|
||||
>;
|
||||
|
||||
export enum RowRendererId {
|
||||
auditd = 'auditd',
|
||||
auditd_file = 'auditd_file',
|
||||
netflow = 'netflow',
|
||||
plain = 'plain',
|
||||
suricata = 'suricata',
|
||||
system = 'system',
|
||||
system_dns = 'system_dns',
|
||||
system_endgame_process = 'system_endgame_process',
|
||||
system_file = 'system_file',
|
||||
system_fim = 'system_fim',
|
||||
system_security_event = 'system_security_event',
|
||||
system_socket = 'system_socket',
|
||||
zeek = 'zeek',
|
||||
}
|
||||
|
||||
export const RowRendererIdRuntimeType = stringEnum(RowRendererId, 'RowRendererId');
|
||||
|
||||
/**
|
||||
* Timeline template type
|
||||
*/
|
||||
|
@ -211,6 +229,7 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
|
|||
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
|
||||
description: unionWithNullType(runtimeTypes.string),
|
||||
eventType: unionWithNullType(runtimeTypes.string),
|
||||
excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)),
|
||||
favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)),
|
||||
filters: unionWithNullType(runtimeTypes.array(SavedFilterRuntimeType)),
|
||||
kqlMode: unionWithNullType(runtimeTypes.string),
|
||||
|
|
|
@ -15,3 +15,14 @@ export interface DescriptionList {
|
|||
|
||||
export const unionWithNullType = <T extends runtimeTypes.Mixed>(type: T) =>
|
||||
runtimeTypes.union([type, runtimeTypes.null]);
|
||||
|
||||
export const stringEnum = <T>(enumObj: T, enumName = 'enum') =>
|
||||
new runtimeTypes.Type<T[keyof T], string>(
|
||||
enumName,
|
||||
(u): u is T[keyof T] => Object.values(enumObj).includes(u),
|
||||
(u, c) =>
|
||||
Object.values(enumObj).includes(u)
|
||||
? runtimeTypes.success(u as T[keyof T])
|
||||
: runtimeTypes.failure(u, c),
|
||||
(a) => (a as unknown) as string
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]';
|
||||
|
||||
export const EVENTS_VIEWER_FIELDS_BUTTON =
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser-gear"]';
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="show-field-browser"]';
|
||||
|
||||
export const EVENTS_VIEWER_PANEL = '[data-test-subj="events-viewer-panel"]';
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RowRendererId } from '../../../../common/types/timeline';
|
||||
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
|
||||
import {
|
||||
DEFAULT_COLUMN_MIN_WIDTH,
|
||||
|
@ -69,5 +70,5 @@ export const alertsHeaders: ColumnHeaderOptions[] = [
|
|||
export const alertsDefaultModel: SubsetTimelineModel = {
|
||||
...timelineDefaults,
|
||||
columns: alertsHeaders,
|
||||
showRowRenderers: false,
|
||||
excludedRowRendererIds: Object.values(RowRendererId),
|
||||
};
|
||||
|
|
|
@ -15,13 +15,13 @@ import {
|
|||
} from 'react-beautiful-dnd';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { dragAndDropActions } from '../../store/drag_and_drop';
|
||||
import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/components/row_renderers_browser/constants';
|
||||
|
||||
import { TruncatableText } from '../truncatable_text';
|
||||
import { WithHoverActions } from '../with_hover_actions';
|
||||
|
||||
import { DraggableWrapperHoverContent, useGetTimelineId } from './draggable_wrapper_hover_content';
|
||||
import { getDraggableId, getDroppableId } from './helpers';
|
||||
import { ProviderContainer } from './provider_container';
|
||||
|
@ -49,13 +49,27 @@ class DragDropErrorBoundary extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
interface WrapperProps {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const Wrapper = styled.div<WrapperProps>`
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
|
||||
[data-rbd-placeholder-context-id] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
${({ disabled }) =>
|
||||
disabled &&
|
||||
`
|
||||
[data-rbd-draggable-id]:hover,
|
||||
.euiBadge:hover,
|
||||
.euiBadge__text:hover {
|
||||
cursor: default;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
@ -74,6 +88,7 @@ type RenderFunctionProp = (
|
|||
|
||||
interface Props {
|
||||
dataProvider: DataProvider;
|
||||
disabled?: boolean;
|
||||
inline?: boolean;
|
||||
render: RenderFunctionProp;
|
||||
timelineId?: string;
|
||||
|
@ -100,162 +115,169 @@ export const getStyle = (
|
|||
};
|
||||
};
|
||||
|
||||
export const DraggableWrapper = React.memo<Props>(
|
||||
({ dataProvider, onFilterAdded, render, timelineId, truncate }) => {
|
||||
const draggableRef = useRef<HTMLDivElement | null>(null);
|
||||
const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
|
||||
const [showTopN, setShowTopN] = useState<boolean>(false);
|
||||
const [goGetTimelineId, setGoGetTimelineId] = useState(false);
|
||||
const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId);
|
||||
const [providerRegistered, setProviderRegistered] = useState(false);
|
||||
const DraggableWrapperComponent: React.FC<Props> = ({
|
||||
dataProvider,
|
||||
onFilterAdded,
|
||||
render,
|
||||
timelineId,
|
||||
truncate,
|
||||
}) => {
|
||||
const draggableRef = useRef<HTMLDivElement | null>(null);
|
||||
const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
|
||||
const [showTopN, setShowTopN] = useState<boolean>(false);
|
||||
const [goGetTimelineId, setGoGetTimelineId] = useState(false);
|
||||
const timelineIdFind = useGetTimelineId(draggableRef, goGetTimelineId);
|
||||
const [providerRegistered, setProviderRegistered] = useState(false);
|
||||
const isDisabled = dataProvider.id.includes(`-${ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID}-`);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const handleClosePopOverTrigger = useCallback(
|
||||
() => setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleClosePopOverTrigger = useCallback(
|
||||
() => setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger),
|
||||
[]
|
||||
);
|
||||
|
||||
const toggleTopN = useCallback(() => {
|
||||
setShowTopN((prevShowTopN) => {
|
||||
const newShowTopN = !prevShowTopN;
|
||||
if (newShowTopN === false) {
|
||||
handleClosePopOverTrigger();
|
||||
}
|
||||
return newShowTopN;
|
||||
});
|
||||
}, [handleClosePopOverTrigger]);
|
||||
|
||||
const registerProvider = useCallback(() => {
|
||||
if (!providerRegistered) {
|
||||
dispatch(dragAndDropActions.registerProvider({ provider: dataProvider }));
|
||||
setProviderRegistered(true);
|
||||
const toggleTopN = useCallback(() => {
|
||||
setShowTopN((prevShowTopN) => {
|
||||
const newShowTopN = !prevShowTopN;
|
||||
if (newShowTopN === false) {
|
||||
handleClosePopOverTrigger();
|
||||
}
|
||||
}, [dispatch, providerRegistered, dataProvider]);
|
||||
return newShowTopN;
|
||||
});
|
||||
}, [handleClosePopOverTrigger]);
|
||||
|
||||
const unRegisterProvider = useCallback(
|
||||
() => dispatch(dragAndDropActions.unRegisterProvider({ id: dataProvider.id })),
|
||||
[dispatch, dataProvider]
|
||||
);
|
||||
const registerProvider = useCallback(() => {
|
||||
if (!isDisabled) {
|
||||
dispatch(dragAndDropActions.registerProvider({ provider: dataProvider }));
|
||||
setProviderRegistered(true);
|
||||
}
|
||||
}, [isDisabled, dispatch, dataProvider]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
unRegisterProvider();
|
||||
},
|
||||
[unRegisterProvider]
|
||||
);
|
||||
const unRegisterProvider = useCallback(
|
||||
() =>
|
||||
providerRegistered &&
|
||||
dispatch(dragAndDropActions.unRegisterProvider({ id: dataProvider.id })),
|
||||
[providerRegistered, dispatch, dataProvider.id]
|
||||
);
|
||||
|
||||
const hoverContent = useMemo(
|
||||
() => (
|
||||
<DraggableWrapperHoverContent
|
||||
closePopOver={handleClosePopOverTrigger}
|
||||
draggableId={getDraggableId(dataProvider.id)}
|
||||
field={dataProvider.queryMatch.field}
|
||||
goGetTimelineId={setGoGetTimelineId}
|
||||
onFilterAdded={onFilterAdded}
|
||||
showTopN={showTopN}
|
||||
timelineId={timelineId ?? timelineIdFind}
|
||||
toggleTopN={toggleTopN}
|
||||
value={
|
||||
typeof dataProvider.queryMatch.value !== 'number'
|
||||
? dataProvider.queryMatch.value
|
||||
: `${dataProvider.queryMatch.value}`
|
||||
}
|
||||
/>
|
||||
),
|
||||
[
|
||||
dataProvider,
|
||||
handleClosePopOverTrigger,
|
||||
onFilterAdded,
|
||||
showTopN,
|
||||
timelineId,
|
||||
timelineIdFind,
|
||||
toggleTopN,
|
||||
]
|
||||
);
|
||||
useEffect(
|
||||
() => () => {
|
||||
unRegisterProvider();
|
||||
},
|
||||
[unRegisterProvider]
|
||||
);
|
||||
|
||||
const renderContent = useCallback(
|
||||
() => (
|
||||
<Wrapper data-test-subj="draggableWrapperDiv">
|
||||
<DragDropErrorBoundary>
|
||||
<Droppable
|
||||
isDropDisabled={true}
|
||||
droppableId={getDroppableId(dataProvider.id)}
|
||||
renderClone={(provided, snapshot) => (
|
||||
<ConditionalPortal registerProvider={registerProvider}>
|
||||
<div
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={getStyle(provided.draggableProps.style, snapshot)}
|
||||
ref={provided.innerRef}
|
||||
data-test-subj="providerContainer"
|
||||
>
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`draggable-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</ProviderContentWrapper>
|
||||
</div>
|
||||
</ConditionalPortal>
|
||||
)}
|
||||
>
|
||||
{(droppableProvided) => (
|
||||
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
<Draggable
|
||||
draggableId={getDraggableId(dataProvider.id)}
|
||||
index={0}
|
||||
key={getDraggableId(dataProvider.id)}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ProviderContainer
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={(e: HTMLDivElement) => {
|
||||
provided.innerRef(e);
|
||||
draggableRef.current = e;
|
||||
}}
|
||||
data-test-subj="providerContainer"
|
||||
isDragging={snapshot.isDragging}
|
||||
registerProvider={registerProvider}
|
||||
>
|
||||
{truncate && !snapshot.isDragging ? (
|
||||
<TruncatableText data-test-subj="draggable-truncatable-content">
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</TruncatableText>
|
||||
) : (
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`draggable-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</ProviderContentWrapper>
|
||||
)}
|
||||
</ProviderContainer>
|
||||
)}
|
||||
</Draggable>
|
||||
{droppableProvided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropErrorBoundary>
|
||||
</Wrapper>
|
||||
),
|
||||
[dataProvider, render, registerProvider, truncate]
|
||||
);
|
||||
|
||||
return (
|
||||
<WithHoverActions
|
||||
alwaysShow={showTopN}
|
||||
closePopOverTrigger={closePopOverTrigger}
|
||||
hoverContent={hoverContent}
|
||||
render={renderContent}
|
||||
const hoverContent = useMemo(
|
||||
() => (
|
||||
<DraggableWrapperHoverContent
|
||||
closePopOver={handleClosePopOverTrigger}
|
||||
draggableId={getDraggableId(dataProvider.id)}
|
||||
field={dataProvider.queryMatch.field}
|
||||
goGetTimelineId={setGoGetTimelineId}
|
||||
onFilterAdded={onFilterAdded}
|
||||
showTopN={showTopN}
|
||||
timelineId={timelineId ?? timelineIdFind}
|
||||
toggleTopN={toggleTopN}
|
||||
value={
|
||||
typeof dataProvider.queryMatch.value !== 'number'
|
||||
? dataProvider.queryMatch.value
|
||||
: `${dataProvider.queryMatch.value}`
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) =>
|
||||
deepEqual(prevProps.dataProvider, nextProps.dataProvider) &&
|
||||
prevProps.render !== nextProps.render &&
|
||||
prevProps.truncate === nextProps.truncate
|
||||
);
|
||||
),
|
||||
[
|
||||
dataProvider,
|
||||
handleClosePopOverTrigger,
|
||||
onFilterAdded,
|
||||
showTopN,
|
||||
timelineId,
|
||||
timelineIdFind,
|
||||
toggleTopN,
|
||||
]
|
||||
);
|
||||
|
||||
const renderContent = useCallback(
|
||||
() => (
|
||||
<Wrapper data-test-subj="draggableWrapperDiv" disabled={isDisabled}>
|
||||
<DragDropErrorBoundary>
|
||||
<Droppable
|
||||
isDropDisabled={true}
|
||||
droppableId={getDroppableId(dataProvider.id)}
|
||||
renderClone={(provided, snapshot) => (
|
||||
<ConditionalPortal registerProvider={registerProvider}>
|
||||
<div
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={getStyle(provided.draggableProps.style, snapshot)}
|
||||
ref={provided.innerRef}
|
||||
data-test-subj="providerContainer"
|
||||
>
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`draggable-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</ProviderContentWrapper>
|
||||
</div>
|
||||
</ConditionalPortal>
|
||||
)}
|
||||
>
|
||||
{(droppableProvided) => (
|
||||
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
<Draggable
|
||||
draggableId={getDraggableId(dataProvider.id)}
|
||||
index={0}
|
||||
key={getDraggableId(dataProvider.id)}
|
||||
isDragDisabled={isDisabled}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ProviderContainer
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={(e: HTMLDivElement) => {
|
||||
provided.innerRef(e);
|
||||
draggableRef.current = e;
|
||||
}}
|
||||
data-test-subj="providerContainer"
|
||||
isDragging={snapshot.isDragging}
|
||||
registerProvider={registerProvider}
|
||||
>
|
||||
{truncate && !snapshot.isDragging ? (
|
||||
<TruncatableText data-test-subj="draggable-truncatable-content">
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</TruncatableText>
|
||||
) : (
|
||||
<ProviderContentWrapper
|
||||
data-test-subj={`draggable-content-${dataProvider.queryMatch.field}`}
|
||||
>
|
||||
{render(dataProvider, provided, snapshot)}
|
||||
</ProviderContentWrapper>
|
||||
)}
|
||||
</ProviderContainer>
|
||||
)}
|
||||
</Draggable>
|
||||
{droppableProvided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropErrorBoundary>
|
||||
</Wrapper>
|
||||
),
|
||||
[dataProvider, registerProvider, render, isDisabled, truncate]
|
||||
);
|
||||
|
||||
if (isDisabled) return <>{renderContent()}</>;
|
||||
|
||||
return (
|
||||
<WithHoverActions
|
||||
alwaysShow={showTopN}
|
||||
closePopOverTrigger={closePopOverTrigger}
|
||||
hoverContent={hoverContent}
|
||||
render={renderContent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DraggableWrapper = React.memo(DraggableWrapperComponent);
|
||||
|
||||
DraggableWrapper.displayName = 'DraggableWrapper';
|
||||
|
||||
|
|
|
@ -5,13 +5,16 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge, EuiToolTip, IconType } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper';
|
||||
import { escapeDataProviderId } from '../drag_and_drop/helpers';
|
||||
import { getEmptyStringTag } from '../empty_value';
|
||||
import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
import {
|
||||
DataProvider,
|
||||
IS_OPERATOR,
|
||||
} from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
import { Provider } from '../../../timelines/components/timeline/data_providers/provider';
|
||||
|
||||
export interface DefaultDraggableType {
|
||||
|
@ -84,36 +87,48 @@ Content.displayName = 'Content';
|
|||
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
|
||||
*/
|
||||
export const DefaultDraggable = React.memo<DefaultDraggableType>(
|
||||
({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) =>
|
||||
value != null ? (
|
||||
({ id, field, value, name, children, timelineId, tooltipContent, queryValue }) => {
|
||||
const dataProviderProp: DataProvider = useMemo(
|
||||
() => ({
|
||||
and: [],
|
||||
enabled: true,
|
||||
id: escapeDataProviderId(id),
|
||||
name: name ? name : value ?? '',
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field,
|
||||
value: queryValue ? queryValue : value ?? '',
|
||||
operator: IS_OPERATOR,
|
||||
},
|
||||
}),
|
||||
[field, id, name, queryValue, value]
|
||||
);
|
||||
|
||||
const renderCallback = useCallback(
|
||||
(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
<Content field={field} tooltipContent={tooltipContent} value={value}>
|
||||
{children}
|
||||
</Content>
|
||||
),
|
||||
[children, field, tooltipContent, value]
|
||||
);
|
||||
|
||||
if (value == null) return null;
|
||||
|
||||
return (
|
||||
<DraggableWrapper
|
||||
dataProvider={{
|
||||
and: [],
|
||||
enabled: true,
|
||||
id: escapeDataProviderId(id),
|
||||
name: name ? name : value,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field,
|
||||
value: queryValue ? queryValue : value,
|
||||
operator: IS_OPERATOR,
|
||||
},
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
<Content field={field} tooltipContent={tooltipContent} value={value}>
|
||||
{children}
|
||||
</Content>
|
||||
)
|
||||
}
|
||||
dataProvider={dataProviderProp}
|
||||
render={renderCallback}
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
) : null
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DefaultDraggable.displayName = 'DefaultDraggable';
|
||||
|
@ -146,33 +161,34 @@ export type BadgeDraggableType = Omit<DefaultDraggableType, 'id'> & {
|
|||
* prevent a tooltip from being displayed, or pass arbitrary content
|
||||
* @param queryValue - defaults to `value`, this query overrides the `queryMatch.value` used by the `DataProvider` that represents the data
|
||||
*/
|
||||
export const DraggableBadge = React.memo<BadgeDraggableType>(
|
||||
({
|
||||
contextId,
|
||||
eventId,
|
||||
field,
|
||||
value,
|
||||
iconType,
|
||||
name,
|
||||
color = 'hollow',
|
||||
children,
|
||||
tooltipContent,
|
||||
queryValue,
|
||||
}) =>
|
||||
value != null ? (
|
||||
<DefaultDraggable
|
||||
id={`draggable-badge-default-draggable-${contextId}-${eventId}-${field}-${value}`}
|
||||
field={field}
|
||||
name={name}
|
||||
value={value}
|
||||
tooltipContent={tooltipContent}
|
||||
queryValue={queryValue}
|
||||
>
|
||||
<Badge iconType={iconType} color={color} title="">
|
||||
{children ? children : value !== '' ? value : getEmptyStringTag()}
|
||||
</Badge>
|
||||
</DefaultDraggable>
|
||||
) : null
|
||||
);
|
||||
const DraggableBadgeComponent: React.FC<BadgeDraggableType> = ({
|
||||
contextId,
|
||||
eventId,
|
||||
field,
|
||||
value,
|
||||
iconType,
|
||||
name,
|
||||
color = 'hollow',
|
||||
children,
|
||||
tooltipContent,
|
||||
queryValue,
|
||||
}) =>
|
||||
value != null ? (
|
||||
<DefaultDraggable
|
||||
id={`draggable-badge-default-draggable-${contextId}-${eventId}-${field}-${value}`}
|
||||
field={field}
|
||||
name={name}
|
||||
value={value}
|
||||
tooltipContent={tooltipContent}
|
||||
queryValue={queryValue}
|
||||
>
|
||||
<Badge iconType={iconType} color={color} title="">
|
||||
{children ? children : value !== '' ? value : getEmptyStringTag()}
|
||||
</Badge>
|
||||
</DefaultDraggable>
|
||||
) : null;
|
||||
|
||||
DraggableBadgeComponent.displayName = 'DraggableBadgeComponent';
|
||||
|
||||
export const DraggableBadge = React.memo(DraggableBadgeComponent);
|
||||
DraggableBadge.displayName = 'DraggableBadge';
|
||||
|
|
|
@ -77,7 +77,7 @@ describe('EventsViewer', () => {
|
|||
await wait();
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find(`[data-test-subj="show-field-browser-gear"]`).first().exists()).toBe(true);
|
||||
expect(wrapper.find(`[data-test-subj="show-field-browser"]`).first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the footer containing the Load More button', async () => {
|
||||
|
|
|
@ -45,6 +45,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
defaultIndices,
|
||||
deleteEventQuery,
|
||||
end,
|
||||
excludedRowRendererIds,
|
||||
filters,
|
||||
headerFilterGroup,
|
||||
id,
|
||||
|
@ -57,7 +58,6 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
removeColumn,
|
||||
start,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
sort,
|
||||
updateItemsPerPage,
|
||||
upsertColumn,
|
||||
|
@ -69,7 +69,14 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (createTimeline != null) {
|
||||
createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers });
|
||||
createTimeline({
|
||||
id,
|
||||
columns,
|
||||
excludedRowRendererIds,
|
||||
sort,
|
||||
itemsPerPage,
|
||||
showCheckboxes,
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
deleteEventQuery({ id, inputId: 'global' });
|
||||
|
@ -125,7 +132,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
query={query}
|
||||
start={start}
|
||||
sort={sort!}
|
||||
sort={sort}
|
||||
toggleColumn={toggleColumn}
|
||||
utilityBar={utilityBar}
|
||||
/>
|
||||
|
@ -145,18 +152,19 @@ const makeMapStateToProps = () => {
|
|||
columns,
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
excludedRowRendererIds,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
sort,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
} = events;
|
||||
|
||||
return {
|
||||
columns,
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
excludedRowRendererIds,
|
||||
filters: getGlobalFiltersQuerySelector(state),
|
||||
id,
|
||||
isLive: input.policy.kind === 'interval',
|
||||
|
@ -166,7 +174,6 @@ const makeMapStateToProps = () => {
|
|||
query: getGlobalQuerySelector(state),
|
||||
sort,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
};
|
||||
};
|
||||
return mapStateToProps;
|
||||
|
@ -192,6 +199,7 @@ export const StatefulEventsViewer = connector(
|
|||
deepEqual(prevProps.columns, nextProps.columns) &&
|
||||
deepEqual(prevProps.defaultIndices, nextProps.defaultIndices) &&
|
||||
deepEqual(prevProps.dataProviders, nextProps.dataProviders) &&
|
||||
deepEqual(prevProps.excludedRowRendererIds, nextProps.excludedRowRendererIds) &&
|
||||
prevProps.deletedEventIds === nextProps.deletedEventIds &&
|
||||
prevProps.end === nextProps.end &&
|
||||
deepEqual(prevProps.filters, nextProps.filters) &&
|
||||
|
@ -204,7 +212,6 @@ export const StatefulEventsViewer = connector(
|
|||
prevProps.start === nextProps.start &&
|
||||
deepEqual(prevProps.pageFilters, nextProps.pageFilters) &&
|
||||
prevProps.showCheckboxes === nextProps.showCheckboxes &&
|
||||
prevProps.showRowRenderers === nextProps.showRowRenderers &&
|
||||
prevProps.start === nextProps.start &&
|
||||
prevProps.utilityBar === nextProps.utilityBar
|
||||
)
|
||||
|
|
|
@ -195,6 +195,7 @@ export const mockGlobalState: State = {
|
|||
dataProviders: [],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -215,7 +216,6 @@ export const mockGlobalState: State = {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: false,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
pinnedEventIds: {},
|
||||
pinnedEventsSaveObject: {},
|
||||
|
|
|
@ -418,8 +418,8 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
data: [
|
||||
{ field: '@timestamp', value: ['2019-03-07T05:06:51.000Z'] },
|
||||
{ field: 'host.name', value: ['zeek-franfurt'] },
|
||||
{ field: 'source.ip', value: ['185.176.26.101'] },
|
||||
{ field: 'destination.ip', value: ['207.154.238.205'] },
|
||||
{ field: 'source.ip', value: ['192.168.26.101'] },
|
||||
{ field: 'destination.ip', value: ['192.168.238.205'] },
|
||||
],
|
||||
ecs: {
|
||||
_id: '14',
|
||||
|
@ -466,8 +466,8 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
data: [
|
||||
{ field: '@timestamp', value: ['2019-03-07T00:51:28.000Z'] },
|
||||
{ field: 'host.name', value: ['suricata-zeek-singapore'] },
|
||||
{ field: 'source.ip', value: ['206.189.35.240'] },
|
||||
{ field: 'destination.ip', value: ['67.207.67.3'] },
|
||||
{ field: 'source.ip', value: ['192.168.35.240'] },
|
||||
{ field: 'destination.ip', value: ['192.168.67.3'] },
|
||||
],
|
||||
ecs: {
|
||||
_id: '15',
|
||||
|
@ -520,8 +520,8 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
data: [
|
||||
{ field: '@timestamp', value: ['2019-03-05T07:00:20.000Z'] },
|
||||
{ field: 'host.name', value: ['suricata-zeek-singapore'] },
|
||||
{ field: 'source.ip', value: ['206.189.35.240'] },
|
||||
{ field: 'destination.ip', value: ['192.241.164.26'] },
|
||||
{ field: 'source.ip', value: ['192.168.35.240'] },
|
||||
{ field: 'destination.ip', value: ['192.168.164.26'] },
|
||||
],
|
||||
ecs: {
|
||||
_id: '16',
|
||||
|
@ -572,7 +572,7 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
data: [
|
||||
{ field: '@timestamp', value: ['2019-02-28T22:36:28.000Z'] },
|
||||
{ field: 'host.name', value: ['zeek-franfurt'] },
|
||||
{ field: 'source.ip', value: ['8.42.77.171'] },
|
||||
{ field: 'source.ip', value: ['192.168.77.171'] },
|
||||
],
|
||||
ecs: {
|
||||
_id: '17',
|
||||
|
@ -621,8 +621,8 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
data: [
|
||||
{ field: '@timestamp', value: ['2019-02-22T21:12:13.000Z'] },
|
||||
{ field: 'host.name', value: ['zeek-sensor-amsterdam'] },
|
||||
{ field: 'source.ip', value: ['188.166.66.184'] },
|
||||
{ field: 'destination.ip', value: ['91.189.95.15'] },
|
||||
{ field: 'source.ip', value: ['192.168.66.184'] },
|
||||
{ field: 'destination.ip', value: ['192.168.95.15'] },
|
||||
],
|
||||
ecs: {
|
||||
_id: '18',
|
||||
|
@ -767,7 +767,7 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
{ field: '@timestamp', value: ['2019-03-14T22:30:25.527Z'] },
|
||||
{ field: 'event.category', value: ['user-login'] },
|
||||
{ field: 'host.name', value: ['zeek-london'] },
|
||||
{ field: 'source.ip', value: ['8.42.77.171'] },
|
||||
{ field: 'source.ip', value: ['192.168.77.171'] },
|
||||
{ field: 'user.name', value: ['root'] },
|
||||
],
|
||||
ecs: {
|
||||
|
@ -1101,7 +1101,7 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
{ field: 'event.action', value: ['connected-to'] },
|
||||
{ field: 'event.category', value: ['audit-rule'] },
|
||||
{ field: 'host.name', value: ['zeek-london'] },
|
||||
{ field: 'destination.ip', value: ['93.184.216.34'] },
|
||||
{ field: 'destination.ip', value: ['192.168.216.34'] },
|
||||
{ field: 'user.name', value: ['alice'] },
|
||||
],
|
||||
ecs: {
|
||||
|
@ -1121,7 +1121,7 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
data: null,
|
||||
summary: {
|
||||
actor: { primary: ['alice'], secondary: ['alice'] },
|
||||
object: { primary: ['93.184.216.34'], secondary: ['80'], type: ['socket'] },
|
||||
object: { primary: ['192.168.216.34'], secondary: ['80'], type: ['socket'] },
|
||||
how: ['/usr/bin/wget'],
|
||||
message_type: null,
|
||||
sequence: null,
|
||||
|
@ -1133,7 +1133,7 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
ip: ['46.101.3.136', '10.16.0.5', 'fe80::4066:42ff:fe19:b3b9'],
|
||||
},
|
||||
source: null,
|
||||
destination: { ip: ['93.184.216.34'], port: [80] },
|
||||
destination: { ip: ['192.168.216.34'], port: [80] },
|
||||
geo: null,
|
||||
suricata: null,
|
||||
network: null,
|
||||
|
@ -1174,7 +1174,7 @@ export const mockTimelineData: TimelineItem[] = [
|
|||
},
|
||||
auditd: {
|
||||
result: ['success'],
|
||||
session: ['unset'],
|
||||
session: ['242'],
|
||||
data: null,
|
||||
summary: {
|
||||
actor: { primary: ['unset'], secondary: ['root'] },
|
||||
|
|
|
@ -2098,6 +2098,7 @@ export const mockTimelineModel: TimelineModel = {
|
|||
description: 'This is a sample rule description',
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
|
@ -2137,7 +2138,6 @@ export const mockTimelineModel: TimelineModel = {
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: Direction.desc,
|
||||
|
@ -2217,6 +2217,7 @@ export const defaultTimelineProps: CreateTimelineProps = {
|
|||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
|
@ -2241,7 +2242,6 @@ export const defaultTimelineProps: CreateTimelineProps = {
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: { columnId: '@timestamp', sortDirection: Direction.desc },
|
||||
status: TimelineStatus.draft,
|
||||
title: '',
|
||||
|
|
|
@ -158,6 +158,7 @@ describe('alert actions', () => {
|
|||
description: 'This is a sample rule description',
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
|
@ -210,7 +211,6 @@ describe('alert actions', () => {
|
|||
selectedEventIds: {},
|
||||
show: true,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
|
|
|
@ -39,6 +39,10 @@ interface AlertsUtilityBarProps {
|
|||
updateAlertsStatus: UpdateAlertsStatus;
|
||||
}
|
||||
|
||||
const UtilityBarFlexGroup = styled(EuiFlexGroup)`
|
||||
min-width: 175px;
|
||||
`;
|
||||
|
||||
const AlertsUtilityBarComponent: React.FC<AlertsUtilityBarProps> = ({
|
||||
canUserCRUD,
|
||||
hasIndexWrite,
|
||||
|
@ -69,10 +73,6 @@ const AlertsUtilityBarComponent: React.FC<AlertsUtilityBarProps> = ({
|
|||
defaultNumberFormat
|
||||
);
|
||||
|
||||
const UtilityBarFlexGroup = styled(EuiFlexGroup)`
|
||||
min-width: 175px;
|
||||
`;
|
||||
|
||||
const UtilityBarPopoverContent = (closePopover: () => void) => (
|
||||
<UtilityBarFlexGroup direction="column">
|
||||
{currentFilter !== FILTER_OPEN && (
|
||||
|
|
|
@ -11,6 +11,7 @@ import ApolloClient from 'apollo-client';
|
|||
import { Dispatch } from 'redux';
|
||||
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { RowRendererId } from '../../../../common/types/timeline';
|
||||
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
|
||||
import { Filter } from '../../../../../../../src/plugins/data/common/es_query';
|
||||
import {
|
||||
|
@ -162,7 +163,7 @@ export const alertsDefaultModel: SubsetTimelineModel = {
|
|||
...timelineDefaults,
|
||||
columns: alertsHeaders,
|
||||
showCheckboxes: true,
|
||||
showRowRenderers: false,
|
||||
excludedRowRendererIds: Object.values(RowRendererId),
|
||||
};
|
||||
|
||||
export const requiredFieldsForActions = [
|
||||
|
|
|
@ -10,7 +10,7 @@ import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline';
|
|||
import { AlertsRoutes } from './routes';
|
||||
import { SecuritySubPlugin } from '../app/types';
|
||||
|
||||
const ALERTS_TIMELINE_IDS: TimelineIdLiteral[] = [
|
||||
const DETECTIONS_TIMELINE_IDS: TimelineIdLiteral[] = [
|
||||
TimelineId.detectionsRulesDetailsPage,
|
||||
TimelineId.detectionsPage,
|
||||
];
|
||||
|
@ -22,7 +22,7 @@ export class Detections {
|
|||
return {
|
||||
SubPluginRoutes: AlertsRoutes,
|
||||
storageTimelines: {
|
||||
timelineById: getTimelinesInStorageByIds(storage, ALERTS_TIMELINE_IDS),
|
||||
timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9641,6 +9641,22 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "excludedRowRendererIds",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null }
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "favorite",
|
||||
"description": "",
|
||||
|
@ -10146,6 +10162,75 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "RowRendererId",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{ "name": "auditd", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{
|
||||
"name": "auditd_file",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "netflow",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{ "name": "plain", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{
|
||||
"name": "suricata",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{ "name": "system", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{
|
||||
"name": "system_dns",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "system_endgame_process",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "system_file",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "system_fim",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "system_security_event",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "system_socket",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{ "name": "zeek", "description": "", "isDeprecated": false, "deprecationReason": null }
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "FavoriteTimelineResult",
|
||||
|
@ -11061,6 +11146,20 @@
|
|||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "excludedRowRendererIds",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "ENUM", "name": "RowRendererId", "ofType": null }
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "filters",
|
||||
"description": "",
|
||||
|
|
|
@ -124,6 +124,8 @@ export interface TimelineInput {
|
|||
|
||||
eventType?: Maybe<string>;
|
||||
|
||||
excludedRowRendererIds?: Maybe<RowRendererId[]>;
|
||||
|
||||
filters?: Maybe<FilterTimelineInput[]>;
|
||||
|
||||
kqlMode?: Maybe<string>;
|
||||
|
@ -349,6 +351,22 @@ export enum DataProviderType {
|
|||
template = 'template',
|
||||
}
|
||||
|
||||
export enum RowRendererId {
|
||||
auditd = 'auditd',
|
||||
auditd_file = 'auditd_file',
|
||||
netflow = 'netflow',
|
||||
plain = 'plain',
|
||||
suricata = 'suricata',
|
||||
system = 'system',
|
||||
system_dns = 'system_dns',
|
||||
system_endgame_process = 'system_endgame_process',
|
||||
system_file = 'system_file',
|
||||
system_fim = 'system_fim',
|
||||
system_security_event = 'system_security_event',
|
||||
system_socket = 'system_socket',
|
||||
zeek = 'zeek',
|
||||
}
|
||||
|
||||
export enum TimelineStatus {
|
||||
active = 'active',
|
||||
draft = 'draft',
|
||||
|
@ -1961,6 +1979,8 @@ export interface TimelineResult {
|
|||
|
||||
eventType?: Maybe<string>;
|
||||
|
||||
excludedRowRendererIds?: Maybe<RowRendererId[]>;
|
||||
|
||||
favorite?: Maybe<FavoriteTimelineResult[]>;
|
||||
|
||||
filters?: Maybe<FilterTimelineResult[]>;
|
||||
|
@ -4385,6 +4405,8 @@ export namespace GetAllTimeline {
|
|||
|
||||
eventIdToNoteIds: Maybe<EventIdToNoteIds[]>;
|
||||
|
||||
excludedRowRendererIds: Maybe<RowRendererId[]>;
|
||||
|
||||
notes: Maybe<Notes[]>;
|
||||
|
||||
noteIds: Maybe<string[]>;
|
||||
|
@ -5454,6 +5476,8 @@ export namespace GetOneTimeline {
|
|||
|
||||
eventIdToNoteIds: Maybe<EventIdToNoteIds[]>;
|
||||
|
||||
excludedRowRendererIds: Maybe<RowRendererId[]>;
|
||||
|
||||
favorite: Maybe<Favorite[]>;
|
||||
|
||||
filters: Maybe<Filters[]>;
|
||||
|
|
|
@ -35,6 +35,10 @@ Percent.displayName = 'Percent';
|
|||
|
||||
const SourceDestinationArrowsContainer = styled(EuiFlexGroup)`
|
||||
margin: 0 2px;
|
||||
|
||||
.euiToolTipAnchor {
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
SourceDestinationArrowsContainer.displayName = 'SourceDestinationArrowsContainer';
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
|
||||
import { mockBrowserFields } from '../../../common/containers/source/mock';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { ColumnHeaderOptions } from '../../store/timeline/model';
|
||||
|
||||
import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from './helpers';
|
||||
|
||||
|
@ -29,17 +27,6 @@ afterAll(() => {
|
|||
console.warn = originalWarn;
|
||||
});
|
||||
|
||||
const removeColumnMock = (jest.fn() as unknown) as ActionCreator<{
|
||||
id: string;
|
||||
columnId: string;
|
||||
}>;
|
||||
|
||||
const upsertColumnMock = (jest.fn() as unknown) as ActionCreator<{
|
||||
column: ColumnHeaderOptions;
|
||||
id: string;
|
||||
index: number;
|
||||
}>;
|
||||
|
||||
describe('StatefulFieldsBrowser', () => {
|
||||
const timelineId = 'test';
|
||||
|
||||
|
@ -54,13 +41,11 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser"]').first().text()).toEqual('Columns');
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('toggleShow', () => {
|
||||
|
@ -75,8 +60,6 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -95,8 +78,6 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -122,8 +103,6 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -149,8 +128,6 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
@ -186,39 +163,14 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser-gear"]').first().exists()).toBe(true);
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser"]').first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it does NOT render the Fields Browser button as a settings gear when the isEventViewer prop is false', () => {
|
||||
const isEventViewer = false;
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulFieldsBrowserComponent
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={[]}
|
||||
height={FIELD_BROWSER_HEIGHT}
|
||||
isEventViewer={isEventViewer}
|
||||
onUpdateColumns={jest.fn()}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser-gear"]').first().exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('it does NOT render the default Fields Browser button when the isEventViewer prop is true', () => {
|
||||
test('it renders the Fields Browser button as a settings gear when the isEventViewer prop is false', () => {
|
||||
const isEventViewer = true;
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -232,12 +184,10 @@ describe('StatefulFieldsBrowser', () => {
|
|||
timelineId={timelineId}
|
||||
toggleColumn={jest.fn()}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
removeColumn={removeColumnMock}
|
||||
upsertColumn={upsertColumnMock}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser"]').first().exists()).toBe(false);
|
||||
expect(wrapper.find('[data-test-subj="show-field-browser"]').first().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { noop } from 'lodash/fp';
|
||||
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { BrowserFields } from '../../../common/containers/source';
|
||||
import { timelineActions } from '../../store/timeline';
|
||||
import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model';
|
||||
import { DEFAULT_CATEGORY_NAME } from '../timeline/body/column_headers/default_headers';
|
||||
import { FieldsBrowser } from './field_browser';
|
||||
|
@ -34,181 +32,156 @@ FieldsBrowserButtonContainer.displayName = 'FieldsBrowserButtonContainer';
|
|||
/**
|
||||
* Manages the state of the field browser
|
||||
*/
|
||||
export const StatefulFieldsBrowserComponent = React.memo<FieldBrowserProps & PropsFromRedux>(
|
||||
({
|
||||
columnHeaders,
|
||||
browserFields,
|
||||
height,
|
||||
isEventViewer = false,
|
||||
onFieldSelected,
|
||||
onUpdateColumns,
|
||||
timelineId,
|
||||
toggleColumn,
|
||||
width,
|
||||
}) => {
|
||||
/** tracks the latest timeout id from `setTimeout`*/
|
||||
const inputTimeoutId = useRef(0);
|
||||
export const StatefulFieldsBrowserComponent: React.FC<FieldBrowserProps> = ({
|
||||
columnHeaders,
|
||||
browserFields,
|
||||
height,
|
||||
isEventViewer = false,
|
||||
onFieldSelected,
|
||||
onUpdateColumns,
|
||||
timelineId,
|
||||
toggleColumn,
|
||||
width,
|
||||
}) => {
|
||||
/** tracks the latest timeout id from `setTimeout`*/
|
||||
const inputTimeoutId = useRef(0);
|
||||
|
||||
/** all field names shown in the field browser must contain this string (when specified) */
|
||||
const [filterInput, setFilterInput] = useState('');
|
||||
/** all fields in this collection have field names that match the filterInput */
|
||||
const [filteredBrowserFields, setFilteredBrowserFields] = useState<BrowserFields | null>(null);
|
||||
/** when true, show a spinner in the input to indicate the field browser is searching for matching field names */
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
/** this category will be displayed in the right-hand pane of the field browser */
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_NAME);
|
||||
/** show the field browser */
|
||||
const [show, setShow] = useState(false);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (inputTimeoutId.current !== 0) {
|
||||
// ⚠️ mutation: cancel any remaining timers and zero-out the timer id:
|
||||
clearTimeout(inputTimeoutId.current);
|
||||
inputTimeoutId.current = 0;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
/** all field names shown in the field browser must contain this string (when specified) */
|
||||
const [filterInput, setFilterInput] = useState('');
|
||||
/** all fields in this collection have field names that match the filterInput */
|
||||
const [filteredBrowserFields, setFilteredBrowserFields] = useState<BrowserFields | null>(null);
|
||||
/** when true, show a spinner in the input to indicate the field browser is searching for matching field names */
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
/** this category will be displayed in the right-hand pane of the field browser */
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_NAME);
|
||||
/** show the field browser */
|
||||
const [show, setShow] = useState(false);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (inputTimeoutId.current !== 0) {
|
||||
// ⚠️ mutation: cancel any remaining timers and zero-out the timer id:
|
||||
clearTimeout(inputTimeoutId.current);
|
||||
inputTimeoutId.current = 0;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
/** Shows / hides the field browser */
|
||||
const toggleShow = useCallback(() => {
|
||||
setShow(!show);
|
||||
}, [show]);
|
||||
/** Shows / hides the field browser */
|
||||
const toggleShow = useCallback(() => {
|
||||
setShow(!show);
|
||||
}, [show]);
|
||||
|
||||
/** Invoked when the user types in the filter input */
|
||||
const updateFilter = useCallback(
|
||||
(newFilterInput: string) => {
|
||||
setFilterInput(newFilterInput);
|
||||
setIsSearching(true);
|
||||
if (inputTimeoutId.current !== 0) {
|
||||
clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers
|
||||
}
|
||||
// ⚠️ mutation: schedule a new timer that will apply the filter when it fires:
|
||||
inputTimeoutId.current = window.setTimeout(() => {
|
||||
const newFilteredBrowserFields = filterBrowserFieldsByFieldName({
|
||||
browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields),
|
||||
substring: newFilterInput,
|
||||
});
|
||||
setFilteredBrowserFields(newFilteredBrowserFields);
|
||||
setIsSearching(false);
|
||||
/** Invoked when the user types in the filter input */
|
||||
const updateFilter = useCallback(
|
||||
(newFilterInput: string) => {
|
||||
setFilterInput(newFilterInput);
|
||||
setIsSearching(true);
|
||||
if (inputTimeoutId.current !== 0) {
|
||||
clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers
|
||||
}
|
||||
// ⚠️ mutation: schedule a new timer that will apply the filter when it fires:
|
||||
inputTimeoutId.current = window.setTimeout(() => {
|
||||
const newFilteredBrowserFields = filterBrowserFieldsByFieldName({
|
||||
browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields),
|
||||
substring: newFilterInput,
|
||||
});
|
||||
setFilteredBrowserFields(newFilteredBrowserFields);
|
||||
setIsSearching(false);
|
||||
|
||||
const newSelectedCategoryId =
|
||||
newFilterInput === '' || Object.keys(newFilteredBrowserFields).length === 0
|
||||
? DEFAULT_CATEGORY_NAME
|
||||
: Object.keys(newFilteredBrowserFields)
|
||||
.sort()
|
||||
.reduce<string>(
|
||||
(selected, category) =>
|
||||
newFilteredBrowserFields[category].fields != null &&
|
||||
newFilteredBrowserFields[selected].fields != null &&
|
||||
Object.keys(newFilteredBrowserFields[category].fields!).length >
|
||||
Object.keys(newFilteredBrowserFields[selected].fields!).length
|
||||
? category
|
||||
: selected,
|
||||
Object.keys(newFilteredBrowserFields)[0]
|
||||
);
|
||||
setSelectedCategoryId(newSelectedCategoryId);
|
||||
}, INPUT_TIMEOUT);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[browserFields, filterInput, inputTimeoutId.current]
|
||||
);
|
||||
const newSelectedCategoryId =
|
||||
newFilterInput === '' || Object.keys(newFilteredBrowserFields).length === 0
|
||||
? DEFAULT_CATEGORY_NAME
|
||||
: Object.keys(newFilteredBrowserFields)
|
||||
.sort()
|
||||
.reduce<string>(
|
||||
(selected, category) =>
|
||||
newFilteredBrowserFields[category].fields != null &&
|
||||
newFilteredBrowserFields[selected].fields != null &&
|
||||
Object.keys(newFilteredBrowserFields[category].fields!).length >
|
||||
Object.keys(newFilteredBrowserFields[selected].fields!).length
|
||||
? category
|
||||
: selected,
|
||||
Object.keys(newFilteredBrowserFields)[0]
|
||||
);
|
||||
setSelectedCategoryId(newSelectedCategoryId);
|
||||
}, INPUT_TIMEOUT);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[browserFields, filterInput, inputTimeoutId.current]
|
||||
);
|
||||
|
||||
/**
|
||||
* Invoked when the user clicks a category name in the left-hand side of
|
||||
* the field browser
|
||||
*/
|
||||
const updateSelectedCategoryId = useCallback((categoryId: string) => {
|
||||
setSelectedCategoryId(categoryId);
|
||||
}, []);
|
||||
/**
|
||||
* Invoked when the user clicks a category name in the left-hand side of
|
||||
* the field browser
|
||||
*/
|
||||
const updateSelectedCategoryId = useCallback((categoryId: string) => {
|
||||
setSelectedCategoryId(categoryId);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Invoked when the user clicks on the context menu to view a category's
|
||||
* columns in the timeline, this function dispatches the action that
|
||||
* causes the timeline display those columns.
|
||||
*/
|
||||
const updateColumnsAndSelectCategoryId = useCallback((columns: ColumnHeaderOptions[]) => {
|
||||
onUpdateColumns(columns); // show the category columns in the timeline
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
/**
|
||||
* Invoked when the user clicks on the context menu to view a category's
|
||||
* columns in the timeline, this function dispatches the action that
|
||||
* causes the timeline display those columns.
|
||||
*/
|
||||
const updateColumnsAndSelectCategoryId = useCallback((columns: ColumnHeaderOptions[]) => {
|
||||
onUpdateColumns(columns); // show the category columns in the timeline
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
/** Invoked when the field browser should be hidden */
|
||||
const hideFieldBrowser = useCallback(() => {
|
||||
setFilterInput('');
|
||||
setFilterInput('');
|
||||
setFilteredBrowserFields(null);
|
||||
setIsSearching(false);
|
||||
setSelectedCategoryId(DEFAULT_CATEGORY_NAME);
|
||||
setShow(false);
|
||||
}, []);
|
||||
// only merge in the default category if the field browser is visible
|
||||
const browserFieldsWithDefaultCategory = useMemo(
|
||||
() => (show ? mergeBrowserFieldsWithDefaultCategory(browserFields) : {}),
|
||||
[show, browserFields]
|
||||
);
|
||||
/** Invoked when the field browser should be hidden */
|
||||
const hideFieldBrowser = useCallback(() => {
|
||||
setFilterInput('');
|
||||
setFilterInput('');
|
||||
setFilteredBrowserFields(null);
|
||||
setIsSearching(false);
|
||||
setSelectedCategoryId(DEFAULT_CATEGORY_NAME);
|
||||
setShow(false);
|
||||
}, []);
|
||||
// only merge in the default category if the field browser is visible
|
||||
const browserFieldsWithDefaultCategory = useMemo(
|
||||
() => (show ? mergeBrowserFieldsWithDefaultCategory(browserFields) : {}),
|
||||
[show, browserFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FieldsBrowserButtonContainer data-test-subj="fields-browser-button-container">
|
||||
<EuiToolTip content={i18n.CUSTOMIZE_COLUMNS}>
|
||||
{isEventViewer ? (
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.CUSTOMIZE_COLUMNS}
|
||||
className={fieldsButtonClassName}
|
||||
data-test-subj="show-field-browser-gear"
|
||||
iconType="list"
|
||||
onClick={toggleShow}
|
||||
/>
|
||||
) : (
|
||||
<EuiButtonEmpty
|
||||
className={fieldsButtonClassName}
|
||||
data-test-subj="show-field-browser"
|
||||
iconType="list"
|
||||
onClick={toggleShow}
|
||||
size="xs"
|
||||
>
|
||||
{i18n.FIELDS}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
return (
|
||||
<FieldsBrowserButtonContainer data-test-subj="fields-browser-button-container">
|
||||
<EuiToolTip content={i18n.CUSTOMIZE_COLUMNS}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.CUSTOMIZE_COLUMNS}
|
||||
className={fieldsButtonClassName}
|
||||
data-test-subj="show-field-browser"
|
||||
iconType="list"
|
||||
onClick={toggleShow}
|
||||
>
|
||||
{i18n.FIELDS}
|
||||
</EuiButtonIcon>
|
||||
</EuiToolTip>
|
||||
|
||||
{show && (
|
||||
<FieldsBrowser
|
||||
browserFields={browserFieldsWithDefaultCategory}
|
||||
columnHeaders={columnHeaders}
|
||||
filteredBrowserFields={
|
||||
filteredBrowserFields != null
|
||||
? filteredBrowserFields
|
||||
: browserFieldsWithDefaultCategory
|
||||
}
|
||||
height={height}
|
||||
isEventViewer={isEventViewer}
|
||||
isSearching={isSearching}
|
||||
onCategorySelected={updateSelectedCategoryId}
|
||||
onFieldSelected={onFieldSelected}
|
||||
onHideFieldBrowser={hideFieldBrowser}
|
||||
onOutsideClick={show ? hideFieldBrowser : noop}
|
||||
onSearchInputChange={updateFilter}
|
||||
onUpdateColumns={updateColumnsAndSelectCategoryId}
|
||||
searchInput={filterInput}
|
||||
selectedCategoryId={selectedCategoryId}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
</FieldsBrowserButtonContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const mapDispatchToProps = {
|
||||
removeColumn: timelineActions.removeColumn,
|
||||
upsertColumn: timelineActions.upsertColumn,
|
||||
{show && (
|
||||
<FieldsBrowser
|
||||
browserFields={browserFieldsWithDefaultCategory}
|
||||
columnHeaders={columnHeaders}
|
||||
filteredBrowserFields={
|
||||
filteredBrowserFields != null ? filteredBrowserFields : browserFieldsWithDefaultCategory
|
||||
}
|
||||
height={height}
|
||||
isEventViewer={isEventViewer}
|
||||
isSearching={isSearching}
|
||||
onCategorySelected={updateSelectedCategoryId}
|
||||
onFieldSelected={onFieldSelected}
|
||||
onHideFieldBrowser={hideFieldBrowser}
|
||||
onOutsideClick={show ? hideFieldBrowser : noop}
|
||||
onSearchInputChange={updateFilter}
|
||||
onUpdateColumns={updateColumnsAndSelectCategoryId}
|
||||
searchInput={filterInput}
|
||||
selectedCategoryId={selectedCategoryId}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
</FieldsBrowserButtonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export const StatefulFieldsBrowser = connector(React.memo(StatefulFieldsBrowserComponent));
|
||||
export const StatefulFieldsBrowser = React.memo(StatefulFieldsBrowserComponent);
|
||||
|
|
|
@ -28,6 +28,7 @@ interface FlyoutPaneComponentProps {
|
|||
|
||||
const EuiFlyoutContainer = styled.div`
|
||||
.timeline-flyout {
|
||||
z-index: 4001;
|
||||
min-width: 150px;
|
||||
width: auto;
|
||||
}
|
||||
|
|
|
@ -270,6 +270,7 @@ describe('helpers', () => {
|
|||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
|
@ -294,7 +295,6 @@ describe('helpers', () => {
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
|
@ -368,6 +368,7 @@ describe('helpers', () => {
|
|||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
|
@ -392,7 +393,6 @@ describe('helpers', () => {
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
|
@ -502,6 +502,7 @@ describe('helpers', () => {
|
|||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
|
@ -532,7 +533,6 @@ describe('helpers', () => {
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
|
@ -628,6 +628,7 @@ describe('helpers', () => {
|
|||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
|
@ -701,7 +702,6 @@ describe('helpers', () => {
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
TimelineTypeLiteralWithNull,
|
||||
TimelineStatus,
|
||||
TemplateTimelineTypeLiteral,
|
||||
RowRendererId,
|
||||
} from '../../../../common/types/timeline';
|
||||
|
||||
/** The users who added a timeline to favorites */
|
||||
|
@ -46,6 +47,7 @@ export interface OpenTimelineResult {
|
|||
created?: number | null;
|
||||
description?: string | null;
|
||||
eventIdToNoteIds?: Readonly<Record<string, string[]>> | null;
|
||||
excludedRowRendererIds?: RowRendererId[] | null;
|
||||
favorite?: FavoriteTimelineResult[] | null;
|
||||
noteIds?: string[] | null;
|
||||
notes?: TimelineResultNote[] | null;
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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 { EuiLink } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ExternalLinkIcon } from '../../../../common/components/external_link_icon';
|
||||
|
||||
import { RowRendererId } from '../../../../../common/types/timeline';
|
||||
import {
|
||||
AuditdExample,
|
||||
AuditdFileExample,
|
||||
NetflowExample,
|
||||
SuricataExample,
|
||||
SystemExample,
|
||||
SystemDnsExample,
|
||||
SystemEndgameProcessExample,
|
||||
SystemFileExample,
|
||||
SystemFimExample,
|
||||
SystemSecurityEventExample,
|
||||
SystemSocketExample,
|
||||
ZeekExample,
|
||||
} from '../examples';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const Link = ({ children, url }: { children: React.ReactNode; url: string }) => (
|
||||
<EuiLink
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener nofollow noreferrer"
|
||||
data-test-subj="externalLink"
|
||||
>
|
||||
{children}
|
||||
<ExternalLinkIcon data-test-subj="externalLinkIcon" />
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
export interface RowRendererOption {
|
||||
id: RowRendererId;
|
||||
name: string;
|
||||
description: React.ReactNode;
|
||||
searchableDescription: string;
|
||||
example: React.ReactNode;
|
||||
}
|
||||
|
||||
export const renderers: RowRendererOption[] = [
|
||||
{
|
||||
id: RowRendererId.auditd,
|
||||
name: i18n.AUDITD_NAME,
|
||||
description: (
|
||||
<span>
|
||||
<Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-auditd.html">
|
||||
{i18n.AUDITD_NAME}
|
||||
</Link>{' '}
|
||||
{i18n.AUDITD_DESCRIPTION_PART1}
|
||||
</span>
|
||||
),
|
||||
example: AuditdExample,
|
||||
searchableDescription: `${i18n.AUDITD_NAME} ${i18n.AUDITD_DESCRIPTION_PART1}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.auditd_file,
|
||||
name: i18n.AUDITD_FILE_NAME,
|
||||
description: (
|
||||
<span>
|
||||
<Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-auditd.html">
|
||||
{i18n.AUDITD_NAME}
|
||||
</Link>{' '}
|
||||
{i18n.AUDITD_FILE_DESCRIPTION_PART1}
|
||||
</span>
|
||||
),
|
||||
example: AuditdFileExample,
|
||||
searchableDescription: `${i18n.AUDITD_FILE_NAME} ${i18n.AUDITD_FILE_DESCRIPTION_PART1}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system_security_event,
|
||||
name: i18n.AUTHENTICATION_NAME,
|
||||
description: (
|
||||
<div>
|
||||
<p>{i18n.AUTHENTICATION_DESCRIPTION_PART1}</p>
|
||||
<br />
|
||||
<p>{i18n.AUTHENTICATION_DESCRIPTION_PART2}</p>
|
||||
</div>
|
||||
),
|
||||
example: SystemSecurityEventExample,
|
||||
searchableDescription: `${i18n.AUTHENTICATION_DESCRIPTION_PART1} ${i18n.AUTHENTICATION_DESCRIPTION_PART2}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system_dns,
|
||||
name: i18n.DNS_NAME,
|
||||
description: i18n.DNS_DESCRIPTION_PART1,
|
||||
example: SystemDnsExample,
|
||||
searchableDescription: i18n.DNS_DESCRIPTION_PART1,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.netflow,
|
||||
name: i18n.FLOW_NAME,
|
||||
description: (
|
||||
<div>
|
||||
<p>{i18n.FLOW_DESCRIPTION_PART1}</p>
|
||||
<br />
|
||||
<p>{i18n.FLOW_DESCRIPTION_PART2}</p>
|
||||
</div>
|
||||
),
|
||||
example: NetflowExample,
|
||||
searchableDescription: `${i18n.FLOW_DESCRIPTION_PART1} ${i18n.FLOW_DESCRIPTION_PART2}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system,
|
||||
name: i18n.SYSTEM_NAME,
|
||||
description: (
|
||||
<div>
|
||||
<p>
|
||||
{i18n.SYSTEM_DESCRIPTION_PART1}{' '}
|
||||
<Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-system.html">
|
||||
{i18n.SYSTEM_NAME}
|
||||
</Link>{' '}
|
||||
{i18n.SYSTEM_DESCRIPTION_PART2}
|
||||
</p>
|
||||
<br />
|
||||
<p>{i18n.SYSTEM_DESCRIPTION_PART3}</p>
|
||||
</div>
|
||||
),
|
||||
example: SystemExample,
|
||||
searchableDescription: `${i18n.SYSTEM_DESCRIPTION_PART1} ${i18n.SYSTEM_NAME} ${i18n.SYSTEM_DESCRIPTION_PART2} ${i18n.SYSTEM_DESCRIPTION_PART3}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system_endgame_process,
|
||||
name: i18n.PROCESS,
|
||||
description: (
|
||||
<div>
|
||||
<p>{i18n.PROCESS_DESCRIPTION_PART1}</p>
|
||||
<br />
|
||||
<p>{i18n.PROCESS_DESCRIPTION_PART2}</p>
|
||||
</div>
|
||||
),
|
||||
example: SystemEndgameProcessExample,
|
||||
searchableDescription: `${i18n.PROCESS_DESCRIPTION_PART1} ${i18n.PROCESS_DESCRIPTION_PART2}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system_fim,
|
||||
name: i18n.FIM_NAME,
|
||||
description: i18n.FIM_DESCRIPTION_PART1,
|
||||
example: SystemFimExample,
|
||||
searchableDescription: i18n.FIM_DESCRIPTION_PART1,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system_file,
|
||||
name: i18n.FILE_NAME,
|
||||
description: i18n.FILE_DESCRIPTION_PART1,
|
||||
example: SystemFileExample,
|
||||
searchableDescription: i18n.FILE_DESCRIPTION_PART1,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.system_socket,
|
||||
name: i18n.SOCKET_NAME,
|
||||
description: (
|
||||
<div>
|
||||
<p>{i18n.SOCKET_DESCRIPTION_PART1}</p>
|
||||
<br />
|
||||
<p>{i18n.SOCKET_DESCRIPTION_PART2}</p>
|
||||
</div>
|
||||
),
|
||||
example: SystemSocketExample,
|
||||
searchableDescription: `${i18n.SOCKET_DESCRIPTION_PART1} ${i18n.SOCKET_DESCRIPTION_PART2}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.suricata,
|
||||
name: 'Suricata',
|
||||
description: (
|
||||
<p>
|
||||
{i18n.SURICATA_DESCRIPTION_PART1}{' '}
|
||||
<Link url="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-suricata.html">
|
||||
{i18n.SURICATA_NAME}
|
||||
</Link>{' '}
|
||||
{i18n.SURICATA_DESCRIPTION_PART2}
|
||||
</p>
|
||||
),
|
||||
example: SuricataExample,
|
||||
searchableDescription: `${i18n.SURICATA_DESCRIPTION_PART1} ${i18n.SURICATA_NAME} ${i18n.SURICATA_DESCRIPTION_PART2}`,
|
||||
},
|
||||
{
|
||||
id: RowRendererId.zeek,
|
||||
name: i18n.ZEEK_NAME,
|
||||
description: (
|
||||
<p>
|
||||
{i18n.ZEEK_DESCRIPTION_PART1}{' '}
|
||||
<Link url="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-zeek.html">
|
||||
{i18n.ZEEK_NAME}
|
||||
</Link>{' '}
|
||||
{i18n.ZEEK_DESCRIPTION_PART2}
|
||||
</p>
|
||||
),
|
||||
example: ZeekExample,
|
||||
searchableDescription: `${i18n.ZEEK_DESCRIPTION_PART1} ${i18n.ZEEK_NAME} ${i18n.ZEEK_DESCRIPTION_PART2}`,
|
||||
},
|
||||
];
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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 AUDITD_NAME = i18n.translate('xpack.securitySolution.eventRenderers.auditdName', {
|
||||
defaultMessage: 'Auditd',
|
||||
});
|
||||
|
||||
export const AUDITD_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.auditdDescriptionPart1',
|
||||
{
|
||||
defaultMessage: 'audit events convey security-relevant logs from the Linux Audit Framework.',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUDITD_FILE_NAME = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.auditdFileName',
|
||||
{
|
||||
defaultMessage: 'Auditd File',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUDITD_FILE_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.auditdFileDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'File events show users (and system accounts) performing CRUD operations on files via specific processes.',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_NAME = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.authenticationName',
|
||||
{
|
||||
defaultMessage: 'Authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.authenticationDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'Authentication events show users (and system accounts) successfully or unsuccessfully logging into hosts.',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.authenticationDescriptionPart2',
|
||||
{
|
||||
defaultMessage:
|
||||
'Some authentication events may include additional details when users authenticate on behalf of other users.',
|
||||
}
|
||||
);
|
||||
|
||||
export const DNS_NAME = i18n.translate('xpack.securitySolution.eventRenderers.dnsName', {
|
||||
defaultMessage: 'Domain Name System (DNS)',
|
||||
});
|
||||
|
||||
export const DNS_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.dnsDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'Domain Name System (DNS) events show users (and system accounts) making requests via specific processes to translate from host names to IP addresses.',
|
||||
}
|
||||
);
|
||||
|
||||
export const FILE_NAME = i18n.translate('xpack.securitySolution.eventRenderers.fileName', {
|
||||
defaultMessage: 'File',
|
||||
});
|
||||
|
||||
export const FILE_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.fileDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'File events show users (and system accounts) performing CRUD operations on files via specific processes.',
|
||||
}
|
||||
);
|
||||
|
||||
export const FIM_NAME = i18n.translate('xpack.securitySolution.eventRenderers.fimName', {
|
||||
defaultMessage: 'File Integrity Module (FIM)',
|
||||
});
|
||||
|
||||
export const FIM_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.fimDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'File Integrity Module (FIM) events show users (and system accounts) performing CRUD operations on files via specific processes.',
|
||||
}
|
||||
);
|
||||
|
||||
export const FLOW_NAME = i18n.translate('xpack.securitySolution.eventRenderers.flowName', {
|
||||
defaultMessage: 'Flow',
|
||||
});
|
||||
|
||||
export const FLOW_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.flowDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
"The Flow renderer visualizes the flow of data between a source and destination. It's applicable to many types of events.",
|
||||
}
|
||||
);
|
||||
|
||||
export const FLOW_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.flowDescriptionPart2',
|
||||
{
|
||||
defaultMessage:
|
||||
'The hosts, ports, protocol, direction, duration, amount transferred, process, geographic location, and other details are visualized when available.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PROCESS = i18n.translate('xpack.securitySolution.eventRenderers.processName', {
|
||||
defaultMessage: 'Process',
|
||||
});
|
||||
|
||||
export const PROCESS_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.processDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'Process events show users (and system accounts) starting and stopping processes.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PROCESS_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.processDescriptionPart2',
|
||||
{
|
||||
defaultMessage:
|
||||
'Details including the command line arguments, parent process, and if applicable, file hashes are displayed when available.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SOCKET_NAME = i18n.translate('xpack.securitySolution.eventRenderers.socketName', {
|
||||
defaultMessage: 'Socket (Network)',
|
||||
});
|
||||
|
||||
export const SOCKET_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.socketDescriptionPart1',
|
||||
{
|
||||
defaultMessage:
|
||||
'Socket (Network) events show processes listening, accepting, and closing connections.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SOCKET_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.socketDescriptionPart2',
|
||||
{
|
||||
defaultMessage:
|
||||
'Details including the protocol, ports, and a community ID for correlating all network events related to a single flow are displayed when available.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SURICATA_NAME = i18n.translate('xpack.securitySolution.eventRenderers.suricataName', {
|
||||
defaultMessage: 'Suricata',
|
||||
});
|
||||
|
||||
export const SURICATA_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.suricataDescriptionPart1',
|
||||
{
|
||||
defaultMessage: 'Summarizes',
|
||||
}
|
||||
);
|
||||
|
||||
export const SURICATA_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.suricataDescriptionPart2',
|
||||
{
|
||||
defaultMessage:
|
||||
'intrusion detection (IDS), inline intrusion prevention (IPS), and network security monitoring (NSM) events',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_NAME = i18n.translate('xpack.securitySolution.eventRenderers.systemName', {
|
||||
defaultMessage: 'System',
|
||||
});
|
||||
|
||||
export const SYSTEM_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.systemDescriptionPart1',
|
||||
{
|
||||
defaultMessage: 'The Auditbeat',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.systemDescriptionPart2',
|
||||
{
|
||||
defaultMessage: 'module collects various security related information about a system.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SYSTEM_DESCRIPTION_PART3 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.systemDescriptionPart3',
|
||||
{
|
||||
defaultMessage:
|
||||
'All datasets send both periodic state information (e.g. all currently running processes) and real-time changes (e.g. when a new process starts or stops).',
|
||||
}
|
||||
);
|
||||
|
||||
export const ZEEK_NAME = i18n.translate('xpack.securitySolution.eventRenderers.zeekName', {
|
||||
defaultMessage: 'Zeek (formerly Bro)',
|
||||
});
|
||||
|
||||
export const ZEEK_DESCRIPTION_PART1 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.zeekDescriptionPart1',
|
||||
{
|
||||
defaultMessage: 'Summarizes events from the',
|
||||
}
|
||||
);
|
||||
|
||||
export const ZEEK_DESCRIPTION_PART2 = i18n.translate(
|
||||
'xpack.securitySolution.eventRenderers.zeekDescriptionPart2',
|
||||
{
|
||||
defaultMessage: 'Network Security Monitoring (NSM) tool',
|
||||
}
|
||||
);
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID = 'row-renderer-example';
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
|
||||
import { createGenericAuditRowRenderer } from '../../timeline/body/renderers/auditd/generic_row_renderer';
|
||||
import { CONNECTED_USING } from '../../timeline/body/renderers/auditd/translations';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const AuditdExampleComponent: React.FC = () => {
|
||||
const auditdRowRenderer = createGenericAuditRowRenderer({
|
||||
actionName: 'connected-to',
|
||||
text: CONNECTED_USING,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{auditdRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[26].ecs,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const AuditdExample = React.memo(AuditdExampleComponent);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
|
||||
import { createGenericFileRowRenderer } from '../../timeline/body/renderers/auditd/generic_row_renderer';
|
||||
import { OPENED_FILE, USING } from '../../timeline/body/renderers/auditd/translations';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const AuditdFileExampleComponent: React.FC = () => {
|
||||
const auditdFileRowRenderer = createGenericFileRowRenderer({
|
||||
actionName: 'opened-file',
|
||||
text: `${OPENED_FILE} ${USING}`,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{auditdFileRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[27].ecs,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const AuditdFileExample = React.memo(AuditdFileExampleComponent);
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export * from './auditd';
|
||||
export * from './auditd_file';
|
||||
export * from './netflow';
|
||||
export * from './suricata';
|
||||
export * from './system';
|
||||
export * from './system_dns';
|
||||
export * from './system_endgame_process';
|
||||
export * from './system_file';
|
||||
export * from './system_fim';
|
||||
export * from './system_security_event';
|
||||
export * from './system_socket';
|
||||
export * from './zeek';
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { getMockNetflowData } from '../../../../common/mock/netflow';
|
||||
import { netflowRowRenderer } from '../../timeline/body/renderers/netflow/netflow_row_renderer';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const NetflowExampleComponent: React.FC = () => (
|
||||
<>
|
||||
{netflowRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: getMockNetflowData(),
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
export const NetflowExample = React.memo(NetflowExampleComponent);
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
|
||||
import { suricataRowRenderer } from '../../timeline/body/renderers/suricata/suricata_row_renderer';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SuricataExampleComponent: React.FC = () => (
|
||||
<>
|
||||
{suricataRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[2].ecs,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
export const SuricataExample = React.memo(SuricataExampleComponent);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { TERMINATED_PROCESS } from '../../timeline/body/renderers/system/translations';
|
||||
import { createGenericSystemRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { mockEndgameTerminationEvent } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemExampleComponent: React.FC = () => {
|
||||
const systemRowRenderer = createGenericSystemRowRenderer({
|
||||
actionName: 'termination_event',
|
||||
text: TERMINATED_PROCESS,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameTerminationEvent,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemExample = React.memo(SystemExampleComponent);
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { createDnsRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { mockEndgameDnsRequest } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemDnsExampleComponent: React.FC = () => {
|
||||
const systemDnsRowRenderer = createDnsRowRenderer();
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemDnsRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameDnsRequest,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemDnsExample = React.memo(SystemDnsExampleComponent);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { createEndgameProcessRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { mockEndgameCreationEvent } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { PROCESS_STARTED } from '../../timeline/body/renderers/system/translations';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemEndgameProcessExampleComponent: React.FC = () => {
|
||||
const systemEndgameProcessRowRenderer = createEndgameProcessRowRenderer({
|
||||
actionName: 'creation_event',
|
||||
text: PROCESS_STARTED,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemEndgameProcessRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameCreationEvent,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemEndgameProcessExample = React.memo(SystemEndgameProcessExampleComponent);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { mockEndgameFileDeleteEvent } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { createGenericFileRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { DELETED_FILE } from '../../timeline/body/renderers/system/translations';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemFileExampleComponent: React.FC = () => {
|
||||
const systemFileRowRenderer = createGenericFileRowRenderer({
|
||||
actionName: 'file_delete_event',
|
||||
text: DELETED_FILE,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemFileRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameFileDeleteEvent,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemFileExample = React.memo(SystemFileExampleComponent);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { mockEndgameFileCreateEvent } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { createFimRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { CREATED_FILE } from '../../timeline/body/renderers/system/translations';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemFimExampleComponent: React.FC = () => {
|
||||
const systemFimRowRenderer = createFimRowRenderer({
|
||||
actionName: 'file_create_event',
|
||||
text: CREATED_FILE,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemFimRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameFileCreateEvent,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemFimExample = React.memo(SystemFimExampleComponent);
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { createSecurityEventRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { mockEndgameUserLogon } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemSecurityEventExampleComponent: React.FC = () => {
|
||||
const systemSecurityEventRowRenderer = createSecurityEventRowRenderer({
|
||||
actionName: 'user_logon',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{systemSecurityEventRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameUserLogon,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemSecurityEventExample = React.memo(SystemSecurityEventExampleComponent);
|
|
@ -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 { ACCEPTED_A_CONNECTION_VIA } from '../../timeline/body/renderers/system/translations';
|
||||
import { createSocketRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
|
||||
import { mockEndgameIpv4ConnectionAcceptEvent } from '../../../../common/mock/mock_endgame_ecs_data';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const SystemSocketExampleComponent: React.FC = () => {
|
||||
const systemSocketRowRenderer = createSocketRowRenderer({
|
||||
actionName: 'ipv4_connection_accept_event',
|
||||
text: ACCEPTED_A_CONNECTION_VIA,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{systemSocketRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockEndgameIpv4ConnectionAcceptEvent,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const SystemSocketExample = React.memo(SystemSocketExampleComponent);
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { mockTimelineData } from '../../../../common/mock/mock_timeline_data';
|
||||
import { zeekRowRenderer } from '../../timeline/body/renderers/zeek/zeek_row_renderer';
|
||||
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
|
||||
|
||||
const ZeekExampleComponent: React.FC = () => (
|
||||
<>
|
||||
{zeekRowRenderer.renderRow({
|
||||
browserFields: {},
|
||||
data: mockTimelineData[13].ecs,
|
||||
timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
export const ZeekExample = React.memo(ZeekExampleComponent);
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiButtonIcon,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
EuiOverlayMask,
|
||||
EuiModal,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiModalBody,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiInMemoryTable,
|
||||
} from '@elastic/eui';
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { State } from '../../../common/store';
|
||||
|
||||
import { renderers } from './catalog';
|
||||
import { setExcludedRowRendererIds as dispatchSetExcludedRowRendererIds } from '../../../timelines/store/timeline/actions';
|
||||
import { RowRenderersBrowser } from './row_renderers_browser';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const StyledEuiModal = styled(EuiModal)`
|
||||
margin: 0 auto;
|
||||
max-width: 95vw;
|
||||
min-height: 95vh;
|
||||
|
||||
> .euiModal__flex {
|
||||
max-height: 95vh;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEuiModalBody = styled(EuiModalBody)`
|
||||
.euiModalBody__overflow {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
> div:first-child {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.euiBasicTable {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEuiOverlayMask = styled(EuiOverlayMask)`
|
||||
z-index: 8001;
|
||||
padding-bottom: 0;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
interface StatefulRowRenderersBrowserProps {
|
||||
timelineId: string;
|
||||
}
|
||||
|
||||
const StatefulRowRenderersBrowserComponent: React.FC<StatefulRowRenderersBrowserProps> = ({
|
||||
timelineId,
|
||||
}) => {
|
||||
const tableRef = useRef<EuiInMemoryTable<{}>>();
|
||||
const dispatch = useDispatch();
|
||||
const excludedRowRendererIds = useSelector(
|
||||
(state: State) => state.timeline.timelineById[timelineId]?.excludedRowRendererIds || []
|
||||
);
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const setExcludedRowRendererIds = useCallback(
|
||||
(payload) =>
|
||||
dispatch(
|
||||
dispatchSetExcludedRowRendererIds({
|
||||
id: timelineId,
|
||||
excludedRowRendererIds: payload,
|
||||
})
|
||||
),
|
||||
[dispatch, timelineId]
|
||||
);
|
||||
|
||||
const toggleShow = useCallback(() => setShow(!show), [show]);
|
||||
|
||||
const hideFieldBrowser = useCallback(() => setShow(false), []);
|
||||
|
||||
const handleDisableAll = useCallback(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
tableRef?.current?.setSelection([]);
|
||||
}, [tableRef]);
|
||||
|
||||
const handleEnableAll = useCallback(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
tableRef?.current?.setSelection(renderers);
|
||||
}, [tableRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiToolTip content={i18n.CUSTOMIZE_EVENT_RENDERERS_TITLE}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.CUSTOMIZE_EVENT_RENDERERS_TITLE}
|
||||
data-test-subj="show-row-renderers-gear"
|
||||
iconType="gear"
|
||||
onClick={toggleShow}
|
||||
>
|
||||
{i18n.EVENT_RENDERERS_TITLE}
|
||||
</EuiButtonIcon>
|
||||
</EuiToolTip>
|
||||
|
||||
{show && (
|
||||
<StyledEuiOverlayMask>
|
||||
<StyledEuiModal onClose={hideFieldBrowser}>
|
||||
<EuiModalHeader>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiModalHeaderTitle>{i18n.CUSTOMIZE_EVENT_RENDERERS_TITLE}</EuiModalHeaderTitle>
|
||||
<EuiText size="s">{i18n.CUSTOMIZE_EVENT_RENDERERS_DESCRIPTION}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
data-test-subj="disable-all"
|
||||
onClick={handleDisableAll}
|
||||
>
|
||||
{i18n.DISABLE_ALL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
size="s"
|
||||
data-test-subj="enable-all"
|
||||
onClick={handleEnableAll}
|
||||
>
|
||||
{i18n.ENABLE_ALL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeader>
|
||||
|
||||
<StyledEuiModalBody>
|
||||
<RowRenderersBrowser
|
||||
ref={tableRef}
|
||||
excludedRowRendererIds={excludedRowRendererIds}
|
||||
setExcludedRowRendererIds={setExcludedRowRendererIds}
|
||||
/>
|
||||
</StyledEuiModalBody>
|
||||
</StyledEuiModal>
|
||||
</StyledEuiOverlayMask>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const StatefulRowRenderersBrowser = React.memo(StatefulRowRenderersBrowserComponent);
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* 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 { EuiFlexItem, EuiInMemoryTable } from '@elastic/eui';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { xor, xorBy } from 'lodash/fp';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { RowRendererId } from '../../../../common/types/timeline';
|
||||
import { renderers, RowRendererOption } from './catalog';
|
||||
|
||||
interface RowRenderersBrowserProps {
|
||||
excludedRowRendererIds: RowRendererId[];
|
||||
setExcludedRowRendererIds: (excludedRowRendererIds: RowRendererId[]) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)`
|
||||
.euiTable {
|
||||
tr > *:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.euiTableHeaderCellCheckbox > .euiTableCellContent {
|
||||
display: none; // we don't want to display checkbox in the table
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEuiFlexItem = styled(EuiFlexItem)`
|
||||
overflow: auto;
|
||||
|
||||
> div {
|
||||
padding: 0;
|
||||
|
||||
> div {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ExampleWrapperComponent = (Example?: React.ElementType) => {
|
||||
if (!Example) return;
|
||||
|
||||
return (
|
||||
<StyledEuiFlexItem grow={1}>
|
||||
<Example />
|
||||
</StyledEuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
const search = {
|
||||
box: {
|
||||
incremental: true,
|
||||
schema: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Since `searchableDescription` contains raw text to power the Search bar,
|
||||
* this "noop" function ensures it's not actually rendered
|
||||
*/
|
||||
const renderSearchableDescriptionNoop = () => <></>;
|
||||
|
||||
const initialSorting = {
|
||||
sort: {
|
||||
field: 'name',
|
||||
direction: 'asc',
|
||||
},
|
||||
};
|
||||
|
||||
const StyledNameButton = styled.button`
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const RowRenderersBrowserComponent = React.forwardRef(
|
||||
({ excludedRowRendererIds = [], setExcludedRowRendererIds }: RowRenderersBrowserProps, ref) => {
|
||||
const notExcludedRowRenderers = useMemo(() => {
|
||||
if (excludedRowRendererIds.length === Object.keys(RowRendererId).length) return [];
|
||||
|
||||
return renderers.filter((renderer) => !excludedRowRendererIds.includes(renderer.id));
|
||||
}, [excludedRowRendererIds]);
|
||||
|
||||
const handleNameClick = useCallback(
|
||||
(item: RowRendererOption) => () => {
|
||||
const newSelection = xor([item], notExcludedRowRenderers);
|
||||
// @ts-ignore
|
||||
ref?.current?.setSelection(newSelection); // eslint-disable-line no-unused-expressions
|
||||
},
|
||||
[notExcludedRowRenderers, ref]
|
||||
);
|
||||
|
||||
const nameColumnRenderCallback = useCallback(
|
||||
(value, item) => (
|
||||
<StyledNameButton className="kbn-resetFocusState" onClick={handleNameClick(item)}>
|
||||
{value}
|
||||
</StyledNameButton>
|
||||
),
|
||||
[handleNameClick]
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
field: 'name',
|
||||
name: 'Name',
|
||||
sortable: true,
|
||||
width: '10%',
|
||||
render: nameColumnRenderCallback,
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
name: 'Description',
|
||||
width: '25%',
|
||||
render: (description: React.ReactNode) => description,
|
||||
},
|
||||
{
|
||||
field: 'example',
|
||||
name: 'Example',
|
||||
width: '65%',
|
||||
render: ExampleWrapperComponent,
|
||||
},
|
||||
{
|
||||
field: 'searchableDescription',
|
||||
name: 'Searchable Description',
|
||||
sortable: false,
|
||||
width: '0px',
|
||||
render: renderSearchableDescriptionNoop,
|
||||
},
|
||||
],
|
||||
[nameColumnRenderCallback]
|
||||
);
|
||||
|
||||
const handleSelectable = useCallback(() => true, []);
|
||||
|
||||
const handleSelectionChange = useCallback(
|
||||
(selection: RowRendererOption[]) => {
|
||||
if (!selection || !selection.length)
|
||||
return setExcludedRowRendererIds(Object.values(RowRendererId));
|
||||
|
||||
const excludedRowRenderers = xorBy('id', renderers, selection);
|
||||
|
||||
setExcludedRowRendererIds(excludedRowRenderers.map((rowRenderer) => rowRenderer.id));
|
||||
},
|
||||
[setExcludedRowRendererIds]
|
||||
);
|
||||
|
||||
const selectionValue = useMemo(
|
||||
() => ({
|
||||
selectable: handleSelectable,
|
||||
onSelectionChange: handleSelectionChange,
|
||||
initialSelected: notExcludedRowRenderers,
|
||||
}),
|
||||
[handleSelectable, handleSelectionChange, notExcludedRowRenderers]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledEuiInMemoryTable
|
||||
ref={ref}
|
||||
items={renderers}
|
||||
itemId="id"
|
||||
columns={columns}
|
||||
search={search}
|
||||
sorting={initialSorting}
|
||||
isSelectable={true}
|
||||
selection={selectionValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RowRenderersBrowserComponent.displayName = 'RowRenderersBrowserComponent';
|
||||
|
||||
export const RowRenderersBrowser = React.memo(RowRenderersBrowserComponent);
|
||||
|
||||
RowRenderersBrowser.displayName = 'RowRenderersBrowser';
|
|
@ -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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const EVENT_RENDERERS_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.customizeEventRenderers.eventRenderersTitle',
|
||||
{
|
||||
defaultMessage: 'Event Renderers',
|
||||
}
|
||||
);
|
||||
|
||||
export const CUSTOMIZE_EVENT_RENDERERS_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.customizeEventRenderers.customizeEventRenderersTitle',
|
||||
{
|
||||
defaultMessage: 'Customize Event Renderers',
|
||||
}
|
||||
);
|
||||
|
||||
export const CUSTOMIZE_EVENT_RENDERERS_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.customizeEventRenderers.customizeEventRenderersDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Event Renderers automatically convey the most relevant details in an event to reveal its story',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENABLE_ALL = i18n.translate(
|
||||
'xpack.securitySolution.customizeEventRenderers.enableAllRenderersButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Enable all',
|
||||
}
|
||||
);
|
||||
|
||||
export const DISABLE_ALL = i18n.translate(
|
||||
'xpack.securitySolution.customizeEventRenderers.disableAllRenderersButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Disable all',
|
||||
}
|
||||
);
|
|
@ -119,9 +119,9 @@ export const Actions = React.memo<Props>(
|
|||
|
||||
<EventsTd>
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
{loading && <EventsLoading />}
|
||||
|
||||
{!loading && (
|
||||
{loading ? (
|
||||
<EventsLoading />
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={expanded ? i18n.COLLAPSE : i18n.EXPAND}
|
||||
data-test-subj="expand-event"
|
||||
|
|
|
@ -8,479 +8,481 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
|
|||
<styled.div
|
||||
actionsColumnWidth={76}
|
||||
data-test-subj="actions-container"
|
||||
isEventViewer={false}
|
||||
>
|
||||
<styled.div>
|
||||
<styled.div
|
||||
textAlign="center"
|
||||
width={24}
|
||||
>
|
||||
<Connect(Component)
|
||||
browserFields={
|
||||
<Memo(StatefulFieldsBrowserComponent)
|
||||
browserFields={
|
||||
Object {
|
||||
"agent": Object {
|
||||
"fields": Object {
|
||||
"agent.ephemeral_id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.",
|
||||
"example": "8a4f500f",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.ephemeral_id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"agent.hostname": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.hostname",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"agent.id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.",
|
||||
"example": "8a4f500d",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"agent.name": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": "Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.",
|
||||
"example": "foo",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.name",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"auditd": Object {
|
||||
"fields": Object {
|
||||
"auditd.data.a0": Object {
|
||||
"aggregatable": true,
|
||||
"category": "auditd",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
],
|
||||
"name": "auditd.data.a0",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"auditd.data.a1": Object {
|
||||
"aggregatable": true,
|
||||
"category": "auditd",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
],
|
||||
"name": "auditd.data.a1",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"auditd.data.a2": Object {
|
||||
"aggregatable": true,
|
||||
"category": "auditd",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
],
|
||||
"name": "auditd.data.a2",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"base": Object {
|
||||
"fields": Object {
|
||||
"@timestamp": Object {
|
||||
"aggregatable": true,
|
||||
"category": "base",
|
||||
"description": "Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.",
|
||||
"example": "2016-05-23T08:05:34.853Z",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "@timestamp",
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
},
|
||||
},
|
||||
"client": Object {
|
||||
"fields": Object {
|
||||
"client.address": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.address",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"client.bytes": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Bytes sent from the client to the server.",
|
||||
"example": "184",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.bytes",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
"client.domain": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Client domain.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.domain",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"client.geo.country_iso_code": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Country ISO code.",
|
||||
"example": "CA",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.geo.country_iso_code",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"cloud": Object {
|
||||
"fields": Object {
|
||||
"cloud.account.id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "cloud",
|
||||
"description": "The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.",
|
||||
"example": "666777888999",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "cloud.account.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"cloud.availability_zone": Object {
|
||||
"aggregatable": true,
|
||||
"category": "cloud",
|
||||
"description": "Availability zone in which this host is running.",
|
||||
"example": "us-east-1c",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "cloud.availability_zone",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"container": Object {
|
||||
"fields": Object {
|
||||
"container.id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "container",
|
||||
"description": "Unique container id.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "container.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"container.image.name": Object {
|
||||
"aggregatable": true,
|
||||
"category": "container",
|
||||
"description": "Name of the image the container was built on.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "container.image.name",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"container.image.tag": Object {
|
||||
"aggregatable": true,
|
||||
"category": "container",
|
||||
"description": "Container image tag.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "container.image.tag",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"destination": Object {
|
||||
"fields": Object {
|
||||
"destination.address": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.address",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"destination.bytes": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Bytes sent from the destination to the source.",
|
||||
"example": "184",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.bytes",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
"destination.domain": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Destination domain.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.domain",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"destination.ip": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.ip",
|
||||
"searchable": true,
|
||||
"type": "ip",
|
||||
},
|
||||
"destination.port": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Port of the destination.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.port",
|
||||
"searchable": true,
|
||||
"type": "long",
|
||||
},
|
||||
},
|
||||
},
|
||||
"event": Object {
|
||||
"fields": Object {
|
||||
"event.end": Object {
|
||||
"aggregatable": true,
|
||||
"category": "event",
|
||||
"description": "event.end contains the date when the event ended or when the activity was last observed.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"apm-*-transaction*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*",
|
||||
"logs-*",
|
||||
],
|
||||
"name": "event.end",
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
},
|
||||
},
|
||||
"source": Object {
|
||||
"fields": Object {
|
||||
"source.ip": Object {
|
||||
"aggregatable": true,
|
||||
"category": "source",
|
||||
"description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "source.ip",
|
||||
"searchable": true,
|
||||
"type": "ip",
|
||||
},
|
||||
"source.port": Object {
|
||||
"aggregatable": true,
|
||||
"category": "source",
|
||||
"description": "Port of the source.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "source.port",
|
||||
"searchable": true,
|
||||
"type": "long",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
columnHeaders={
|
||||
Array [
|
||||
Object {
|
||||
"agent": Object {
|
||||
"fields": Object {
|
||||
"agent.ephemeral_id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.",
|
||||
"example": "8a4f500f",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.ephemeral_id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"agent.hostname": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.hostname",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"agent.id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.",
|
||||
"example": "8a4f500d",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"agent.name": Object {
|
||||
"aggregatable": true,
|
||||
"category": "agent",
|
||||
"description": "Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.",
|
||||
"example": "foo",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "agent.name",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"auditd": Object {
|
||||
"fields": Object {
|
||||
"auditd.data.a0": Object {
|
||||
"aggregatable": true,
|
||||
"category": "auditd",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
],
|
||||
"name": "auditd.data.a0",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"auditd.data.a1": Object {
|
||||
"aggregatable": true,
|
||||
"category": "auditd",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
],
|
||||
"name": "auditd.data.a1",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"auditd.data.a2": Object {
|
||||
"aggregatable": true,
|
||||
"category": "auditd",
|
||||
"description": null,
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
],
|
||||
"name": "auditd.data.a2",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"base": Object {
|
||||
"fields": Object {
|
||||
"@timestamp": Object {
|
||||
"aggregatable": true,
|
||||
"category": "base",
|
||||
"description": "Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.",
|
||||
"example": "2016-05-23T08:05:34.853Z",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "@timestamp",
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
},
|
||||
},
|
||||
"client": Object {
|
||||
"fields": Object {
|
||||
"client.address": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.address",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"client.bytes": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Bytes sent from the client to the server.",
|
||||
"example": "184",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.bytes",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
"client.domain": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Client domain.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.domain",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"client.geo.country_iso_code": Object {
|
||||
"aggregatable": true,
|
||||
"category": "client",
|
||||
"description": "Country ISO code.",
|
||||
"example": "CA",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "client.geo.country_iso_code",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"cloud": Object {
|
||||
"fields": Object {
|
||||
"cloud.account.id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "cloud",
|
||||
"description": "The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.",
|
||||
"example": "666777888999",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "cloud.account.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"cloud.availability_zone": Object {
|
||||
"aggregatable": true,
|
||||
"category": "cloud",
|
||||
"description": "Availability zone in which this host is running.",
|
||||
"example": "us-east-1c",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "cloud.availability_zone",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"container": Object {
|
||||
"fields": Object {
|
||||
"container.id": Object {
|
||||
"aggregatable": true,
|
||||
"category": "container",
|
||||
"description": "Unique container id.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "container.id",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"container.image.name": Object {
|
||||
"aggregatable": true,
|
||||
"category": "container",
|
||||
"description": "Name of the image the container was built on.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "container.image.name",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"container.image.tag": Object {
|
||||
"aggregatable": true,
|
||||
"category": "container",
|
||||
"description": "Container image tag.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "container.image.tag",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"destination": Object {
|
||||
"fields": Object {
|
||||
"destination.address": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the \`.address\` field. Then it should be duplicated to \`.ip\` or \`.domain\`, depending on which one it is.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.address",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"destination.bytes": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Bytes sent from the destination to the source.",
|
||||
"example": "184",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.bytes",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
"destination.domain": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Destination domain.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.domain",
|
||||
"searchable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"destination.ip": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.ip",
|
||||
"searchable": true,
|
||||
"type": "ip",
|
||||
},
|
||||
"destination.port": Object {
|
||||
"aggregatable": true,
|
||||
"category": "destination",
|
||||
"description": "Port of the destination.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "destination.port",
|
||||
"searchable": true,
|
||||
"type": "long",
|
||||
},
|
||||
},
|
||||
},
|
||||
"event": Object {
|
||||
"fields": Object {
|
||||
"event.end": Object {
|
||||
"aggregatable": true,
|
||||
"category": "event",
|
||||
"description": "event.end contains the date when the event ended or when the activity was last observed.",
|
||||
"example": null,
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"apm-*-transaction*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*",
|
||||
"logs-*",
|
||||
],
|
||||
"name": "event.end",
|
||||
"searchable": true,
|
||||
"type": "date",
|
||||
},
|
||||
},
|
||||
},
|
||||
"source": Object {
|
||||
"fields": Object {
|
||||
"source.ip": Object {
|
||||
"aggregatable": true,
|
||||
"category": "source",
|
||||
"description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "source.ip",
|
||||
"searchable": true,
|
||||
"type": "ip",
|
||||
},
|
||||
"source.port": Object {
|
||||
"aggregatable": true,
|
||||
"category": "source",
|
||||
"description": "Port of the source.",
|
||||
"example": "",
|
||||
"format": "",
|
||||
"indexes": Array [
|
||||
"auditbeat",
|
||||
"filebeat",
|
||||
"packetbeat",
|
||||
],
|
||||
"name": "source.port",
|
||||
"searchable": true,
|
||||
"type": "long",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
columnHeaders={
|
||||
Array [
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "@timestamp",
|
||||
"width": 190,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "message",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "event.category",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "event.action",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "host.name",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "source.ip",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "destination.ip",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "user.name",
|
||||
"width": 180,
|
||||
},
|
||||
]
|
||||
}
|
||||
data-test-subj="field-browser"
|
||||
height={300}
|
||||
isEventViewer={false}
|
||||
onUpdateColumns={[MockFunction]}
|
||||
timelineId="test"
|
||||
toggleColumn={[MockFunction]}
|
||||
width={900}
|
||||
/>
|
||||
</styled.div>
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "@timestamp",
|
||||
"width": 190,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "message",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "event.category",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "event.action",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "host.name",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "source.ip",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "destination.ip",
|
||||
"width": 180,
|
||||
},
|
||||
Object {
|
||||
"columnHeaderType": "not-filtered",
|
||||
"id": "user.name",
|
||||
"width": 180,
|
||||
},
|
||||
]
|
||||
}
|
||||
data-test-subj="field-browser"
|
||||
height={300}
|
||||
isEventViewer={false}
|
||||
onUpdateColumns={[MockFunction]}
|
||||
timelineId="test"
|
||||
toggleColumn={[MockFunction]}
|
||||
width={900}
|
||||
/>
|
||||
</styled.div>
|
||||
<styled.div>
|
||||
<Memo(StatefulRowRenderersBrowserComponent)
|
||||
data-test-subj="row-renderers-browser"
|
||||
timelineId="test"
|
||||
/>
|
||||
</styled.div>
|
||||
</styled.div>
|
||||
<Connect(Droppable)
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
SHOW_CHECK_BOXES_COLUMN_WIDTH,
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH,
|
||||
} from '../constants';
|
||||
|
||||
describe('helpers', () => {
|
||||
|
@ -36,12 +36,12 @@ describe('helpers', () => {
|
|||
});
|
||||
|
||||
test('returns the events viewer actions column width when isEventViewer is true', () => {
|
||||
expect(getActionsColumnWidth(true)).toEqual(EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH);
|
||||
expect(getActionsColumnWidth(true)).toEqual(MINIMUM_ACTIONS_COLUMN_WIDTH);
|
||||
});
|
||||
|
||||
test('returns the events viewer actions column width + checkbox width when isEventViewer is true and showCheckboxes is true', () => {
|
||||
expect(getActionsColumnWidth(true, true)).toEqual(
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
SHOW_CHECK_BOXES_COLUMN_WIDTH,
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
|
||||
DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH,
|
||||
} from '../constants';
|
||||
|
||||
/** Enriches the column headers with field details from the specified browserFields */
|
||||
|
@ -42,7 +43,14 @@ export const getActionsColumnWidth = (
|
|||
isEventViewer: boolean,
|
||||
showCheckboxes = false,
|
||||
additionalActionWidth = 0
|
||||
): number =>
|
||||
(showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0) +
|
||||
(isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) +
|
||||
additionalActionWidth;
|
||||
): number => {
|
||||
const checkboxesWidth = showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0;
|
||||
const actionsColumnWidth =
|
||||
checkboxesWidth +
|
||||
(isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) +
|
||||
additionalActionWidth;
|
||||
|
||||
return actionsColumnWidth > MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth
|
||||
? actionsColumnWidth
|
||||
: MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth;
|
||||
};
|
||||
|
|
|
@ -18,8 +18,6 @@ import {
|
|||
DRAG_TYPE_FIELD,
|
||||
droppableTimelineColumnsPrefix,
|
||||
} from '../../../../../common/components/drag_and_drop/helpers';
|
||||
import { StatefulFieldsBrowser } from '../../../fields_browser';
|
||||
import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers';
|
||||
import {
|
||||
OnColumnRemoved,
|
||||
OnColumnResized,
|
||||
|
@ -29,6 +27,9 @@ import {
|
|||
OnUpdateColumns,
|
||||
} from '../../events';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
import { StatefulFieldsBrowser } from '../../../fields_browser';
|
||||
import { StatefulRowRenderersBrowser } from '../../../row_renderers_browser';
|
||||
import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers';
|
||||
import {
|
||||
EventsTh,
|
||||
EventsThContent,
|
||||
|
@ -170,6 +171,7 @@ export const ColumnHeadersComponent = ({
|
|||
<EventsThGroupActions
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
data-test-subj="actions-container"
|
||||
isEventViewer={isEventViewer}
|
||||
>
|
||||
{showSelectAllCheckbox && (
|
||||
<EventsTh>
|
||||
|
@ -185,22 +187,23 @@ export const ColumnHeadersComponent = ({
|
|||
)}
|
||||
|
||||
<EventsTh>
|
||||
<EventsThContent
|
||||
textAlign={showSelectAllCheckbox ? 'left' : 'center'}
|
||||
width={DEFAULT_ICON_BUTTON_WIDTH}
|
||||
>
|
||||
<StatefulFieldsBrowser
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
data-test-subj="field-browser"
|
||||
height={FIELD_BROWSER_HEIGHT}
|
||||
isEventViewer={isEventViewer}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
/>
|
||||
</EventsThContent>
|
||||
<StatefulFieldsBrowser
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
data-test-subj="field-browser"
|
||||
height={FIELD_BROWSER_HEIGHT}
|
||||
isEventViewer={isEventViewer}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
timelineId={timelineId}
|
||||
toggleColumn={toggleColumn}
|
||||
width={FIELD_BROWSER_WIDTH}
|
||||
/>
|
||||
</EventsTh>
|
||||
<EventsTh>
|
||||
<StatefulRowRenderersBrowser
|
||||
data-test-subj="row-renderers-browser"
|
||||
timelineId={timelineId}
|
||||
/>
|
||||
</EventsTh>
|
||||
|
||||
{showEventsSelect && (
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/** The minimum (fixed) width of the Actions column */
|
||||
export const MINIMUM_ACTIONS_COLUMN_WIDTH = 50; // px;
|
||||
|
||||
/** The (fixed) width of the Actions column */
|
||||
export const DEFAULT_ACTIONS_COLUMN_WIDTH = 76; // px;
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Note } from '../../../../../common/lib/note';
|
|||
import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model';
|
||||
import { AssociateNote, UpdateNote } from '../../../notes/helpers';
|
||||
import { OnColumnResized, OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events';
|
||||
import { EventsTdContent, EventsTrData } from '../../styles';
|
||||
import { EventsTd, EventsTdContent, EventsTrData } from '../../styles';
|
||||
import { Actions } from '../actions';
|
||||
import { DataDrivenColumns } from '../data_driven_columns';
|
||||
import { eventHasNotes, getPinOnClick } from '../helpers';
|
||||
|
@ -133,22 +133,24 @@ export const EventColumnView = React.memo<Props>(
|
|||
...acc,
|
||||
icon: [
|
||||
...acc.icon,
|
||||
<EventsTdContent key={action.id} textAlign="center" width={action.width}>
|
||||
<EuiToolTip
|
||||
data-test-subj={`${action.dataTestSubj}-tool-tip`}
|
||||
content={action.content}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={action.ariaLabel}
|
||||
data-test-subj={`${action.dataTestSubj}-button`}
|
||||
iconType={action.iconType}
|
||||
isDisabled={
|
||||
action.isActionDisabled != null ? action.isActionDisabled(ecsData) : false
|
||||
}
|
||||
onClick={() => action.onClick({ eventId: id, ecsData, data })}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EventsTdContent>,
|
||||
<EventsTd>
|
||||
<EventsTdContent key={action.id} textAlign="center" width={action.width}>
|
||||
<EuiToolTip
|
||||
data-test-subj={`${action.dataTestSubj}-tool-tip`}
|
||||
content={action.content}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={action.ariaLabel}
|
||||
data-test-subj={`${action.dataTestSubj}-button`}
|
||||
iconType={action.iconType}
|
||||
isDisabled={
|
||||
action.isActionDisabled != null ? action.isActionDisabled(ecsData) : false
|
||||
}
|
||||
onClick={() => action.onClick({ eventId: id, ecsData, data })}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EventsTdContent>
|
||||
</EventsTd>,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@ -176,23 +178,25 @@ export const EventColumnView = React.memo<Props>(
|
|||
return grouped.contextMenu.length > 0
|
||||
? [
|
||||
...grouped.icon,
|
||||
<EventsTdContent
|
||||
key="actions-context-menu"
|
||||
textAlign="center"
|
||||
width={DEFAULT_ICON_BUTTON_WIDTH}
|
||||
>
|
||||
<EuiPopover
|
||||
id="singlePanel"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
repositionOnScroll
|
||||
<EventsTd>
|
||||
<EventsTdContent
|
||||
key="actions-context-menu"
|
||||
textAlign="center"
|
||||
width={DEFAULT_ICON_BUTTON_WIDTH}
|
||||
>
|
||||
<ContextMenuPanel items={grouped.contextMenu} />
|
||||
</EuiPopover>
|
||||
</EventsTdContent>,
|
||||
<EuiPopover
|
||||
id="singlePanel"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
repositionOnScroll
|
||||
>
|
||||
<ContextMenuPanel items={grouped.contextMenu} />
|
||||
</EuiPopover>
|
||||
</EventsTdContent>
|
||||
</EventsTd>,
|
||||
]
|
||||
: grouped.icon;
|
||||
}, [button, closePopover, id, onClickCb, data, ecsData, timelineActions, isPopoverOpen]);
|
||||
|
|
|
@ -16,7 +16,7 @@ exports[`GenericFileDetails rendering it renders the default GenericFileDetails
|
|||
processTitle="/lib/systemd/systemd-journald"
|
||||
result="success"
|
||||
secondary="root"
|
||||
session="unset"
|
||||
session="242"
|
||||
text="generic-text-123"
|
||||
userName="root"
|
||||
workingDirectory="/"
|
||||
|
@ -34,7 +34,7 @@ exports[`GenericFileDetails rendering it renders the default GenericFileDetails
|
|||
"success",
|
||||
],
|
||||
"session": Array [
|
||||
"unset",
|
||||
"242",
|
||||
],
|
||||
"summary": Object {
|
||||
"actor": Object {
|
||||
|
|
|
@ -32,7 +32,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
|
|||
"message_type": null,
|
||||
"object": Object {
|
||||
"primary": Array [
|
||||
"93.184.216.34",
|
||||
"192.168.216.34",
|
||||
],
|
||||
"secondary": Array [
|
||||
"80",
|
||||
|
@ -46,7 +46,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
|
|||
},
|
||||
"destination": Object {
|
||||
"ip": Array [
|
||||
"93.184.216.34",
|
||||
"192.168.216.34",
|
||||
],
|
||||
"port": Array [
|
||||
80,
|
||||
|
@ -113,7 +113,7 @@ exports[`GenericRowRenderer #createGenericAuditRowRenderer renders correctly aga
|
|||
"zeek": null,
|
||||
}
|
||||
}
|
||||
text="some text"
|
||||
text="connected using"
|
||||
timelineId="test"
|
||||
/>
|
||||
</RowRendererContainer>
|
||||
|
@ -135,7 +135,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
|
|||
"success",
|
||||
],
|
||||
"session": Array [
|
||||
"unset",
|
||||
"242",
|
||||
],
|
||||
"summary": Object {
|
||||
"actor": Object {
|
||||
|
@ -259,7 +259,7 @@ exports[`GenericRowRenderer #createGenericFileRowRenderer renders correctly agai
|
|||
}
|
||||
}
|
||||
fileIcon="document"
|
||||
text="some text"
|
||||
text="opened file using"
|
||||
timelineId="test"
|
||||
/>
|
||||
</RowRendererContainer>
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('GenericRowRenderer', () => {
|
|||
auditd = cloneDeep(mockTimelineData[26].ecs);
|
||||
connectedToRenderer = createGenericAuditRowRenderer({
|
||||
actionName: 'connected-to',
|
||||
text: 'some text',
|
||||
text: 'connected using',
|
||||
});
|
||||
});
|
||||
test('renders correctly against snapshot', () => {
|
||||
|
@ -80,7 +80,7 @@ describe('GenericRowRenderer', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.text()).toContain(
|
||||
'Session246alice@zeek-londonsome textwget(1490)wget www.example.comwith resultsuccessDestination93.184.216.34:80'
|
||||
'Session246alice@zeek-londonconnected usingwget(1490)wget www.example.comwith resultsuccessDestination192.168.216.34:80'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -95,7 +95,7 @@ describe('GenericRowRenderer', () => {
|
|||
auditdFile = cloneDeep(mockTimelineData[27].ecs);
|
||||
fileToRenderer = createGenericFileRowRenderer({
|
||||
actionName: 'opened-file',
|
||||
text: 'some text',
|
||||
text: 'opened file using',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -142,7 +142,7 @@ describe('GenericRowRenderer', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.text()).toContain(
|
||||
'Sessionunsetroot@zeek-londonin/some text/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess'
|
||||
'Session242root@zeek-londonin/opened file using/proc/15990/attr/currentusingsystemd-journal(27244)/lib/systemd/systemd-journaldwith resultsuccess'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,8 @@ import { IconType } from '@elastic/eui';
|
|||
import { get } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
import { RowRendererId } from '../../../../../../../common/types/timeline';
|
||||
|
||||
import { RowRenderer, RowRendererContainer } from '../row_renderer';
|
||||
import { AuditdGenericDetails } from './generic_details';
|
||||
import { AuditdGenericFileDetails } from './generic_file_details';
|
||||
|
@ -22,6 +24,7 @@ export const createGenericAuditRowRenderer = ({
|
|||
actionName: string;
|
||||
text: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.auditd,
|
||||
isInstance: (ecs) => {
|
||||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
|
@ -54,6 +57,7 @@ export const createGenericFileRowRenderer = ({
|
|||
text: string;
|
||||
fileIcon?: IconType;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.auditd_file,
|
||||
isInstance: (ecs) => {
|
||||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { get } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { RowRendererId } from '../../../../../../../common/types/timeline';
|
||||
import { asArrayIfExists } from '../../../../../../common/lib/helpers';
|
||||
import {
|
||||
TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME,
|
||||
|
@ -84,6 +85,7 @@ export const eventActionMatches = (eventAction: string | object | undefined | nu
|
|||
};
|
||||
|
||||
export const netflowRowRenderer: RowRenderer = {
|
||||
id: RowRendererId.netflow,
|
||||
isInstance: (ecs) =>
|
||||
eventCategoryMatches(get(EVENT_CATEGORY_FIELD, ecs)) ||
|
||||
eventActionMatches(get(EVENT_ACTION_FIELD, ecs)),
|
||||
|
|
|
@ -4,13 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { RowRendererId } from '../../../../../../common/types/timeline';
|
||||
|
||||
import { RowRenderer } from './row_renderer';
|
||||
|
||||
const PlainRowRenderer = () => <></>;
|
||||
|
||||
PlainRowRenderer.displayName = 'PlainRowRenderer';
|
||||
|
||||
export const plainRowRenderer: RowRenderer = {
|
||||
id: RowRendererId.plain,
|
||||
isInstance: (_) => true,
|
||||
renderRow: () => <></>,
|
||||
renderRow: PlainRowRenderer,
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { BrowserFields } from '../../../../../common/containers/source';
|
||||
import type { RowRendererId } from '../../../../../../common/types/timeline';
|
||||
import { Ecs } from '../../../../../graphql/types';
|
||||
import { EventsTrSupplement } from '../../styles';
|
||||
|
||||
|
@ -22,6 +23,7 @@ export const RowRendererContainer = React.memo<RowRendererContainerProps>(({ chi
|
|||
RowRendererContainer.displayName = 'RowRendererContainer';
|
||||
|
||||
export interface RowRenderer {
|
||||
id: RowRendererId;
|
||||
isInstance: (data: Ecs) => boolean;
|
||||
renderRow: ({
|
||||
browserFields,
|
||||
|
|
|
@ -34,7 +34,6 @@ exports[`SuricataSignature rendering it renders the default SuricataSignature 1`
|
|||
>
|
||||
Hello
|
||||
</GoogleLink>
|
||||
<ExternalLinkIcon />
|
||||
</div>
|
||||
</DefaultDraggable>
|
||||
</LinkFlexItem>
|
||||
|
|
|
@ -9,10 +9,13 @@
|
|||
import { get } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
import { RowRendererId } from '../../../../../../../common/types/timeline';
|
||||
|
||||
import { RowRenderer, RowRendererContainer } from '../row_renderer';
|
||||
import { SuricataDetails } from './suricata_details';
|
||||
|
||||
export const suricataRowRenderer: RowRenderer = {
|
||||
id: RowRendererId.suricata,
|
||||
isInstance: (ecs) => {
|
||||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
return module != null && module.toLowerCase() === 'suricata';
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
DraggableWrapper,
|
||||
} from '../../../../../../common/components/drag_and_drop/draggable_wrapper';
|
||||
import { escapeDataProviderId } from '../../../../../../common/components/drag_and_drop/helpers';
|
||||
import { ExternalLinkIcon } from '../../../../../../common/components/external_link_icon';
|
||||
import { GoogleLink } from '../../../../../../common/components/links';
|
||||
import { Provider } from '../../../data_providers/provider';
|
||||
|
||||
|
@ -122,7 +121,6 @@ export const SuricataSignature = React.memo<{
|
|||
<GoogleLink link={signature}>
|
||||
{signature.split(' ').splice(tokens.length).join(' ')}
|
||||
</GoogleLink>
|
||||
<ExternalLinkIcon />
|
||||
</div>
|
||||
</DefaultDraggable>
|
||||
</LinkFlexItem>
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
import { get } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
import { RowRendererId } from '../../../../../../../common/types/timeline';
|
||||
|
||||
import { DnsRequestEventDetails } from '../dns/dns_request_event_details';
|
||||
import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details';
|
||||
import { isFileEvent, isNillEmptyOrNotFinite } from '../helpers';
|
||||
|
@ -25,6 +27,7 @@ export const createGenericSystemRowRenderer = ({
|
|||
actionName: string;
|
||||
text: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.system,
|
||||
isInstance: (ecs) => {
|
||||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
|
@ -55,6 +58,7 @@ export const createEndgameProcessRowRenderer = ({
|
|||
actionName: string;
|
||||
text: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.system_file,
|
||||
isInstance: (ecs) => {
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
const category: string | null | undefined = get('event.category[0]', ecs);
|
||||
|
@ -86,6 +90,7 @@ export const createFimRowRenderer = ({
|
|||
actionName: string;
|
||||
text: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.system_fim,
|
||||
isInstance: (ecs) => {
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
const category: string | null | undefined = get('event.category[0]', ecs);
|
||||
|
@ -117,6 +122,7 @@ export const createGenericFileRowRenderer = ({
|
|||
actionName: string;
|
||||
text: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.system_file,
|
||||
isInstance: (ecs) => {
|
||||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
|
@ -147,6 +153,7 @@ export const createSocketRowRenderer = ({
|
|||
actionName: string;
|
||||
text: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.system_socket,
|
||||
isInstance: (ecs) => {
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
return action != null && action.toLowerCase() === actionName;
|
||||
|
@ -169,6 +176,7 @@ export const createSecurityEventRowRenderer = ({
|
|||
}: {
|
||||
actionName: string;
|
||||
}): RowRenderer => ({
|
||||
id: RowRendererId.system_security_event,
|
||||
isInstance: (ecs) => {
|
||||
const category: string | null | undefined = get('event.category[0]', ecs);
|
||||
const action: string | null | undefined = get('event.action[0]', ecs);
|
||||
|
@ -192,6 +200,7 @@ export const createSecurityEventRowRenderer = ({
|
|||
});
|
||||
|
||||
export const createDnsRowRenderer = (): RowRenderer => ({
|
||||
id: RowRendererId.system_dns,
|
||||
isInstance: (ecs) => {
|
||||
const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', ecs);
|
||||
const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', ecs);
|
||||
|
|
|
@ -9,10 +9,13 @@
|
|||
import { get } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
import { RowRendererId } from '../../../../../../../common/types/timeline';
|
||||
|
||||
import { RowRenderer, RowRendererContainer } from '../row_renderer';
|
||||
import { ZeekDetails } from './zeek_details';
|
||||
|
||||
export const zeekRowRenderer: RowRenderer = {
|
||||
id: RowRendererId.zeek,
|
||||
isInstance: (ecs) => {
|
||||
const module: string | null | undefined = get('event.module[0]', ecs);
|
||||
return module != null && module.toLowerCase() === 'zeek';
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
DraggableWrapper,
|
||||
} from '../../../../../../common/components/drag_and_drop/draggable_wrapper';
|
||||
import { escapeDataProviderId } from '../../../../../../common/components/drag_and_drop/helpers';
|
||||
import { ExternalLinkIcon } from '../../../../../../common/components/external_link_icon';
|
||||
import { GoogleLink, ReputationLink } from '../../../../../../common/components/links';
|
||||
import { Provider } from '../../../data_providers/provider';
|
||||
import { IS_OPERATOR } from '../../../data_providers/data_provider';
|
||||
|
@ -120,7 +119,6 @@ export const Link = React.memo<LinkProps>(({ value, link }) => {
|
|||
<LinkFlexItem grow={false}>
|
||||
<div>
|
||||
<GoogleLink link={link}>{value}</GoogleLink>
|
||||
<ExternalLinkIcon />
|
||||
</div>
|
||||
</LinkFlexItem>
|
||||
);
|
||||
|
@ -129,7 +127,6 @@ export const Link = React.memo<LinkProps>(({ value, link }) => {
|
|||
<LinkFlexItem grow={false}>
|
||||
<div>
|
||||
<GoogleLink link={value} />
|
||||
<ExternalLinkIcon />
|
||||
</div>
|
||||
</LinkFlexItem>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
|
|||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import { RowRendererId, TimelineId } from '../../../../../common/types/timeline';
|
||||
import { BrowserFields } from '../../../../common/containers/source';
|
||||
import { TimelineItem } from '../../../../graphql/types';
|
||||
import { Note } from '../../../../common/lib/note';
|
||||
|
@ -60,6 +60,7 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
columnHeaders,
|
||||
data,
|
||||
eventIdToNoteIds,
|
||||
excludedRowRendererIds,
|
||||
height,
|
||||
id,
|
||||
isEventViewer = false,
|
||||
|
@ -74,7 +75,6 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
clearSelected,
|
||||
show,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
graphEventId,
|
||||
sort,
|
||||
toggleColumn,
|
||||
|
@ -97,8 +97,7 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
const onAddNoteToEvent: AddNoteToEvent = useCallback(
|
||||
({ eventId, noteId }: { eventId: string; noteId: string }) =>
|
||||
addNoteToEvent!({ id, eventId, noteId }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[id]
|
||||
[id, addNoteToEvent]
|
||||
);
|
||||
|
||||
const onRowSelected: OnRowSelected = useCallback(
|
||||
|
@ -135,35 +134,36 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
(sorted) => {
|
||||
updateSort!({ id, sort: sorted });
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[id]
|
||||
[id, updateSort]
|
||||
);
|
||||
|
||||
const onColumnRemoved: OnColumnRemoved = useCallback(
|
||||
(columnId) => removeColumn!({ id, columnId }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[id]
|
||||
[id, removeColumn]
|
||||
);
|
||||
|
||||
const onColumnResized: OnColumnResized = useCallback(
|
||||
({ columnId, delta }) => applyDeltaToColumnWidth!({ id, columnId, delta }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[id]
|
||||
[applyDeltaToColumnWidth, id]
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onPinEvent: OnPinEvent = useCallback((eventId) => pinEvent!({ id, eventId }), [id]);
|
||||
const onPinEvent: OnPinEvent = useCallback((eventId) => pinEvent!({ id, eventId }), [
|
||||
id,
|
||||
pinEvent,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onUnPinEvent: OnUnPinEvent = useCallback((eventId) => unPinEvent!({ id, eventId }), [id]);
|
||||
const onUnPinEvent: OnUnPinEvent = useCallback((eventId) => unPinEvent!({ id, eventId }), [
|
||||
id,
|
||||
unPinEvent,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const onUpdateNote: UpdateNote = useCallback((note: Note) => updateNote!({ note }), []);
|
||||
const onUpdateNote: UpdateNote = useCallback((note: Note) => updateNote!({ note }), [
|
||||
updateNote,
|
||||
]);
|
||||
|
||||
const onUpdateColumns: OnUpdateColumns = useCallback(
|
||||
(columns) => updateColumns!({ id, columns }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[id]
|
||||
[id, updateColumns]
|
||||
);
|
||||
|
||||
// Sync to selectAll so parent components can select all events
|
||||
|
@ -171,8 +171,19 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
if (selectAll) {
|
||||
onSelectAll({ isSelected: true });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectAll]); // onSelectAll dependency not necessary
|
||||
}, [onSelectAll, selectAll]);
|
||||
|
||||
const enabledRowRenderers = useMemo(() => {
|
||||
if (
|
||||
excludedRowRendererIds &&
|
||||
excludedRowRendererIds.length === Object.keys(RowRendererId).length
|
||||
)
|
||||
return [plainRowRenderer];
|
||||
|
||||
if (!excludedRowRendererIds) return rowRenderers;
|
||||
|
||||
return rowRenderers.filter((rowRenderer) => !excludedRowRendererIds.includes(rowRenderer.id));
|
||||
}, [excludedRowRendererIds]);
|
||||
|
||||
return (
|
||||
<Body
|
||||
|
@ -199,7 +210,7 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
onUnPinEvent={onUnPinEvent}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
pinnedEventIds={pinnedEventIds}
|
||||
rowRenderers={showRowRenderers ? rowRenderers : [plainRowRenderer]}
|
||||
rowRenderers={enabledRowRenderers}
|
||||
selectedEventIds={selectedEventIds}
|
||||
show={id === TimelineId.active ? show : true}
|
||||
showCheckboxes={showCheckboxes}
|
||||
|
@ -213,6 +224,7 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
deepEqual(prevProps.browserFields, nextProps.browserFields) &&
|
||||
deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) &&
|
||||
deepEqual(prevProps.data, nextProps.data) &&
|
||||
deepEqual(prevProps.excludedRowRendererIds, nextProps.excludedRowRendererIds) &&
|
||||
prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds &&
|
||||
prevProps.graphEventId === nextProps.graphEventId &&
|
||||
deepEqual(prevProps.notesById, nextProps.notesById) &&
|
||||
|
@ -225,7 +237,6 @@ const StatefulBodyComponent = React.memo<StatefulBodyComponentProps>(
|
|||
prevProps.show === nextProps.show &&
|
||||
prevProps.selectedEventIds === nextProps.selectedEventIds &&
|
||||
prevProps.showCheckboxes === nextProps.showCheckboxes &&
|
||||
prevProps.showRowRenderers === nextProps.showRowRenderers &&
|
||||
prevProps.sort === nextProps.sort
|
||||
);
|
||||
|
||||
|
@ -245,6 +256,7 @@ const makeMapStateToProps = () => {
|
|||
columns,
|
||||
eventIdToNoteIds,
|
||||
eventType,
|
||||
excludedRowRendererIds,
|
||||
graphEventId,
|
||||
isSelectAllChecked,
|
||||
loadingEventIds,
|
||||
|
@ -252,13 +264,13 @@ const makeMapStateToProps = () => {
|
|||
selectedEventIds,
|
||||
show,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
} = timeline;
|
||||
|
||||
return {
|
||||
columnHeaders: memoizedColumnHeaders(columns, browserFields),
|
||||
eventIdToNoteIds,
|
||||
eventType,
|
||||
excludedRowRendererIds,
|
||||
graphEventId,
|
||||
isSelectAllChecked,
|
||||
loadingEventIds,
|
||||
|
@ -268,7 +280,6 @@ const makeMapStateToProps = () => {
|
|||
selectedEventIds,
|
||||
show,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
};
|
||||
};
|
||||
return mapStateToProps;
|
||||
|
|
|
@ -97,7 +97,7 @@ export const ProviderItemBadge = React.memo<ProviderItemBadgeProps>(
|
|||
|
||||
useEffect(() => {
|
||||
// optionally register the provider if provided
|
||||
if (!providerRegistered && register != null) {
|
||||
if (register != null) {
|
||||
dispatch(dragAndDropActions.registerProvider({ provider: { ...register, and: [] } }));
|
||||
setProviderRegistered(true);
|
||||
}
|
||||
|
|
|
@ -91,10 +91,14 @@ export const EventsTrHeader = styled.div.attrs(({ className }) => ({
|
|||
|
||||
export const EventsThGroupActions = styled.div.attrs(({ className = '' }) => ({
|
||||
className: `siemEventsTable__thGroupActions ${className}`,
|
||||
}))<{ actionsColumnWidth: number }>`
|
||||
}))<{ actionsColumnWidth: number; isEventViewer: boolean }>`
|
||||
display: flex;
|
||||
flex: 0 0 ${({ actionsColumnWidth }) => `${actionsColumnWidth}px`};
|
||||
flex: 0 0
|
||||
${({ actionsColumnWidth, isEventViewer }) =>
|
||||
`${!isEventViewer ? actionsColumnWidth + 4 : actionsColumnWidth}px`};
|
||||
min-width: 0;
|
||||
padding-left: ${({ isEventViewer }) =>
|
||||
!isEventViewer ? '4px;' : '0;'}; // match timeline event border
|
||||
`;
|
||||
|
||||
export const EventsThGroupData = styled.div.attrs(({ className = '' }) => ({
|
||||
|
@ -151,6 +155,11 @@ export const EventsThContent = styled.div.attrs(({ className = '' }) => ({
|
|||
width != null
|
||||
? `${width}px`
|
||||
: '100%'}; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */
|
||||
|
||||
> button.euiButtonIcon,
|
||||
> .euiToolTipAnchor > button.euiButtonIcon {
|
||||
margin-left: ${({ theme }) => `-${theme.eui.paddingSizes.xs}`};
|
||||
}
|
||||
`;
|
||||
|
||||
/* EVENTS BODY */
|
||||
|
@ -198,8 +207,7 @@ export const EventsTrSupplement = styled.div.attrs(({ className = '' }) => ({
|
|||
}))<{ className: string }>`
|
||||
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
|
||||
line-height: ${({ theme }) => theme.eui.euiLineHeight};
|
||||
padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0
|
||||
${({ theme }) => theme.eui.paddingSizes.xl};
|
||||
padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0 52px;
|
||||
`;
|
||||
|
||||
export const EventsTdGroupActions = styled.div.attrs(({ className = '' }) => ({
|
||||
|
@ -249,6 +257,11 @@ export const EventsTdContent = styled.div.attrs(({ className }) => ({
|
|||
width != null
|
||||
? `${width}px`
|
||||
: '100%'}; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */
|
||||
|
||||
> button.euiButtonIcon,
|
||||
> .euiToolTipAnchor > button.euiButtonIcon {
|
||||
margin-left: ${({ theme }) => `-${theme.eui.paddingSizes.xs}`};
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
|
@ -334,6 +347,5 @@ export const EventsHeadingHandle = styled.div.attrs(({ className = '' }) => ({
|
|||
*/
|
||||
|
||||
export const EventsLoading = styled(EuiLoadingSpinner)`
|
||||
margin: ${({ theme }) => theme.eui.euiSizeXS};
|
||||
vertical-align: top;
|
||||
vertical-align: middle;
|
||||
`;
|
||||
|
|
|
@ -51,6 +51,7 @@ export const allTimelinesQuery = gql`
|
|||
updatedBy
|
||||
version
|
||||
}
|
||||
excludedRowRendererIds
|
||||
notes {
|
||||
eventId
|
||||
note
|
||||
|
|
|
@ -75,6 +75,7 @@ export const getAllTimeline = memoizeOne(
|
|||
return acc;
|
||||
}, {})
|
||||
: null,
|
||||
excludedRowRendererIds: timeline.excludedRowRendererIds,
|
||||
favorite: timeline.favorite,
|
||||
noteIds: timeline.noteIds,
|
||||
notes:
|
||||
|
|
|
@ -69,6 +69,7 @@ export const oneTimelineQuery = gql`
|
|||
updatedBy
|
||||
version
|
||||
}
|
||||
excludedRowRendererIds
|
||||
favorite {
|
||||
fullName
|
||||
userName
|
||||
|
|
|
@ -17,7 +17,7 @@ import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/t
|
|||
|
||||
import { EventType, KqlMode, TimelineModel, ColumnHeaderOptions } from './model';
|
||||
import { TimelineNonEcsData } from '../../../graphql/types';
|
||||
import { TimelineTypeLiteral } from '../../../../common/types/timeline';
|
||||
import { TimelineTypeLiteral, RowRendererId } from '../../../../common/types/timeline';
|
||||
import { InsertTimeline } from './types';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline');
|
||||
|
@ -59,6 +59,7 @@ export const createTimeline = actionCreator<{
|
|||
start: number;
|
||||
end: number;
|
||||
};
|
||||
excludedRowRendererIds?: RowRendererId[];
|
||||
filters?: Filter[];
|
||||
columns: ColumnHeaderOptions[];
|
||||
itemsPerPage?: number;
|
||||
|
@ -69,7 +70,6 @@ export const createTimeline = actionCreator<{
|
|||
show?: boolean;
|
||||
sort?: Sort;
|
||||
showCheckboxes?: boolean;
|
||||
showRowRenderers?: boolean;
|
||||
timelineType?: TimelineTypeLiteral;
|
||||
templateTimelineId?: string;
|
||||
templateTimelineVersion?: number;
|
||||
|
@ -266,3 +266,8 @@ export const clearEventsDeleted = actionCreator<{
|
|||
export const updateEventType = actionCreator<{ id: string; eventType: EventType }>(
|
||||
'UPDATE_EVENT_TYPE'
|
||||
);
|
||||
|
||||
export const setExcludedRowRendererIds = actionCreator<{
|
||||
id: string;
|
||||
excludedRowRendererIds: RowRendererId[];
|
||||
}>('SET_TIMELINE_EXCLUDED_ROW_RENDERER_IDS');
|
||||
|
|
|
@ -18,6 +18,7 @@ export const timelineDefaults: SubsetTimelineModel & Pick<TimelineModel, 'filter
|
|||
description: '',
|
||||
eventType: 'all',
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
filters: [],
|
||||
|
@ -49,7 +50,6 @@ export const timelineDefaults: SubsetTimelineModel & Pick<TimelineModel, 'filter
|
|||
selectedEventIds: {},
|
||||
show: false,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: Direction.desc,
|
||||
|
|
|
@ -89,6 +89,7 @@ describe('Epic Timeline', () => {
|
|||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
filters: [
|
||||
|
@ -146,7 +147,6 @@ describe('Epic Timeline', () => {
|
|||
selectedEventIds: {},
|
||||
show: true,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: { columnId: '@timestamp', sortDirection: Direction.desc },
|
||||
status: TimelineStatus.active,
|
||||
width: 1100,
|
||||
|
@ -233,6 +233,7 @@ describe('Epic Timeline', () => {
|
|||
},
|
||||
description: '',
|
||||
eventType: 'all',
|
||||
excludedRowRendererIds: [],
|
||||
filters: [
|
||||
{
|
||||
exists: null,
|
||||
|
|
|
@ -331,6 +331,7 @@ const timelineInput: TimelineInput = {
|
|||
dataProviders: null,
|
||||
description: null,
|
||||
eventType: null,
|
||||
excludedRowRendererIds: null,
|
||||
filters: null,
|
||||
kqlMode: null,
|
||||
kqlQuery: null,
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
removeColumn,
|
||||
upsertColumn,
|
||||
applyDeltaToColumnWidth,
|
||||
setExcludedRowRendererIds,
|
||||
updateColumns,
|
||||
updateItemsPerPage,
|
||||
updateSort,
|
||||
|
@ -30,6 +31,7 @@ const timelineActionTypes = [
|
|||
updateColumns.type,
|
||||
updateItemsPerPage.type,
|
||||
updateSort.type,
|
||||
setExcludedRowRendererIds.type,
|
||||
];
|
||||
|
||||
export const isPageTimeline = (timelineId: string | undefined): boolean =>
|
||||
|
|
|
@ -21,7 +21,11 @@ import {
|
|||
} from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/model';
|
||||
import { TimelineNonEcsData } from '../../../graphql/types';
|
||||
import { TimelineTypeLiteral, TimelineType } from '../../../../common/types/timeline';
|
||||
import {
|
||||
TimelineTypeLiteral,
|
||||
TimelineType,
|
||||
RowRendererId,
|
||||
} from '../../../../common/types/timeline';
|
||||
|
||||
import { timelineDefaults } from './defaults';
|
||||
import { ColumnHeaderOptions, KqlMode, TimelineModel, EventType } from './model';
|
||||
|
@ -130,6 +134,7 @@ interface AddNewTimelineParams {
|
|||
start: number;
|
||||
end: number;
|
||||
};
|
||||
excludedRowRendererIds?: RowRendererId[];
|
||||
filters?: Filter[];
|
||||
id: string;
|
||||
itemsPerPage?: number;
|
||||
|
@ -140,7 +145,6 @@ interface AddNewTimelineParams {
|
|||
show?: boolean;
|
||||
sort?: Sort;
|
||||
showCheckboxes?: boolean;
|
||||
showRowRenderers?: boolean;
|
||||
timelineById: TimelineById;
|
||||
timelineType: TimelineTypeLiteral;
|
||||
}
|
||||
|
@ -150,6 +154,7 @@ export const addNewTimeline = ({
|
|||
columns,
|
||||
dataProviders = [],
|
||||
dateRange = { start: 0, end: 0 },
|
||||
excludedRowRendererIds = [],
|
||||
filters = timelineDefaults.filters,
|
||||
id,
|
||||
itemsPerPage = timelineDefaults.itemsPerPage,
|
||||
|
@ -157,7 +162,6 @@ export const addNewTimeline = ({
|
|||
sort = timelineDefaults.sort,
|
||||
show = false,
|
||||
showCheckboxes = false,
|
||||
showRowRenderers = true,
|
||||
timelineById,
|
||||
timelineType,
|
||||
}: AddNewTimelineParams): TimelineById => {
|
||||
|
@ -176,6 +180,7 @@ export const addNewTimeline = ({
|
|||
columns,
|
||||
dataProviders,
|
||||
dateRange,
|
||||
excludedRowRendererIds,
|
||||
filters,
|
||||
itemsPerPage,
|
||||
kqlQuery,
|
||||
|
@ -186,7 +191,6 @@ export const addNewTimeline = ({
|
|||
isSaving: false,
|
||||
isLoading: false,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
timelineType,
|
||||
...templateTimelineInfo,
|
||||
},
|
||||
|
@ -1436,3 +1440,25 @@ export const updateFilters = ({ id, filters, timelineById }: UpdateFiltersParams
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateExcludedRowRenderersIds {
|
||||
id: string;
|
||||
excludedRowRendererIds: RowRendererId[];
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
export const updateExcludedRowRenderersIds = ({
|
||||
id,
|
||||
excludedRowRendererIds,
|
||||
timelineById,
|
||||
}: UpdateExcludedRowRenderersIds): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
excludedRowRendererIds,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
TimelineStatus,
|
||||
} from '../../../graphql/types';
|
||||
import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/types';
|
||||
import type { RowRendererId } from '../../../../common/types/timeline';
|
||||
|
||||
export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages
|
||||
export type KqlMode = 'filter' | 'search';
|
||||
|
@ -54,6 +55,8 @@ export interface TimelineModel {
|
|||
eventType?: EventType;
|
||||
/** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */
|
||||
eventIdToNoteIds: Record<string, string[]>;
|
||||
/** A list of Ids of excluded Row Renderers */
|
||||
excludedRowRendererIds: RowRendererId[];
|
||||
filters?: Filter[];
|
||||
/** When non-empty, display a graph view for this event */
|
||||
graphEventId?: string;
|
||||
|
@ -108,8 +111,6 @@ export interface TimelineModel {
|
|||
show: boolean;
|
||||
/** When true, shows checkboxes enabling selection. Selected events store in selectedEventIds **/
|
||||
showCheckboxes: boolean;
|
||||
/** When true, shows additional rowRenderers below the PlainRowRenderer **/
|
||||
showRowRenderers: boolean;
|
||||
/** Specifies which column the timeline is sorted on, and the direction (ascending / descending) */
|
||||
sort: Sort;
|
||||
/** status: active | draft */
|
||||
|
@ -131,6 +132,7 @@ export type SubsetTimelineModel = Readonly<
|
|||
| 'description'
|
||||
| 'eventType'
|
||||
| 'eventIdToNoteIds'
|
||||
| 'excludedRowRendererIds'
|
||||
| 'graphEventId'
|
||||
| 'highlightedDropAndProviderId'
|
||||
| 'historyIds'
|
||||
|
@ -153,7 +155,6 @@ export type SubsetTimelineModel = Readonly<
|
|||
| 'selectedEventIds'
|
||||
| 'show'
|
||||
| 'showCheckboxes'
|
||||
| 'showRowRenderers'
|
||||
| 'sort'
|
||||
| 'width'
|
||||
| 'isSaving'
|
||||
|
|
|
@ -70,6 +70,7 @@ const timelineByIdMock: TimelineById = {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
id: 'foo',
|
||||
|
@ -97,7 +98,6 @@ const timelineByIdMock: TimelineById = {
|
|||
selectedEventIds: {},
|
||||
show: true,
|
||||
showCheckboxes: false,
|
||||
showRowRenderers: true,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: Direction.desc,
|
||||
|
@ -1119,6 +1119,7 @@ describe('Timeline', () => {
|
|||
deletedEventIds: [],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1139,7 +1140,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1215,6 +1215,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1235,7 +1236,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1421,6 +1421,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1441,7 +1442,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1517,6 +1517,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1537,7 +1538,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1619,6 +1619,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1639,7 +1640,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1722,6 +1722,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1742,7 +1743,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1917,6 +1917,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -1937,7 +1938,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
@ -1995,6 +1995,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
|
@ -2003,7 +2004,6 @@ describe('Timeline', () => {
|
|||
isLoading: false,
|
||||
id: 'foo',
|
||||
savedObjectId: null,
|
||||
showRowRenderers: true,
|
||||
kqlMode: 'filter',
|
||||
kqlQuery: { filterQuery: null, filterQueryDraft: null },
|
||||
loadingEventIds: [],
|
||||
|
@ -2099,6 +2099,7 @@ describe('Timeline', () => {
|
|||
description: '',
|
||||
deletedEventIds: [],
|
||||
eventIdToNoteIds: {},
|
||||
excludedRowRendererIds: [],
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
id: 'foo',
|
||||
|
@ -2121,7 +2122,6 @@ describe('Timeline', () => {
|
|||
},
|
||||
selectedEventIds: {},
|
||||
show: true,
|
||||
showRowRenderers: true,
|
||||
showCheckboxes: false,
|
||||
sort: {
|
||||
columnId: '@timestamp',
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
removeProvider,
|
||||
setEventsDeleted,
|
||||
setEventsLoading,
|
||||
setExcludedRowRendererIds,
|
||||
setFilters,
|
||||
setInsertTimeline,
|
||||
setKqlFilterQueryDraft,
|
||||
|
@ -75,6 +76,7 @@ import {
|
|||
setLoadingTimelineEvents,
|
||||
setSelectedTimelineEvents,
|
||||
unPinTimelineEvent,
|
||||
updateExcludedRowRenderersIds,
|
||||
updateHighlightedDropAndProvider,
|
||||
updateKqlFilterQueryDraft,
|
||||
updateTimelineColumns,
|
||||
|
@ -129,13 +131,13 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
id,
|
||||
dataProviders,
|
||||
dateRange,
|
||||
excludedRowRendererIds,
|
||||
show,
|
||||
columns,
|
||||
itemsPerPage,
|
||||
kqlQuery,
|
||||
sort,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
timelineType = TimelineType.default,
|
||||
filters,
|
||||
}
|
||||
|
@ -146,6 +148,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
columns,
|
||||
dataProviders,
|
||||
dateRange,
|
||||
excludedRowRendererIds,
|
||||
filters,
|
||||
id,
|
||||
itemsPerPage,
|
||||
|
@ -153,7 +156,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
sort,
|
||||
show,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
timelineById: state.timelineById,
|
||||
timelineType,
|
||||
}),
|
||||
|
@ -306,6 +308,14 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
},
|
||||
},
|
||||
}))
|
||||
.case(setExcludedRowRendererIds, (state, { id, excludedRowRendererIds }) => ({
|
||||
...state,
|
||||
timelineById: updateExcludedRowRenderersIds({
|
||||
id,
|
||||
excludedRowRendererIds,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(setSelected, (state, { id, eventIds, isSelected, isSelectAllChecked }) => ({
|
||||
...state,
|
||||
timelineById: setSelectedTimelineEvents({
|
||||
|
|
|
@ -147,11 +147,28 @@ export const timelineSchema = gql`
|
|||
custom
|
||||
}
|
||||
|
||||
enum RowRendererId {
|
||||
auditd
|
||||
auditd_file
|
||||
netflow
|
||||
plain
|
||||
suricata
|
||||
system
|
||||
system_dns
|
||||
system_endgame_process
|
||||
system_file
|
||||
system_fim
|
||||
system_security_event
|
||||
system_socket
|
||||
zeek
|
||||
}
|
||||
|
||||
input TimelineInput {
|
||||
columns: [ColumnHeaderInput!]
|
||||
dataProviders: [DataProviderInput!]
|
||||
description: String
|
||||
eventType: String
|
||||
excludedRowRendererIds: [RowRendererId!]
|
||||
filters: [FilterTimelineInput!]
|
||||
kqlMode: String
|
||||
kqlQuery: SerializedFilterQueryInput
|
||||
|
@ -252,6 +269,7 @@ export const timelineSchema = gql`
|
|||
description: String
|
||||
eventIdToNoteIds: [NoteResult!]
|
||||
eventType: String
|
||||
excludedRowRendererIds: [RowRendererId!]
|
||||
favorite: [FavoriteTimelineResult!]
|
||||
filters: [FilterTimelineResult!]
|
||||
kqlMode: String
|
||||
|
|
|
@ -126,6 +126,8 @@ export interface TimelineInput {
|
|||
|
||||
eventType?: Maybe<string>;
|
||||
|
||||
excludedRowRendererIds?: Maybe<RowRendererId[]>;
|
||||
|
||||
filters?: Maybe<FilterTimelineInput[]>;
|
||||
|
||||
kqlMode?: Maybe<string>;
|
||||
|
@ -351,6 +353,22 @@ export enum DataProviderType {
|
|||
template = 'template',
|
||||
}
|
||||
|
||||
export enum RowRendererId {
|
||||
auditd = 'auditd',
|
||||
auditd_file = 'auditd_file',
|
||||
netflow = 'netflow',
|
||||
plain = 'plain',
|
||||
suricata = 'suricata',
|
||||
system = 'system',
|
||||
system_dns = 'system_dns',
|
||||
system_endgame_process = 'system_endgame_process',
|
||||
system_file = 'system_file',
|
||||
system_fim = 'system_fim',
|
||||
system_security_event = 'system_security_event',
|
||||
system_socket = 'system_socket',
|
||||
zeek = 'zeek',
|
||||
}
|
||||
|
||||
export enum TimelineStatus {
|
||||
active = 'active',
|
||||
draft = 'draft',
|
||||
|
@ -1963,6 +1981,8 @@ export interface TimelineResult {
|
|||
|
||||
eventType?: Maybe<string>;
|
||||
|
||||
excludedRowRendererIds?: Maybe<RowRendererId[]>;
|
||||
|
||||
favorite?: Maybe<FavoriteTimelineResult[]>;
|
||||
|
||||
filters?: Maybe<FilterTimelineResult[]>;
|
||||
|
@ -8101,6 +8121,12 @@ export namespace TimelineResultResolvers {
|
|||
|
||||
eventType?: EventTypeResolver<Maybe<string>, TypeParent, TContext>;
|
||||
|
||||
excludedRowRendererIds?: ExcludedRowRendererIdsResolver<
|
||||
Maybe<RowRendererId[]>,
|
||||
TypeParent,
|
||||
TContext
|
||||
>;
|
||||
|
||||
favorite?: FavoriteResolver<Maybe<FavoriteTimelineResult[]>, TypeParent, TContext>;
|
||||
|
||||
filters?: FiltersResolver<Maybe<FilterTimelineResult[]>, TypeParent, TContext>;
|
||||
|
@ -8184,6 +8210,11 @@ export namespace TimelineResultResolvers {
|
|||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type ExcludedRowRendererIdsResolver<
|
||||
R = Maybe<RowRendererId[]>,
|
||||
Parent = TimelineResult,
|
||||
TContext = SiemContext
|
||||
> = Resolver<R, Parent, TContext>;
|
||||
export type FavoriteResolver<
|
||||
R = Maybe<FavoriteTimelineResult[]>,
|
||||
Parent = TimelineResult,
|
||||
|
|
|
@ -44,5 +44,7 @@ export const pickSavedTimeline = (
|
|||
savedTimeline.status = TimelineStatus.active;
|
||||
}
|
||||
|
||||
savedTimeline.excludedRowRendererIds = savedTimeline.excludedRowRendererIds ?? [];
|
||||
|
||||
return savedTimeline;
|
||||
};
|
||||
|
|
|
@ -181,7 +181,7 @@ const getTimelinesFromObjects = async (
|
|||
if (myTimeline != null) {
|
||||
const timelineNotes = myNotes.filter((n) => n.timelineId === timelineId);
|
||||
const timelinePinnedEventIds = myPinnedEventIds.filter((p) => p.timelineId === timelineId);
|
||||
const exportedTimeline = omit('status', myTimeline);
|
||||
const exportedTimeline = omit(['status', 'excludedRowRendererIds'], myTimeline);
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
|
|
|
@ -135,6 +135,9 @@ export const timelineSavedObjectMappings: SavedObjectsType['mappings'] = {
|
|||
eventType: {
|
||||
type: 'keyword',
|
||||
},
|
||||
excludedRowRendererIds: {
|
||||
type: 'text',
|
||||
},
|
||||
favorite: {
|
||||
properties: {
|
||||
keySearch: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue