mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution][Endpoint] Fix the endpoint pending actions status and popover to include totals for all actions (#136966)
* update pending badge logic fixes elastic/security-team/issues/4356 * remove command/todo * Rework logic in EndpointHostIsolationStatus to ensure that multiple pending of the same type, then still show isolating/releasing * Fix for when there are no pending isolation but there are others * Fix pending action api service so that it only waits for a metadta update for isolate/release * Fix tests * add additional test Co-authored-by: Paul Tavares <paul.tavares@elastic.co>
This commit is contained in:
parent
78da900fd4
commit
ccabbc735b
12 changed files with 305 additions and 103 deletions
|
@ -236,7 +236,7 @@ export interface ResponseActionApiResponse<TOutput extends object = object> {
|
|||
export interface EndpointPendingActions {
|
||||
agent_id: string;
|
||||
pending_actions: {
|
||||
/** Number of actions pending for each type. The `key` could be one of the `ISOLATION_ACTIONS` values. */
|
||||
/** Number of actions pending for each type. The `key` could be one of the `RESPONSE_ACTION_COMMANDS` values. */
|
||||
[key: string]: number;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { memo } from 'react';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiToolTip } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { EndpointHostIsolationStatusProps } from './host_isolation';
|
||||
|
||||
export const AgentPendingActionStatusBadge = memo<
|
||||
{ 'data-test-subj'?: string } & Pick<EndpointHostIsolationStatusProps, 'pendingActions'>
|
||||
>(({ 'data-test-subj': dataTestSubj, pendingActions }) => {
|
||||
return (
|
||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
||||
<EuiToolTip
|
||||
display="block"
|
||||
anchorClassName="eui-textTruncate"
|
||||
content={
|
||||
<div style={{ width: 150 }} data-test-subj={`${dataTestSubj}-tooltipContent`}>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingActions"
|
||||
defaultMessage="Pending actions:"
|
||||
/>
|
||||
</div>
|
||||
{!!pendingActions.pendingIsolate && (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingIsolate"
|
||||
defaultMessage="Isolate"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingActions.pendingIsolate}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{!!pendingActions.pendingUnIsolate && (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingUnIsolate"
|
||||
defaultMessage="Release"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingActions.pendingUnIsolate}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{!!pendingActions.pendingKillProcess && (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingKillProcess"
|
||||
defaultMessage="Kill process"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingActions.pendingKillProcess}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{!!pendingActions.pendingSuspendProcess && (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingSuspendProcess"
|
||||
defaultMessage="Suspend process"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingActions.pendingSuspendProcess}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{!!pendingActions.pendingRunningProcesses && (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingRunningProcesses"
|
||||
defaultMessage="Processes"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingActions.pendingRunningProcesses}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EuiTextColor color="subdued" data-test-subj={`${dataTestSubj}-pending`}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.multiplePendingActions"
|
||||
defaultMessage="{count} {count, plural, one {action} other {actions}} pending"
|
||||
values={{
|
||||
count: Object.values(pendingActions).reduce((prev, curr) => prev + curr, 0),
|
||||
}}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</EuiToolTip>
|
||||
</EuiBadge>
|
||||
);
|
||||
});
|
||||
|
||||
AgentPendingActionStatusBadge.displayName = 'AgentPendingActionStatusBadge';
|
|
@ -26,8 +26,7 @@ describe('when using the EndpointHostIsolationStatus component', () => {
|
|||
{...{
|
||||
'data-test-subj': 'test',
|
||||
isIsolated: false,
|
||||
pendingUnIsolate: 0,
|
||||
pendingIsolate: 0,
|
||||
pendingActions: {},
|
||||
...renderProps,
|
||||
}}
|
||||
/>
|
||||
|
@ -45,9 +44,64 @@ describe('when using the EndpointHostIsolationStatus component', () => {
|
|||
});
|
||||
|
||||
it.each([
|
||||
['Isolating', { pendingIsolate: 2 }],
|
||||
['Releasing', { pendingUnIsolate: 2 }],
|
||||
['4 actions pending', { isIsolated: true, pendingUnIsolate: 2, pendingIsolate: 2 }],
|
||||
[
|
||||
'Isolating',
|
||||
{
|
||||
pendingActions: {
|
||||
pendingIsolate: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'Releasing',
|
||||
{
|
||||
pendingActions: {
|
||||
pendingUnIsolate: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
// Because they are both of the same type and there are no other types,
|
||||
// the status should be `isolating`
|
||||
'Isolating',
|
||||
{
|
||||
pendingActions: {
|
||||
pendingIsolate: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
// Because they are both of the same type and there are no other types,
|
||||
// the status should be `Releasing`
|
||||
'Releasing',
|
||||
{
|
||||
pendingActions: {
|
||||
pendingUnIsolate: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'10 actions pending',
|
||||
{
|
||||
isIsolated: true,
|
||||
pendingActions: {
|
||||
pendingIsolate: 2,
|
||||
pendingUnIsolate: 2,
|
||||
pendingKillProcess: 2,
|
||||
pendingSuspendProcess: 2,
|
||||
pendingRunningProcesses: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'1 action pending',
|
||||
{
|
||||
isIsolated: true,
|
||||
pendingActions: {
|
||||
pendingKillProcess: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
])('should show %s}', (expectedLabel, componentProps) => {
|
||||
const { getByTestId } = render(componentProps);
|
||||
expect(getByTestId('test').textContent).toBe(expectedLabel);
|
||||
|
@ -61,15 +115,22 @@ describe('when using the EndpointHostIsolationStatus component', () => {
|
|||
});
|
||||
|
||||
it('should render `null` if not isolated', () => {
|
||||
const renderResult = render({ pendingIsolate: 10, pendingUnIsolate: 20 });
|
||||
const renderResult = render({
|
||||
pendingActions: {
|
||||
pendingIsolate: 10,
|
||||
pendingUnIsolate: 20,
|
||||
},
|
||||
});
|
||||
expect(renderResult.container.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should show `Isolated` when no pending actions and isolated', () => {
|
||||
const { getByTestId } = render({
|
||||
isIsolated: true,
|
||||
pendingIsolate: 10,
|
||||
pendingUnIsolate: 20,
|
||||
pendingActions: {
|
||||
pendingIsolate: 10,
|
||||
pendingUnIsolate: 20,
|
||||
},
|
||||
});
|
||||
expect(getByTestId('test').textContent).toBe('Isolated');
|
||||
});
|
||||
|
|
|
@ -6,17 +6,23 @@
|
|||
*/
|
||||
|
||||
import React, { memo, useMemo, useRef, useEffect } from 'react';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiBadge, EuiTextColor } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
import { AgentPendingActionStatusBadge } from '../agent_pending_action_status_badge';
|
||||
|
||||
export interface EndpointHostIsolationStatusProps {
|
||||
isIsolated: boolean;
|
||||
/** the count of pending isolate actions */
|
||||
pendingIsolate?: number;
|
||||
/** the count of pending unisolate actions */
|
||||
pendingUnIsolate?: number;
|
||||
pendingActions: {
|
||||
/** the count of pending isolate actions */
|
||||
pendingIsolate?: number;
|
||||
/** the count of pending unisolate actions */
|
||||
pendingUnIsolate?: number;
|
||||
pendingKillProcess?: number;
|
||||
pendingSuspendProcess?: number;
|
||||
pendingRunningProcesses?: number;
|
||||
};
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
|
@ -26,15 +32,50 @@ export interface EndpointHostIsolationStatusProps {
|
|||
* (`null` is returned)
|
||||
*/
|
||||
export const EndpointHostIsolationStatus = memo<EndpointHostIsolationStatusProps>(
|
||||
({ isIsolated, pendingIsolate = 0, pendingUnIsolate = 0, 'data-test-subj': dataTestSubj }) => {
|
||||
({ isIsolated, pendingActions, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isPendingStatusDisabled = useIsExperimentalFeatureEnabled(
|
||||
'disableIsolationUIPendingStatuses'
|
||||
);
|
||||
|
||||
const {
|
||||
pendingIsolate = 0,
|
||||
pendingUnIsolate = 0,
|
||||
pendingKillProcess = 0,
|
||||
pendingSuspendProcess = 0,
|
||||
pendingRunningProcesses = 0,
|
||||
} = pendingActions;
|
||||
|
||||
const wasReleasing = useRef<boolean>(false);
|
||||
const wasIsolating = useRef<boolean>(false);
|
||||
|
||||
const totalPending = useMemo(
|
||||
() =>
|
||||
pendingIsolate +
|
||||
pendingUnIsolate +
|
||||
pendingKillProcess +
|
||||
pendingSuspendProcess +
|
||||
pendingRunningProcesses,
|
||||
[
|
||||
pendingIsolate,
|
||||
pendingKillProcess,
|
||||
pendingRunningProcesses,
|
||||
pendingSuspendProcess,
|
||||
pendingUnIsolate,
|
||||
]
|
||||
);
|
||||
|
||||
const hasMultipleActionTypesPending = useMemo<boolean>(() => {
|
||||
return (
|
||||
Object.values(pendingActions).reduce((countOfTypes, pendingActionCount) => {
|
||||
if (pendingActionCount > 0) {
|
||||
return countOfTypes + 1;
|
||||
}
|
||||
return countOfTypes;
|
||||
}, 0) > 1
|
||||
);
|
||||
}, [pendingActions]);
|
||||
|
||||
useEffect(() => {
|
||||
wasReleasing.current = pendingIsolate === 0 && pendingUnIsolate > 0;
|
||||
wasIsolating.current = pendingIsolate > 0 && pendingUnIsolate === 0;
|
||||
|
@ -58,7 +99,7 @@ export const EndpointHostIsolationStatus = memo<EndpointHostIsolationStatusProps
|
|||
}
|
||||
|
||||
// If nothing is pending
|
||||
if (!(pendingIsolate || pendingUnIsolate)) {
|
||||
if (totalPending === 0) {
|
||||
// and host is either releasing and or currently released, then render nothing
|
||||
if ((!wasIsolating.current && wasReleasing.current) || !isIsolated) {
|
||||
return null;
|
||||
|
@ -76,55 +117,22 @@ export const EndpointHostIsolationStatus = memo<EndpointHostIsolationStatusProps
|
|||
}
|
||||
}
|
||||
|
||||
// If there are multiple types of pending isolation actions, then show count of actions with tooltip that displays breakdown
|
||||
if (pendingIsolate && pendingUnIsolate) {
|
||||
// If there are different types of action pending
|
||||
// --OR--
|
||||
// the only type of actions pending is NOT isolate/release,
|
||||
// then show a summary with tooltip
|
||||
if (hasMultipleActionTypesPending || (!pendingIsolate && !pendingUnIsolate)) {
|
||||
return (
|
||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
||||
<EuiToolTip
|
||||
display="block"
|
||||
anchorClassName="eui-textTruncate"
|
||||
content={
|
||||
<div data-test-subj={getTestId('tooltipContent')}>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingActions"
|
||||
defaultMessage="Pending actions:"
|
||||
/>
|
||||
</div>
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingIsolate"
|
||||
defaultMessage="Isolate"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingIsolate}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem grow>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.tooltipPendingUnIsolate"
|
||||
defaultMessage="Release"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{pendingUnIsolate}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EuiTextColor color="subdued" data-test-subj={getTestId('pending')}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolationStatus.multiplePendingActions"
|
||||
defaultMessage="{count} actions pending"
|
||||
values={{ count: pendingIsolate + pendingUnIsolate }}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</EuiToolTip>
|
||||
</EuiBadge>
|
||||
<AgentPendingActionStatusBadge
|
||||
data-test-subj={dataTestSubj}
|
||||
pendingActions={pendingActions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Show 'pending [un]isolate' depending on what's pending
|
||||
// show pending isolation badge if a single type of isolation action has pending numbers.
|
||||
// We don't care about the count here because if there were more than 1 of the same type
|
||||
// (ex. 3 isolate... 0 release), then the action status displayed is still the same - "isolating".
|
||||
return (
|
||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
||||
<EuiTextColor color="subdued" data-test-subj={getTestId('pending')}>
|
||||
|
@ -143,12 +151,15 @@ export const EndpointHostIsolationStatus = memo<EndpointHostIsolationStatusProps
|
|||
</EuiBadge>
|
||||
);
|
||||
}, [
|
||||
isPendingStatusDisabled,
|
||||
totalPending,
|
||||
hasMultipleActionTypesPending,
|
||||
pendingIsolate,
|
||||
pendingUnIsolate,
|
||||
dataTestSubj,
|
||||
getTestId,
|
||||
isIsolated,
|
||||
isPendingStatusDisabled,
|
||||
pendingIsolate,
|
||||
pendingUnIsolate,
|
||||
pendingActions,
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('When using the EndpointAgentAndIsolationStatus component', () => {
|
|||
renderProps = {
|
||||
status: HostStatus.HEALTHY,
|
||||
'data-test-subj': 'test',
|
||||
pendingActions: {},
|
||||
};
|
||||
|
||||
render = () => {
|
||||
|
|
|
@ -21,7 +21,7 @@ const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
|||
`;
|
||||
|
||||
export interface EndpointAgentAndIsolationStatusProps
|
||||
extends Pick<EndpointHostIsolationStatusProps, 'pendingIsolate' | 'pendingUnIsolate'> {
|
||||
extends Pick<EndpointHostIsolationStatusProps, 'pendingActions'> {
|
||||
status: HostStatus;
|
||||
/**
|
||||
* If defined with a boolean, then the isolation status will be shown along with the agent status.
|
||||
|
@ -33,7 +33,7 @@ export interface EndpointAgentAndIsolationStatusProps
|
|||
}
|
||||
|
||||
export const EndpointAgentAndIsolationStatus = memo<EndpointAgentAndIsolationStatusProps>(
|
||||
({ status, isIsolated, pendingIsolate, pendingUnIsolate, 'data-test-subj': dataTestSubj }) => {
|
||||
({ status, isIsolated, pendingActions, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
return (
|
||||
<EuiFlexGroupStyled
|
||||
|
@ -50,8 +50,7 @@ export const EndpointAgentAndIsolationStatus = memo<EndpointAgentAndIsolationSta
|
|||
<EndpointHostIsolationStatus
|
||||
data-test-subj={getTestId('isolationStatus')}
|
||||
isIsolated={isIsolated}
|
||||
pendingIsolate={pendingIsolate}
|
||||
pendingUnIsolate={pendingUnIsolate}
|
||||
pendingActions={pendingActions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -32,20 +32,18 @@ export const HeaderEndpointInfo = memo<HeaderEndpointInfoProps>(({ endpointId })
|
|||
refetchInterval: 10000,
|
||||
});
|
||||
|
||||
const pendingIsolationActions = useMemo<
|
||||
Pick<Required<EndpointHostIsolationStatusProps>, 'pendingIsolate' | 'pendingUnIsolate'>
|
||||
const pendingActionRequests = useMemo<
|
||||
Pick<Required<EndpointHostIsolationStatusProps>, 'pendingActions'>
|
||||
>(() => {
|
||||
if (endpointPendingActions?.data.length) {
|
||||
const pendingActions = endpointPendingActions.data[0].pending_actions;
|
||||
|
||||
return {
|
||||
pendingIsolate: pendingActions.isolate ?? 0,
|
||||
pendingUnIsolate: pendingActions.unisolate ?? 0,
|
||||
};
|
||||
}
|
||||
const pendingActions = endpointPendingActions?.data?.[0].pending_actions;
|
||||
return {
|
||||
pendingIsolate: 0,
|
||||
pendingUnIsolate: 0,
|
||||
pendingActions: {
|
||||
pendingIsolate: pendingActions?.isolate ?? 0,
|
||||
pendingUnIsolate: pendingActions?.unisolate ?? 0,
|
||||
pendingKillProcess: pendingActions?.['kill-process'] ?? 0,
|
||||
pendingSuspendProcess: pendingActions?.['suspend-process'] ?? 0,
|
||||
pendingRunningProcesses: pendingActions?.['running-processes'] ?? 0,
|
||||
},
|
||||
};
|
||||
}, [endpointPendingActions?.data]);
|
||||
|
||||
|
@ -75,7 +73,7 @@ export const HeaderEndpointInfo = memo<HeaderEndpointInfoProps>(({ endpointId })
|
|||
<EndpointAgentAndIsolationStatus
|
||||
status={endpointDetails.host_status}
|
||||
isIsolated={endpointDetails.metadata.Endpoint.state?.isolation}
|
||||
{...pendingIsolationActions}
|
||||
{...pendingActionRequests}
|
||||
data-test-subj="responderHeaderEndpointAgentIsolationStatus"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -54,7 +54,10 @@ export const EndpointStatusActionResult = memo<
|
|||
});
|
||||
|
||||
const pendingIsolationActions = useMemo<
|
||||
Pick<Required<EndpointHostIsolationStatusProps>, 'pendingIsolate' | 'pendingUnIsolate'>
|
||||
Pick<
|
||||
Required<EndpointHostIsolationStatusProps['pendingActions']>,
|
||||
'pendingIsolate' | 'pendingUnIsolate'
|
||||
>
|
||||
>(() => {
|
||||
if (endpointPendingActions?.data.length) {
|
||||
const pendingActions = endpointPendingActions.data[0].pending_actions;
|
||||
|
|
|
@ -287,6 +287,9 @@ export const getEndpointHostIsolationStatusPropsCallback: (
|
|||
return (endpoint: HostMetadata) => {
|
||||
let pendingIsolate = 0;
|
||||
let pendingUnIsolate = 0;
|
||||
let pendingKillProcess = 0;
|
||||
let pendingSuspendProcess = 0;
|
||||
let pendingRunningProcesses = 0;
|
||||
|
||||
if (isLoadedResourceState(pendingActionsState)) {
|
||||
const endpointPendingActions = pendingActionsState.data.get(endpoint.elastic.agent.id);
|
||||
|
@ -294,13 +297,21 @@ export const getEndpointHostIsolationStatusPropsCallback: (
|
|||
if (endpointPendingActions) {
|
||||
pendingIsolate = endpointPendingActions?.isolate ?? 0;
|
||||
pendingUnIsolate = endpointPendingActions?.unisolate ?? 0;
|
||||
pendingKillProcess = endpointPendingActions?.['kill-process'] ?? 0;
|
||||
pendingSuspendProcess = endpointPendingActions?.['suspend-process'] ?? 0;
|
||||
pendingRunningProcesses = endpointPendingActions?.['running-processes'] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isIsolated: isEndpointHostIsolated(endpoint),
|
||||
pendingIsolate,
|
||||
pendingUnIsolate,
|
||||
pendingActions: {
|
||||
pendingIsolate,
|
||||
pendingUnIsolate,
|
||||
pendingKillProcess,
|
||||
pendingSuspendProcess,
|
||||
pendingRunningProcesses,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -82,8 +82,13 @@ export const EndpointOverview = React.memo<Props>(({ contextID, data }) => {
|
|||
<AgentStatus hostStatus={data.elasticAgentStatus} />
|
||||
<EndpointHostIsolationStatus
|
||||
isIsolated={Boolean(data.isolation)}
|
||||
pendingIsolate={data.pendingActions?.isolate ?? 0}
|
||||
pendingUnIsolate={data.pendingActions?.unisolate ?? 0}
|
||||
pendingActions={{
|
||||
pendingIsolate: data.pendingActions?.isolate ?? 0,
|
||||
pendingUnIsolate: data.pendingActions?.unisolate ?? 0,
|
||||
pendingKillProcess: data.pendingActions?.['kill-process'] ?? 0,
|
||||
pendingSuspendProcess: data.pendingActions?.['suspend-process'] ?? 0,
|
||||
pendingRunningProcesses: data.pendingActions?.['running-processes'] ?? 0,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
@ -46,8 +46,10 @@ export const AgentStatuses = React.memo(
|
|||
<EuiFlexItem grow={false}>
|
||||
<EndpointHostIsolationStatus
|
||||
isIsolated={isIsolated}
|
||||
pendingIsolate={pendingIsolation}
|
||||
pendingUnIsolate={pendingUnisolation}
|
||||
pendingActions={{
|
||||
pendingIsolate: pendingIsolation,
|
||||
pendingUnIsolate: pendingUnisolation,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -196,15 +196,16 @@ export const getPendingActionCounts = async (
|
|||
);
|
||||
|
||||
const pending: EndpointPendingActions[] = [];
|
||||
|
||||
for (const agentId of agentIDs) {
|
||||
const agentResponses = responses[agentId];
|
||||
|
||||
// get response actionIds for responses with ACKs
|
||||
// get response actionIds for responses with ACKs from the fleet agent
|
||||
const ackResponseActionIdList: string[] = agentResponses
|
||||
.filter(hasAckInResponse)
|
||||
.map((response) => response.action_id);
|
||||
|
||||
// actions Ids that are indexed in new response index
|
||||
// actions Ids that are indexed in new endpoint response index
|
||||
const indexedActionIds = await hasEndpointResponseDoc({
|
||||
agentId,
|
||||
actionIds: ackResponseActionIdList,
|
||||
|
@ -350,20 +351,30 @@ const fetchActionResponses = async (
|
|||
);
|
||||
|
||||
for (const actionResponse of actionResponses) {
|
||||
const lastEndpointMetadataEventTimestamp = endpointLastEventCreated[actionResponse.agent_id];
|
||||
const actionCompletedAtTimestamp = new Date(actionResponse.completed_at);
|
||||
// If enough time has lapsed in checking for updated Endpoint metadata doc so that we don't keep
|
||||
// checking it forever.
|
||||
// It uses the `@timestamp` in order to ensure we are looking at times that were set by the server
|
||||
const enoughTimeHasLapsed =
|
||||
Date.now() - new Date(actionResponse['@timestamp']).getTime() >
|
||||
PENDING_ACTION_RESPONSE_MAX_LAPSED_TIME;
|
||||
const actionCommand = actionResponse.action_data.command;
|
||||
|
||||
if (
|
||||
!lastEndpointMetadataEventTimestamp ||
|
||||
enoughTimeHasLapsed ||
|
||||
lastEndpointMetadataEventTimestamp > actionCompletedAtTimestamp
|
||||
) {
|
||||
// We only (possibly) withhold fleet action responses for `isolate` and `unisolate`.
|
||||
// All others should just return the responses and not wait until a metadata
|
||||
// document update is received.
|
||||
if (actionCommand === 'unisolate' || actionCommand === 'isolate') {
|
||||
const lastEndpointMetadataEventTimestamp = endpointLastEventCreated[actionResponse.agent_id];
|
||||
const actionCompletedAtTimestamp = new Date(actionResponse.completed_at);
|
||||
|
||||
// If enough time has lapsed in checking for updated Endpoint metadata doc so that we don't keep
|
||||
// checking it forever.
|
||||
// It uses the `@timestamp` in order to ensure we are looking at times that were set by the server
|
||||
const enoughTimeHasLapsed =
|
||||
Date.now() - new Date(actionResponse['@timestamp']).getTime() >
|
||||
PENDING_ACTION_RESPONSE_MAX_LAPSED_TIME;
|
||||
|
||||
if (
|
||||
!lastEndpointMetadataEventTimestamp ||
|
||||
enoughTimeHasLapsed ||
|
||||
lastEndpointMetadataEventTimestamp > actionCompletedAtTimestamp
|
||||
) {
|
||||
actionResponsesByAgentId[actionResponse.agent_id].push(actionResponse);
|
||||
}
|
||||
} else {
|
||||
actionResponsesByAgentId[actionResponse.agent_id].push(actionResponse);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue