Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
export const ENDPOINT_ACTIONS_DS = '.logs-endpoint.actions';
export const ENDPOINT_ACTIONS_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
export const ENDPOINT_ACTION_RESPONSES_DS = '.logs-endpoint.action.responses';
export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTION_RESPONSES_DS}-default`;

export const eventsIndexPattern = 'logs-endpoint.events.*';
export const alertsIndexPattern = 'logs-endpoint.alerts-*';
Expand Down Expand Up @@ -60,3 +60,5 @@ export const UNISOLATE_HOST_ROUTE = `${BASE_ENDPOINT_ROUTE}/unisolate`;
/** Endpoint Actions Log Routes */
export const ENDPOINT_ACTION_LOG_ROUTE = `/api/endpoint/action_log/{agent_id}`;
export const ACTION_STATUS_ROUTE = `/api/endpoint/action_status`;

export const failedFleetActionErrorCode = '424';
33 changes: 30 additions & 3 deletions x-pack/plugins/security_solution/common/endpoint/types/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { ActionStatusRequestSchema, HostIsolationRequestSchema } from '../schema

export type ISOLATION_ACTIONS = 'isolate' | 'unisolate';

export const ActivityLogItemTypes = {
ACTION: 'action' as const,
RESPONSE: 'response' as const,
FLEET_ACTION: 'fleetAction' as const,
FLEET_RESPONSE: 'fleetResponse' as const,
};

interface EcsError {
code?: string;
id?: string;
Expand Down Expand Up @@ -87,8 +94,24 @@ export interface EndpointActionResponse {
action_data: EndpointActionData;
}

export interface EndpointActivityLogAction {
type: typeof ActivityLogItemTypes.ACTION;
item: {
id: string;
data: LogsEndpointAction;
};
}

export interface EndpointActivityLogActionResponse {
type: typeof ActivityLogItemTypes.RESPONSE;
item: {
id: string;
data: LogsEndpointActionResponse;
};
}

export interface ActivityLogAction {
type: 'action';
type: typeof ActivityLogItemTypes.FLEET_ACTION;
item: {
// document _id
id: string;
Expand All @@ -97,15 +120,19 @@ export interface ActivityLogAction {
};
}
export interface ActivityLogActionResponse {
type: 'response';
type: typeof ActivityLogItemTypes.FLEET_RESPONSE;
item: {
// document id
id: string;
// document _source
data: EndpointActionResponse;
};
}
export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
export type ActivityLogEntry =
| ActivityLogAction
| ActivityLogActionResponse
| EndpointActivityLogAction
| EndpointActivityLogActionResponse;
export interface ActivityLog {
page: number;
pageSize: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ interface Range {

const DatePickerWrapper = styled.div`
width: ${(props) => props.theme.eui.fractions.single.percentage};
max-width: 350px;
`;
const StickyFlexItem = styled(EuiFlexItem)`
background: ${(props) => `${props.theme.eui.euiHeaderBackgroundColor}`};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,54 @@ import React, { memo, useMemo } from 'react';
import styled from 'styled-components';

import { EuiComment, EuiText, EuiAvatarProps, EuiCommentProps, IconType } from '@elastic/eui';
import { Immutable, ActivityLogEntry } from '../../../../../../../common/endpoint/types';
import {
Immutable,
ActivityLogEntry,
ActivityLogItemTypes,
} from '../../../../../../../common/endpoint/types';
import { FormattedRelativePreferenceDate } from '../../../../../../common/components/formatted_date';
import { LogEntryTimelineIcon } from './log_entry_timeline_icon';
import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';

import * as i18 from '../../translations';

const useLogEntryUIProps = (
logEntry: Immutable<ActivityLogEntry>
logEntry: Immutable<ActivityLogEntry>,
theme: ReturnType<typeof useEuiTheme>
): {
actionEventTitle: string;
avatarColor: EuiAvatarProps['color'];
avatarIconColor: EuiAvatarProps['iconColor'];
avatarSize: EuiAvatarProps['size'];
commentText: string;
commentType: EuiCommentProps['type'];
displayComment: boolean;
displayResponseEvent: boolean;
failedActionEventTitle: string;
iconType: IconType;
isResponseEvent: boolean;
isSuccessful: boolean;
isCompleted: boolean;
responseEventTitle: string;
username: string | React.ReactNode;
} => {
return useMemo(() => {
let iconType: IconType = 'dot';
let commentType: EuiCommentProps['type'] = 'update';
let commentText: string = '';
let avatarColor: EuiAvatarProps['color'] = theme.euiColorLightestShade;
let avatarIconColor: EuiAvatarProps['iconColor'];
let avatarSize: EuiAvatarProps['size'] = 's';
let failedActionEventTitle: string = '';
let isIsolateAction: boolean = false;
let isResponseEvent: boolean = false;
let isSuccessful: boolean = false;
let isCompleted: boolean = false;
let displayComment: boolean = false;
let displayResponseEvent: boolean = true;
let username: EuiCommentProps['username'] = '';

if (logEntry.type === 'action') {
if (logEntry.type === ActivityLogItemTypes.FLEET_ACTION) {
avatarSize = 'm';
commentType = 'regular';
commentText = logEntry.item.data.data.comment?.trim() ?? '';
Expand All @@ -59,13 +73,51 @@ const useLogEntryUIProps = (
displayComment = true;
}
}
} else if (logEntry.type === 'response') {
}
if (logEntry.type === ActivityLogItemTypes.ACTION) {
avatarSize = 'm';
commentType = 'regular';
commentText = logEntry.item.data.EndpointActions.data.comment?.trim() ?? '';
displayResponseEvent = false;
iconType = 'lockOpen';
username = logEntry.item.data.user.id;
avatarIconColor = theme.euiColorVis9_behindText;
failedActionEventTitle = i18.ACTIVITY_LOG.LogEntry.action.failedEndpointReleaseAction;
if (logEntry.item.data.EndpointActions.data) {
const data = logEntry.item.data.EndpointActions.data;
if (data.command === 'isolate') {
iconType = 'lock';
failedActionEventTitle = i18.ACTIVITY_LOG.LogEntry.action.failedEndpointIsolateAction;
}
if (commentText) {
displayComment = true;
}
}
} else if (logEntry.type === ActivityLogItemTypes.FLEET_RESPONSE) {
isResponseEvent = true;
if (logEntry.item.data.action_data.command === 'isolate') {
isIsolateAction = true;
}
if (!!logEntry.item.data.completed_at && !logEntry.item.data.error) {
isSuccessful = true;
} else {
avatarColor = theme.euiColorVis9_behindText;
}
} else if (logEntry.type === ActivityLogItemTypes.RESPONSE) {
iconType = 'check';
isResponseEvent = true;
if (logEntry.item.data.EndpointActions.data.command === 'isolate') {
isIsolateAction = true;
}
if (logEntry.item.data.EndpointActions.completed_at) {
isCompleted = true;
if (!logEntry.item.data.error) {
isSuccessful = true;
avatarColor = theme.euiColorVis0_behindText;
} else {
isSuccessful = false;
avatarColor = theme.euiColorVis9_behindText;
}
}
}

Expand All @@ -75,13 +127,23 @@ const useLogEntryUIProps = (

const getResponseEventTitle = () => {
if (isIsolateAction) {
if (isSuccessful) {
if (isCompleted) {
if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful;
}
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful;
} else if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.isolationSuccessful;
} else {
return i18.ACTIVITY_LOG.LogEntry.response.isolationFailed;
}
} else {
if (isSuccessful) {
if (isCompleted) {
if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful;
}
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful;
} else if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationSuccessful;
} else {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationFailed;
Expand All @@ -91,18 +153,22 @@ const useLogEntryUIProps = (

return {
actionEventTitle,
avatarColor,
avatarIconColor,
avatarSize,
commentText,
commentType,
displayComment,
displayResponseEvent,
failedActionEventTitle,
iconType,
isResponseEvent,
isSuccessful,
isCompleted,
responseEventTitle: getResponseEventTitle(),
username,
};
}, [logEntry]);
}, [logEntry, theme]);
};

const StyledEuiComment = styled(EuiComment)`
Expand All @@ -126,28 +192,41 @@ const StyledEuiComment = styled(EuiComment)`
`;

export const LogEntry = memo(({ logEntry }: { logEntry: Immutable<ActivityLogEntry> }) => {
const theme = useEuiTheme();
const {
actionEventTitle,
avatarColor,
avatarIconColor,
avatarSize,
commentText,
commentType,
displayComment,
displayResponseEvent,
failedActionEventTitle,
iconType,
isResponseEvent,
isSuccessful,
responseEventTitle,
username,
} = useLogEntryUIProps(logEntry);
} = useLogEntryUIProps(logEntry, theme);

return (
<StyledEuiComment
type={(commentType ?? 'regular') as EuiCommentProps['type']}
username={username}
timestamp={<FormattedRelativePreferenceDate value={logEntry.item.data['@timestamp']} />}
event={<b>{displayResponseEvent ? responseEventTitle : actionEventTitle}</b>}
event={
<b>
{displayResponseEvent
? responseEventTitle
: failedActionEventTitle
? failedActionEventTitle
: actionEventTitle}
</b>
}
timelineIcon={
<LogEntryTimelineIcon {...{ avatarSize, iconType, isResponseEvent, isSuccessful }} />
<LogEntryTimelineIcon
{...{ avatarSize, iconType, isResponseEvent, avatarColor, avatarIconColor }}
/>
}
data-test-subj="timelineEntry"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,27 @@

import React, { memo } from 'react';
import { EuiAvatar, EuiAvatarProps } from '@elastic/eui';
import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';

export const LogEntryTimelineIcon = memo(
({
avatarColor,
avatarIconColor,
avatarSize,
isResponseEvent,
isSuccessful,
iconType,
isResponseEvent,
}: {
avatarColor: EuiAvatarProps['color'];
avatarIconColor?: EuiAvatarProps['iconColor'];
avatarSize: EuiAvatarProps['size'];
isResponseEvent: boolean;
isSuccessful: boolean;
iconType: EuiAvatarProps['iconType'];
isResponseEvent: boolean;
}) => {
const euiTheme = useEuiTheme();

return (
<EuiAvatar
name="Timeline Icon"
size={avatarSize ?? 's'}
color={
isResponseEvent && !isSuccessful
? euiTheme.euiColorVis9_behindText
: euiTheme.euiColorLightestShade
}
iconColor="default"
color={avatarColor}
iconColor={avatarIconColor ?? 'default'}
iconType={iconType ?? 'dot'}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import React, { ComponentType } from 'react';
import moment from 'moment';

import { ActivityLog, Immutable } from '../../../../../../common/endpoint/types';
import {
ActivityLog,
Immutable,
ActivityLogItemTypes,
} from '../../../../../../common/endpoint/types';
import { EndpointDetailsFlyoutTabs } from './components/endpoint_details_tabs';
import { EndpointActivityLog } from './endpoint_activity_log';
import { EndpointDetailsFlyout } from '.';
Expand All @@ -26,7 +30,7 @@ export const dummyEndpointActivityLog = (
endDate: moment().toString(),
data: [
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand All @@ -44,7 +48,7 @@ export const dummyEndpointActivityLog = (
},
},
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand All @@ -63,7 +67,7 @@ export const dummyEndpointActivityLog = (
},
},
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand All @@ -82,7 +86,7 @@ export const dummyEndpointActivityLog = (
},
},
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand Down
Loading