mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution][Endpoint][SentinelOne] Consolidated agent status component (#181632)
## Summary Adds an agent status component that shows agent isolation status, os and last checkin info for endpoint/non-endpoint agents. Updates agent status based on `agentStatusClientEnabled` ff for 1. Endpoint list 2. Endpoint details 3. Endpoint/SentinelOne alerts 4. Endpoint/SentinelOne alerts -> timeline view 5. Endpoint/SentinelOne responder headers 6. Hosts page -> Endpoint Host overview <details><summary>Screenshots</summary> <h4>1. Endpoint list</h4> <img src="259f3053
-7834-4c06-8b91-6519486386ad"/> <h4>2. Endpoint details</h4> <img src="4a04817a
-fa6d-47a6-a938-a4b640f3d039"/> <h4>3. Endpoint alert</h4> <img src="4d9a6975
-bed9-49a4-b6d8-2d7685692180"/> <h4>3. SentinelOne Alert</h4> <img src="3616fb49
-afc9-4584-880f-fab1e50ea4ee"/> <h4>4. Endpoint alert -> Timeline view -> details flyout </h4> <img src="7d2ead35
-5401-4f01-9d5d-89b2e4ff9d9b" /> <h4>4. SentinelOne alert -> Timeline view -> details flyout </h4> <img src="edd1839a
-18ef-44d8-8688-35c181527a29" /> <h4>5. Endpoint responder</h4> <img src="eaeb7293
-84ad-4195-9126-eb1e330f822f"/> <h4>5. SentinelOne responder</h4> <img src="8ea1a50a
-53d2-40ed-aeb4-3620d4e9ebfc"/> <h4>6. Host Page -> Endpoint overview</h4> <img src="c443331a
-0cd8-4fa1-a59c-b254dbaae6d9"/> </details> ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
This commit is contained in:
parent
b00862240c
commit
3227e4c4eb
33 changed files with 997 additions and 469 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1565,6 +1565,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/
|
||||||
## Security Solution sub teams - security-defend-workflows
|
## Security Solution sub teams - security-defend-workflows
|
||||||
/x-pack/plugins/security_solution/public/management/ @elastic/security-defend-workflows
|
/x-pack/plugins/security_solution/public/management/ @elastic/security-defend-workflows
|
||||||
/x-pack/plugins/security_solution/public/common/lib/endpoint*/ @elastic/security-defend-workflows
|
/x-pack/plugins/security_solution/public/common/lib/endpoint*/ @elastic/security-defend-workflows
|
||||||
|
/x-pack/plugins/security_solution/public/common/components/agents/ @elastic/security-defend-workflows
|
||||||
/x-pack/plugins/security_solution/public/common/components/endpoint/ @elastic/security-defend-workflows
|
/x-pack/plugins/security_solution/public/common/components/endpoint/ @elastic/security-defend-workflows
|
||||||
/x-pack/plugins/security_solution/common/endpoint/ @elastic/security-defend-workflows
|
/x-pack/plugins/security_solution/common/endpoint/ @elastic/security-defend-workflows
|
||||||
/x-pack/plugins/security_solution/server/endpoint/ @elastic/security-defend-workflows
|
/x-pack/plugins/security_solution/server/endpoint/ @elastic/security-defend-workflows
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* 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, useMemo } from 'react';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiToolTip } from '@elastic/eui';
|
||||||
|
import type { EndpointPendingActions } from '../../../../../common/endpoint/types';
|
||||||
|
import type { ResponseActionsApiCommandNames } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||||
|
import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||||
|
import { ISOLATED_LABEL, ISOLATING_LABEL, RELEASING_LABEL } from './endpoint/endpoint_agent_status';
|
||||||
|
import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator';
|
||||||
|
|
||||||
|
const TOOLTIP_CONTENT_STYLES: React.CSSProperties = Object.freeze({ width: 150 });
|
||||||
|
|
||||||
|
interface AgentResponseActionsStatusProps {
|
||||||
|
/** The host's individual pending action list as return by the pending action summary api */
|
||||||
|
pendingActions: EndpointPendingActions['pending_actions'];
|
||||||
|
/** Is host currently isolated */
|
||||||
|
isIsolated: boolean;
|
||||||
|
'data-test-subj'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AgentResponseActionsStatus = memo<AgentResponseActionsStatusProps>(
|
||||||
|
({ pendingActions, isIsolated, 'data-test-subj': dataTestSubj }) => {
|
||||||
|
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||||
|
|
||||||
|
interface PendingActionsState {
|
||||||
|
actionList: Array<{ label: string; count: number }>;
|
||||||
|
totalPending: number;
|
||||||
|
wasReleasing: boolean;
|
||||||
|
wasIsolating: boolean;
|
||||||
|
hasMultipleActionTypesPending: boolean;
|
||||||
|
hasPendingIsolate: boolean;
|
||||||
|
hasPendingUnIsolate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
totalPending,
|
||||||
|
actionList,
|
||||||
|
wasReleasing,
|
||||||
|
wasIsolating,
|
||||||
|
hasMultipleActionTypesPending,
|
||||||
|
hasPendingIsolate,
|
||||||
|
hasPendingUnIsolate,
|
||||||
|
} = useMemo<PendingActionsState>(() => {
|
||||||
|
const list: Array<{ label: string; count: number }> = [];
|
||||||
|
let actionTotal = 0;
|
||||||
|
const pendingActionEntries = Object.entries(pendingActions);
|
||||||
|
const actionTypesCount = pendingActionEntries.length;
|
||||||
|
|
||||||
|
pendingActionEntries.sort().forEach(([actionName, actionCount]) => {
|
||||||
|
actionTotal += actionCount;
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
count: actionCount,
|
||||||
|
label:
|
||||||
|
RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[
|
||||||
|
actionName as ResponseActionsApiCommandNames
|
||||||
|
] ?? actionName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const pendingIsolate = pendingActions.isolate ?? 0;
|
||||||
|
const pendingUnIsolate = pendingActions.unisolate ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
actionList: list,
|
||||||
|
totalPending: actionTotal,
|
||||||
|
wasReleasing: pendingIsolate === 0 && pendingUnIsolate > 0,
|
||||||
|
wasIsolating: pendingIsolate > 0 && pendingUnIsolate === 0,
|
||||||
|
hasMultipleActionTypesPending: actionTypesCount > 1,
|
||||||
|
hasPendingIsolate: pendingIsolate > 0,
|
||||||
|
hasPendingUnIsolate: pendingUnIsolate > 0,
|
||||||
|
};
|
||||||
|
}, [pendingActions]);
|
||||||
|
|
||||||
|
const badgeDisplayValue = useMemo(() => {
|
||||||
|
return hasPendingIsolate ? (
|
||||||
|
ISOLATING_LABEL
|
||||||
|
) : hasPendingUnIsolate ? (
|
||||||
|
RELEASING_LABEL
|
||||||
|
) : isIsolated ? (
|
||||||
|
ISOLATED_LABEL
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.securitySolution.agentStatus.actionStatus.multiplePendingActions"
|
||||||
|
defaultMessage="{count} {count, plural, one {action} other {actions}} pending"
|
||||||
|
values={{
|
||||||
|
count: totalPending,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [hasPendingIsolate, hasPendingUnIsolate, isIsolated, totalPending]);
|
||||||
|
|
||||||
|
const isolatedBadge = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
||||||
|
{ISOLATED_LABEL}
|
||||||
|
</EuiBadge>
|
||||||
|
);
|
||||||
|
}, [dataTestSubj]);
|
||||||
|
|
||||||
|
// If nothing is pending
|
||||||
|
if (totalPending === 0) {
|
||||||
|
// and host is either releasing and or currently released, then render nothing
|
||||||
|
if ((!wasIsolating && wasReleasing) || !isIsolated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// else host was isolating or is isolated, then show isolation badge
|
||||||
|
else if ((!isIsolated && wasIsolating && !wasReleasing) || isIsolated) {
|
||||||
|
return isolatedBadge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 || (!hasPendingIsolate && !hasPendingUnIsolate)) {
|
||||||
|
return (
|
||||||
|
<EuiBadge color="hollow" data-test-subj={dataTestSubj} iconType="plus" iconSide="right">
|
||||||
|
<EuiToolTip
|
||||||
|
display="block"
|
||||||
|
anchorClassName="eui-textTruncate"
|
||||||
|
anchorProps={{ 'data-test-subj': getTestId('tooltipTrigger') }}
|
||||||
|
content={
|
||||||
|
<div style={TOOLTIP_CONTENT_STYLES} data-test-subj={`${dataTestSubj}-tooltipContent`}>
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.securitySolution.agentStatus.actionStatus.tooltipPendingActions"
|
||||||
|
defaultMessage="Pending actions:"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{actionList.map(({ count, label }) => {
|
||||||
|
return (
|
||||||
|
<EuiFlexGroup gutterSize="none" key={label}>
|
||||||
|
<EuiFlexItem>{label}</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>{count}</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<EuiTextColor color="subdued" data-test-subj={`${dataTestSubj}-pending`}>
|
||||||
|
{badgeDisplayValue}
|
||||||
|
</EuiTextColor>
|
||||||
|
</EuiToolTip>
|
||||||
|
</EuiBadge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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')}>
|
||||||
|
{badgeDisplayValue}
|
||||||
|
</EuiTextColor>
|
||||||
|
</EuiBadge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
AgentResponseActionsStatus.displayName = 'AgentResponseActionsStatus';
|
|
@ -0,0 +1,244 @@
|
||||||
|
/*
|
||||||
|
* 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 from 'react';
|
||||||
|
|
||||||
|
import { AgentStatus } from './agent_status';
|
||||||
|
import {
|
||||||
|
useAgentStatusHook,
|
||||||
|
useGetAgentStatus,
|
||||||
|
} from '../../../../management/hooks/agents/use_get_agent_status';
|
||||||
|
import {
|
||||||
|
RESPONSE_ACTION_AGENT_TYPE,
|
||||||
|
type ResponseActionAgentType,
|
||||||
|
} from '../../../../../common/endpoint/service/response_actions/constants';
|
||||||
|
import type { AppContextTestRender } from '../../../mock/endpoint';
|
||||||
|
import { createAppRootMockRenderer } from '../../../mock/endpoint';
|
||||||
|
import { HostStatus } from '../../../../../common/endpoint/types';
|
||||||
|
|
||||||
|
jest.mock('../../../hooks/use_experimental_features');
|
||||||
|
jest.mock('../../../../management/hooks/agents/use_get_agent_status');
|
||||||
|
|
||||||
|
const getAgentStatusMock = useGetAgentStatus as jest.Mock;
|
||||||
|
const useAgentStatusHookMock = useAgentStatusHook as jest.Mock;
|
||||||
|
|
||||||
|
describe('AgentStatus component', () => {
|
||||||
|
let render: (agentType?: ResponseActionAgentType) => ReturnType<AppContextTestRender['render']>;
|
||||||
|
let renderResult: ReturnType<typeof render>;
|
||||||
|
let mockedContext: AppContextTestRender;
|
||||||
|
const agentId = 'agent-id-1234';
|
||||||
|
const baseData = {
|
||||||
|
agentId,
|
||||||
|
found: true,
|
||||||
|
isolated: false,
|
||||||
|
lastSeen: new Date().toISOString(),
|
||||||
|
pendingActions: {},
|
||||||
|
status: HostStatus.HEALTHY,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockedContext = createAppRootMockRenderer();
|
||||||
|
render = (agentType?: ResponseActionAgentType) =>
|
||||||
|
(renderResult = mockedContext.render(
|
||||||
|
<AgentStatus agentId={agentId} agentType={agentType || 'endpoint'} data-test-subj="test" />
|
||||||
|
));
|
||||||
|
|
||||||
|
getAgentStatusMock.mockReturnValue({ data: {} });
|
||||||
|
useAgentStatusHookMock.mockImplementation(() => useGetAgentStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(RESPONSE_ACTION_AGENT_TYPE)('`%s` agentType', (agentType) => {
|
||||||
|
it('should show agent health status info', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: { ...baseData, agentType, status: HostStatus.OFFLINE },
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.queryByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Offline');
|
||||||
|
expect(actionStatusBadge).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and Isolated status', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
isolated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('Isolated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and Releasing status', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
isolated: true,
|
||||||
|
pendingActions: {
|
||||||
|
unisolate: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('Releasing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and Isolating status', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
pendingActions: {
|
||||||
|
isolate: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('Isolating');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and Releasing status also when multiple actions are pending', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
isolated: true,
|
||||||
|
pendingActions: {
|
||||||
|
unisolate: 1,
|
||||||
|
execute: 1,
|
||||||
|
'kill-process': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('Releasing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and Isolating status also when multiple actions are pending', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
pendingActions: {
|
||||||
|
isolate: 1,
|
||||||
|
execute: 1,
|
||||||
|
'kill-process': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('Isolating');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and pending action status when not isolating/releasing', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
pendingActions: {
|
||||||
|
'kill-process': 1,
|
||||||
|
'running-processes': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('2 actions pending');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent health status info and Isolated when pending actions', () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
isolated: true,
|
||||||
|
pendingActions: {
|
||||||
|
'kill-process': 1,
|
||||||
|
'running-processes': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(agentType);
|
||||||
|
const statusBadge = renderResult.getByTestId('test-agentStatus');
|
||||||
|
const actionStatusBadge = renderResult.getByTestId('test-actionStatuses');
|
||||||
|
|
||||||
|
expect(statusBadge.textContent).toEqual('Healthy');
|
||||||
|
expect(actionStatusBadge.textContent).toEqual('Isolated');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,15 +8,14 @@
|
||||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||||
import { getAgentStatusText } from '../../../common/components/endpoint/agent_status_text';
|
import type { EndpointPendingActions } from '../../../../../common/endpoint/types';
|
||||||
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants';
|
import { useAgentStatusHook } from '../../../../management/hooks/agents/use_get_agent_status';
|
||||||
import { useAgentStatusHook } from './use_sentinelone_host_isolation';
|
import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator';
|
||||||
import {
|
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../management/pages/endpoint_hosts/view/host_constants';
|
||||||
ISOLATED_LABEL,
|
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||||
ISOLATING_LABEL,
|
import { getAgentStatusText } from '../agent_status_text';
|
||||||
RELEASING_LABEL,
|
import { AgentResponseActionsStatus } from './agent_response_action_status';
|
||||||
} from '../../../common/components/endpoint/endpoint_agent_status';
|
|
||||||
|
|
||||||
export enum SENTINEL_ONE_NETWORK_STATUS {
|
export enum SENTINEL_ONE_NETWORK_STATUS {
|
||||||
CONNECTING = 'connecting',
|
CONNECTING = 'connecting',
|
||||||
|
@ -31,37 +30,39 @@ const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SentinelOneAgentStatus = React.memo(
|
export const AgentStatus = React.memo(
|
||||||
({ agentId, 'data-test-subj': dataTestSubj }: { agentId: string; 'data-test-subj'?: string }) => {
|
({
|
||||||
|
agentId,
|
||||||
|
agentType,
|
||||||
|
'data-test-subj': dataTestSubj,
|
||||||
|
}: {
|
||||||
|
agentId: string;
|
||||||
|
agentType: ResponseActionAgentType;
|
||||||
|
'data-test-subj'?: string;
|
||||||
|
}) => {
|
||||||
|
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||||
const useAgentStatus = useAgentStatusHook();
|
const useAgentStatus = useAgentStatusHook();
|
||||||
|
|
||||||
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
|
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||||
'sentinelOneManualHostActionsEnabled'
|
'sentinelOneManualHostActionsEnabled'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading, isFetched } = useAgentStatus([agentId], 'sentinel_one', {
|
const { data, isLoading, isFetched } = useAgentStatus([agentId], agentType, {
|
||||||
enabled: sentinelOneManualHostActionsEnabled,
|
enabled: sentinelOneManualHostActionsEnabled,
|
||||||
});
|
});
|
||||||
const agentStatus = data?.[`${agentId}`];
|
const agentStatus = data?.[`${agentId}`];
|
||||||
|
const isCurrentlyIsolated = Boolean(agentStatus?.isolated);
|
||||||
|
const pendingActions = agentStatus?.pendingActions;
|
||||||
|
|
||||||
const label = useMemo(() => {
|
const [hasPendingActions, hostPendingActions] = useMemo<
|
||||||
const currentNetworkStatus = agentStatus?.isolated;
|
[boolean, EndpointPendingActions['pending_actions']]
|
||||||
const pendingActions = agentStatus?.pendingActions;
|
>(() => {
|
||||||
|
if (!pendingActions) {
|
||||||
if (pendingActions) {
|
return [false, {}];
|
||||||
if (pendingActions.isolate > 0) {
|
|
||||||
return ISOLATING_LABEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingActions.unisolate > 0) {
|
|
||||||
return RELEASING_LABEL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNetworkStatus) {
|
return [Object.keys(pendingActions).length > 0, pendingActions];
|
||||||
return ISOLATED_LABEL;
|
}, [pendingActions]);
|
||||||
}
|
|
||||||
}, [agentStatus?.isolated, agentStatus?.pendingActions]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroupStyled
|
<EuiFlexGroupStyled
|
||||||
|
@ -75,6 +76,7 @@ export const SentinelOneAgentStatus = React.memo(
|
||||||
<EuiBadge
|
<EuiBadge
|
||||||
color={HOST_STATUS_TO_BADGE_COLOR[agentStatus.status]}
|
color={HOST_STATUS_TO_BADGE_COLOR[agentStatus.status]}
|
||||||
className="eui-textTruncate"
|
className="eui-textTruncate"
|
||||||
|
data-test-subj={getTestId('agentStatus')}
|
||||||
>
|
>
|
||||||
{getAgentStatusText(agentStatus.status)}
|
{getAgentStatusText(agentStatus.status)}
|
||||||
</EuiBadge>
|
</EuiBadge>
|
||||||
|
@ -82,11 +84,13 @@ export const SentinelOneAgentStatus = React.memo(
|
||||||
'-'
|
'-'
|
||||||
)}
|
)}
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
{isFetched && !isLoading && label && (
|
{(isCurrentlyIsolated || hasPendingActions) && (
|
||||||
<EuiFlexItem grow={false} className="eui-textTruncate isolation-status">
|
<EuiFlexItem grow={false} className="eui-textTruncate isolation-status">
|
||||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
<AgentResponseActionsStatus
|
||||||
<>{label}</>
|
data-test-subj={getTestId('actionStatuses')}
|
||||||
</EuiBadge>
|
isIsolated={isCurrentlyIsolated}
|
||||||
|
pendingActions={hostPendingActions}
|
||||||
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
)}
|
)}
|
||||||
</EuiFlexGroupStyled>
|
</EuiFlexGroupStyled>
|
||||||
|
@ -94,4 +98,4 @@ export const SentinelOneAgentStatus = React.memo(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
SentinelOneAgentStatus.displayName = 'SentinelOneAgentStatus';
|
AgentStatus.displayName = 'AgentStatus';
|
|
@ -5,8 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AppContextTestRender } from '../../../mock/endpoint';
|
import type { AppContextTestRender } from '../../../../mock/endpoint';
|
||||||
import { createAppRootMockRenderer } from '../../../mock/endpoint';
|
import { createAppRootMockRenderer } from '../../../../mock/endpoint';
|
||||||
import type {
|
import type {
|
||||||
EndpointAgentStatusByIdProps,
|
EndpointAgentStatusByIdProps,
|
||||||
EndpointAgentStatusProps,
|
EndpointAgentStatusProps,
|
||||||
|
@ -15,18 +15,18 @@ import { EndpointAgentStatus, EndpointAgentStatusById } from './endpoint_agent_s
|
||||||
import type {
|
import type {
|
||||||
EndpointPendingActions,
|
EndpointPendingActions,
|
||||||
HostInfoInterface,
|
HostInfoInterface,
|
||||||
} from '../../../../../common/endpoint/types';
|
} from '../../../../../../common/endpoint/types';
|
||||||
import { HostStatus } from '../../../../../common/endpoint/types';
|
import { HostStatus } from '../../../../../../common/endpoint/types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { EndpointActionGenerator } from '../../../../../common/endpoint/data_generators/endpoint_action_generator';
|
import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator';
|
||||||
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
|
import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data';
|
||||||
import { composeHttpHandlerMocks } from '../../../mock/endpoint/http_handler_mock_factory';
|
import { composeHttpHandlerMocks } from '../../../../mock/endpoint/http_handler_mock_factory';
|
||||||
import type { EndpointMetadataHttpMocksInterface } from '../../../../management/pages/endpoint_hosts/mocks';
|
import type { EndpointMetadataHttpMocksInterface } from '../../../../../management/pages/endpoint_hosts/mocks';
|
||||||
import { endpointMetadataHttpMocks } from '../../../../management/pages/endpoint_hosts/mocks';
|
import { endpointMetadataHttpMocks } from '../../../../../management/pages/endpoint_hosts/mocks';
|
||||||
import type { ResponseActionsHttpMocksInterface } from '../../../../management/mocks/response_actions_http_mocks';
|
import type { ResponseActionsHttpMocksInterface } from '../../../../../management/mocks/response_actions_http_mocks';
|
||||||
import { responseActionsHttpMocks } from '../../../../management/mocks/response_actions_http_mocks';
|
import { responseActionsHttpMocks } from '../../../../../management/mocks/response_actions_http_mocks';
|
||||||
import { waitFor, within, fireEvent } from '@testing-library/react';
|
import { waitFor, within, fireEvent } from '@testing-library/react';
|
||||||
import { getEmptyValue } from '../../empty_value';
|
import { getEmptyValue } from '../../../empty_value';
|
||||||
import { clone, set } from 'lodash';
|
import { clone, set } from 'lodash';
|
||||||
|
|
||||||
type AgentStatusApiMocksInterface = EndpointMetadataHttpMocksInterface &
|
type AgentStatusApiMocksInterface = EndpointMetadataHttpMocksInterface &
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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, useMemo } from 'react';
|
||||||
|
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { DEFAULT_POLL_INTERVAL } from '../../../../../management/common/constants';
|
||||||
|
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../../management/pages/endpoint_hosts/view/host_constants';
|
||||||
|
import { getEmptyValue } from '../../../empty_value';
|
||||||
|
|
||||||
|
import { useGetEndpointPendingActionsSummary } from '../../../../../management/hooks/response_actions/use_get_endpoint_pending_actions_summary';
|
||||||
|
import { useTestIdGenerator } from '../../../../../management/hooks/use_test_id_generator';
|
||||||
|
import type { EndpointPendingActions, HostInfo } from '../../../../../../common/endpoint/types';
|
||||||
|
import { useGetEndpointDetails } from '../../../../../management/hooks';
|
||||||
|
import { getAgentStatusText } from '../../agent_status_text';
|
||||||
|
import { AgentResponseActionsStatus } from '../agent_response_action_status';
|
||||||
|
|
||||||
|
export const ISOLATING_LABEL = i18n.translate(
|
||||||
|
'xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating',
|
||||||
|
{ defaultMessage: 'Isolating' }
|
||||||
|
);
|
||||||
|
export const RELEASING_LABEL = i18n.translate(
|
||||||
|
'xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating',
|
||||||
|
{ defaultMessage: 'Releasing' }
|
||||||
|
);
|
||||||
|
export const ISOLATED_LABEL = i18n.translate(
|
||||||
|
'xpack.securitySolution.endpoint.agentAndActionsStatus.isolated',
|
||||||
|
{ defaultMessage: 'Isolated' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
||||||
|
.isolation-status {
|
||||||
|
margin-left: ${({ theme }) => theme.eui.euiSizeS};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface EndpointAgentStatusProps {
|
||||||
|
endpointHostInfo: HostInfo;
|
||||||
|
/**
|
||||||
|
* If set to `true` (Default), then the endpoint isolation state and response actions count
|
||||||
|
* will be kept up to date by querying the API periodically.
|
||||||
|
* Only used if `pendingActions` is not defined.
|
||||||
|
*/
|
||||||
|
autoRefresh?: boolean;
|
||||||
|
/**
|
||||||
|
* The pending actions for the host (as return by the pending actions summary api).
|
||||||
|
* If undefined, then this component will call the API to retrieve that list of pending actions.
|
||||||
|
* NOTE: if this prop is defined, it will invalidate `autoRefresh` prop.
|
||||||
|
*/
|
||||||
|
pendingActions?: EndpointPendingActions['pending_actions'];
|
||||||
|
'data-test-subj'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the status of an Endpoint agent along with its Isolation state or the number of pending
|
||||||
|
* response actions against it.
|
||||||
|
*
|
||||||
|
* TIP: if you only have the Endpoint's `agent.id`, then consider using `EndpointAgentStatusById`,
|
||||||
|
* which will call the needed APIs to get the information necessary to display the status.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: used by `EndpointAgentStatusById`
|
||||||
|
// remove usage/code when `agentStatusClientEnabled` FF is enabled and removed
|
||||||
|
export const EndpointAgentStatus = memo<EndpointAgentStatusProps>(
|
||||||
|
({ endpointHostInfo, autoRefresh = true, pendingActions, 'data-test-subj': dataTestSubj }) => {
|
||||||
|
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||||
|
const { data: endpointPendingActions } = useGetEndpointPendingActionsSummary(
|
||||||
|
[endpointHostInfo.metadata.agent.id],
|
||||||
|
{
|
||||||
|
refetchInterval: autoRefresh ? DEFAULT_POLL_INTERVAL : false,
|
||||||
|
enabled: !pendingActions,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [hasPendingActions, hostPendingActions] = useMemo<
|
||||||
|
[boolean, EndpointPendingActions['pending_actions']]
|
||||||
|
>(() => {
|
||||||
|
if (!endpointPendingActions && !pendingActions) {
|
||||||
|
return [false, {}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = pendingActions
|
||||||
|
? pendingActions
|
||||||
|
: endpointPendingActions?.data[0].pending_actions ?? {};
|
||||||
|
|
||||||
|
return [Object.keys(pending).length > 0, pending];
|
||||||
|
}, [endpointPendingActions, pendingActions]);
|
||||||
|
|
||||||
|
const status = endpointHostInfo.host_status;
|
||||||
|
const isIsolated = Boolean(endpointHostInfo.metadata.Endpoint.state?.isolation);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiFlexGroupStyled
|
||||||
|
gutterSize="none"
|
||||||
|
responsive={false}
|
||||||
|
className="eui-textTruncate"
|
||||||
|
data-test-subj={dataTestSubj}
|
||||||
|
>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiBadge
|
||||||
|
color={status != null ? HOST_STATUS_TO_BADGE_COLOR[status] : 'warning'}
|
||||||
|
data-test-subj={getTestId('agentStatus')}
|
||||||
|
className="eui-textTruncate"
|
||||||
|
>
|
||||||
|
{getAgentStatusText(status)}
|
||||||
|
</EuiBadge>
|
||||||
|
</EuiFlexItem>
|
||||||
|
{(isIsolated || hasPendingActions) && (
|
||||||
|
<EuiFlexItem grow={false} className="eui-textTruncate isolation-status">
|
||||||
|
<AgentResponseActionsStatus
|
||||||
|
data-test-subj={getTestId('actionStatuses')}
|
||||||
|
isIsolated={isIsolated}
|
||||||
|
pendingActions={hostPendingActions}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
)}
|
||||||
|
</EuiFlexGroupStyled>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
EndpointAgentStatus.displayName = 'EndpointAgentStatus';
|
||||||
|
|
||||||
|
export interface EndpointAgentStatusByIdProps {
|
||||||
|
endpointAgentId: string;
|
||||||
|
/**
|
||||||
|
* If set to `true` (Default), then the endpoint status and isolation/action counts will
|
||||||
|
* be kept up to date by querying the API periodically
|
||||||
|
*/
|
||||||
|
autoRefresh?: boolean;
|
||||||
|
'data-test-subj'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an Endpoint Agent Id, it will make the necessary API calls and then display the agent
|
||||||
|
* status using the `<EndpointAgentStatus />` component.
|
||||||
|
*
|
||||||
|
* NOTE: if the `HostInfo` is already available, consider using `<EndpointAgentStatus/>` component
|
||||||
|
* instead in order to avoid duplicate API calls.
|
||||||
|
*/
|
||||||
|
export const EndpointAgentStatusById = memo<EndpointAgentStatusByIdProps>(
|
||||||
|
({ endpointAgentId, autoRefresh, 'data-test-subj': dataTestSubj }) => {
|
||||||
|
const { data } = useGetEndpointDetails(endpointAgentId, {
|
||||||
|
refetchInterval: autoRefresh ? DEFAULT_POLL_INTERVAL : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<EuiText size="xs" data-test-subj={dataTestSubj}>
|
||||||
|
<p>{getEmptyValue()}</p>
|
||||||
|
</EuiText>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EndpointAgentStatus
|
||||||
|
endpointHostInfo={data}
|
||||||
|
data-test-subj={dataTestSubj}
|
||||||
|
autoRefresh={autoRefresh}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
EndpointAgentStatusById.displayName = 'EndpointAgentStatusById';
|
|
@ -5,5 +5,6 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './endpoint_agent_status';
|
export * from './endpoint/endpoint_agent_status';
|
||||||
export type { EndpointAgentStatusProps } from './endpoint_agent_status';
|
export type { EndpointAgentStatusProps } from './endpoint/endpoint_agent_status';
|
||||||
|
export * from './agent_status';
|
|
@ -1,332 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { memo, useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
EuiBadge,
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiText,
|
|
||||||
EuiTextColor,
|
|
||||||
EuiToolTip,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { DEFAULT_POLL_INTERVAL } from '../../../../management/common/constants';
|
|
||||||
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../management/pages/endpoint_hosts/view/host_constants';
|
|
||||||
import { getEmptyValue } from '../../empty_value';
|
|
||||||
import {
|
|
||||||
RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP,
|
|
||||||
type ResponseActionsApiCommandNames,
|
|
||||||
} from '../../../../../common/endpoint/service/response_actions/constants';
|
|
||||||
import { useGetEndpointPendingActionsSummary } from '../../../../management/hooks/response_actions/use_get_endpoint_pending_actions_summary';
|
|
||||||
import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator';
|
|
||||||
import type { EndpointPendingActions, HostInfo } from '../../../../../common/endpoint/types';
|
|
||||||
import { useGetEndpointDetails } from '../../../../management/hooks';
|
|
||||||
import { getAgentStatusText } from '../agent_status_text';
|
|
||||||
|
|
||||||
const TOOLTIP_CONTENT_STYLES: React.CSSProperties = Object.freeze({ width: 150 });
|
|
||||||
export const ISOLATING_LABEL = i18n.translate(
|
|
||||||
'xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating',
|
|
||||||
{ defaultMessage: 'Isolating' }
|
|
||||||
);
|
|
||||||
export const RELEASING_LABEL = i18n.translate(
|
|
||||||
'xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating',
|
|
||||||
{ defaultMessage: 'Releasing' }
|
|
||||||
);
|
|
||||||
export const ISOLATED_LABEL = i18n.translate(
|
|
||||||
'xpack.securitySolution.endpoint.agentAndActionsStatus.isolated',
|
|
||||||
{ defaultMessage: 'Isolated' }
|
|
||||||
);
|
|
||||||
|
|
||||||
const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
|
||||||
.isolation-status {
|
|
||||||
margin-left: ${({ theme }) => theme.eui.euiSizeS};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export interface EndpointAgentStatusProps {
|
|
||||||
endpointHostInfo: HostInfo;
|
|
||||||
/**
|
|
||||||
* If set to `true` (Default), then the endpoint isolation state and response actions count
|
|
||||||
* will be kept up to date by querying the API periodically.
|
|
||||||
* Only used if `pendingActions` is not defined.
|
|
||||||
*/
|
|
||||||
autoRefresh?: boolean;
|
|
||||||
/**
|
|
||||||
* The pending actions for the host (as return by the pending actions summary api).
|
|
||||||
* If undefined, then this component will call the API to retrieve that list of pending actions.
|
|
||||||
* NOTE: if this prop is defined, it will invalidate `autoRefresh` prop.
|
|
||||||
*/
|
|
||||||
pendingActions?: EndpointPendingActions['pending_actions'];
|
|
||||||
'data-test-subj'?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the status of an Endpoint agent along with its Isolation state or the number of pending
|
|
||||||
* response actions against it.
|
|
||||||
*
|
|
||||||
* TIP: if you only have the Endpoint's `agent.id`, then consider using `EndpointAgentStatusById`,
|
|
||||||
* which will call the needed APIs to get the information necessary to display the status.
|
|
||||||
*/
|
|
||||||
export const EndpointAgentStatus = memo<EndpointAgentStatusProps>(
|
|
||||||
({ endpointHostInfo, autoRefresh = true, pendingActions, 'data-test-subj': dataTestSubj }) => {
|
|
||||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
|
||||||
const { data: endpointPendingActions } = useGetEndpointPendingActionsSummary(
|
|
||||||
[endpointHostInfo.metadata.agent.id],
|
|
||||||
{
|
|
||||||
refetchInterval: autoRefresh ? DEFAULT_POLL_INTERVAL : false,
|
|
||||||
enabled: !pendingActions,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const [hasPendingActions, hostPendingActions] = useMemo<
|
|
||||||
[boolean, EndpointPendingActions['pending_actions']]
|
|
||||||
>(() => {
|
|
||||||
if (!endpointPendingActions && !pendingActions) {
|
|
||||||
return [false, {}];
|
|
||||||
}
|
|
||||||
|
|
||||||
const pending = pendingActions
|
|
||||||
? pendingActions
|
|
||||||
: endpointPendingActions?.data[0].pending_actions ?? {};
|
|
||||||
|
|
||||||
return [Object.keys(pending).length > 0, pending];
|
|
||||||
}, [endpointPendingActions, pendingActions]);
|
|
||||||
|
|
||||||
const status = endpointHostInfo.host_status;
|
|
||||||
const isIsolated = Boolean(endpointHostInfo.metadata.Endpoint.state?.isolation);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EuiFlexGroupStyled
|
|
||||||
gutterSize="none"
|
|
||||||
responsive={false}
|
|
||||||
className="eui-textTruncate"
|
|
||||||
data-test-subj={dataTestSubj}
|
|
||||||
>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiBadge
|
|
||||||
color={status != null ? HOST_STATUS_TO_BADGE_COLOR[status] : 'warning'}
|
|
||||||
data-test-subj={getTestId('agentStatus')}
|
|
||||||
className="eui-textTruncate"
|
|
||||||
>
|
|
||||||
{getAgentStatusText(status)}
|
|
||||||
</EuiBadge>
|
|
||||||
</EuiFlexItem>
|
|
||||||
{(isIsolated || hasPendingActions) && (
|
|
||||||
<EuiFlexItem grow={false} className="eui-textTruncate isolation-status">
|
|
||||||
<EndpointHostResponseActionsStatus
|
|
||||||
data-test-subj={getTestId('actionStatuses')}
|
|
||||||
isIsolated={isIsolated}
|
|
||||||
pendingActions={hostPendingActions}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
</EuiFlexGroupStyled>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
EndpointAgentStatus.displayName = 'EndpointAgentStatus';
|
|
||||||
|
|
||||||
export interface EndpointAgentStatusByIdProps {
|
|
||||||
endpointAgentId: string;
|
|
||||||
/**
|
|
||||||
* If set to `true` (Default), then the endpoint status and isolation/action counts will
|
|
||||||
* be kept up to date by querying the API periodically
|
|
||||||
*/
|
|
||||||
autoRefresh?: boolean;
|
|
||||||
'data-test-subj'?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an Endpoint Agent Id, it will make the necessary API calls and then display the agent
|
|
||||||
* status using the `<EndpointAgentStatus />` component.
|
|
||||||
*
|
|
||||||
* NOTE: if the `HostInfo` is already available, consider using `<EndpointAgentStatus/>` component
|
|
||||||
* instead in order to avoid duplicate API calls.
|
|
||||||
*/
|
|
||||||
export const EndpointAgentStatusById = memo<EndpointAgentStatusByIdProps>(
|
|
||||||
({ endpointAgentId, autoRefresh, 'data-test-subj': dataTestSubj }) => {
|
|
||||||
const { data } = useGetEndpointDetails(endpointAgentId, {
|
|
||||||
refetchInterval: autoRefresh ? DEFAULT_POLL_INTERVAL : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emptyValue = (
|
|
||||||
<EuiText size="xs" data-test-subj={dataTestSubj}>
|
|
||||||
<p>{getEmptyValue()}</p>
|
|
||||||
</EuiText>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return emptyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EndpointAgentStatus
|
|
||||||
endpointHostInfo={data}
|
|
||||||
data-test-subj={dataTestSubj}
|
|
||||||
autoRefresh={autoRefresh}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
EndpointAgentStatusById.displayName = 'EndpointAgentStatusById';
|
|
||||||
|
|
||||||
interface EndpointHostResponseActionsStatusProps {
|
|
||||||
/** The host's individual pending action list as return by the pending action summary api */
|
|
||||||
pendingActions: EndpointPendingActions['pending_actions'];
|
|
||||||
/** Is host currently isolated */
|
|
||||||
isIsolated: boolean;
|
|
||||||
'data-test-subj'?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EndpointHostResponseActionsStatus = memo<EndpointHostResponseActionsStatusProps>(
|
|
||||||
({ pendingActions, isIsolated, 'data-test-subj': dataTestSubj }) => {
|
|
||||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
|
||||||
|
|
||||||
interface PendingActionsState {
|
|
||||||
actionList: Array<{ label: string; count: number }>;
|
|
||||||
totalPending: number;
|
|
||||||
wasReleasing: boolean;
|
|
||||||
wasIsolating: boolean;
|
|
||||||
hasMultipleActionTypesPending: boolean;
|
|
||||||
hasPendingIsolate: boolean;
|
|
||||||
hasPendingUnIsolate: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
totalPending,
|
|
||||||
actionList,
|
|
||||||
wasReleasing,
|
|
||||||
wasIsolating,
|
|
||||||
hasMultipleActionTypesPending,
|
|
||||||
hasPendingIsolate,
|
|
||||||
hasPendingUnIsolate,
|
|
||||||
} = useMemo<PendingActionsState>(() => {
|
|
||||||
const list: Array<{ label: string; count: number }> = [];
|
|
||||||
let actionTotal = 0;
|
|
||||||
let actionTypesCount = 0;
|
|
||||||
|
|
||||||
Object.entries(pendingActions)
|
|
||||||
.sort()
|
|
||||||
.forEach(([actionName, actionCount]) => {
|
|
||||||
actionTotal += actionCount;
|
|
||||||
actionTypesCount += 1;
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
count: actionCount,
|
|
||||||
label:
|
|
||||||
RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[
|
|
||||||
actionName as ResponseActionsApiCommandNames
|
|
||||||
] ?? actionName,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const pendingIsolate = pendingActions.isolate ?? 0;
|
|
||||||
const pendingUnIsolate = pendingActions.unisolate ?? 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
actionList: list,
|
|
||||||
totalPending: actionTotal,
|
|
||||||
wasReleasing: pendingIsolate === 0 && pendingUnIsolate > 0,
|
|
||||||
wasIsolating: pendingIsolate > 0 && pendingUnIsolate === 0,
|
|
||||||
hasMultipleActionTypesPending: actionTypesCount > 1,
|
|
||||||
hasPendingIsolate: pendingIsolate > 0,
|
|
||||||
hasPendingUnIsolate: pendingUnIsolate > 0,
|
|
||||||
};
|
|
||||||
}, [pendingActions]);
|
|
||||||
|
|
||||||
const badgeDisplayValue = useMemo(() => {
|
|
||||||
return hasPendingIsolate ? (
|
|
||||||
ISOLATING_LABEL
|
|
||||||
) : hasPendingUnIsolate ? (
|
|
||||||
RELEASING_LABEL
|
|
||||||
) : isIsolated ? (
|
|
||||||
ISOLATED_LABEL
|
|
||||||
) : (
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.securitySolution.endpoint.agentAndActionsStatus.multiplePendingActions"
|
|
||||||
defaultMessage="{count} {count, plural, one {action} other {actions}} pending"
|
|
||||||
values={{
|
|
||||||
count: totalPending,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, [hasPendingIsolate, hasPendingUnIsolate, isIsolated, totalPending]);
|
|
||||||
|
|
||||||
const isolatedBadge = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
|
||||||
{ISOLATED_LABEL}
|
|
||||||
</EuiBadge>
|
|
||||||
);
|
|
||||||
}, [dataTestSubj]);
|
|
||||||
|
|
||||||
// If nothing is pending
|
|
||||||
if (totalPending === 0) {
|
|
||||||
// and host is either releasing and or currently released, then render nothing
|
|
||||||
if ((!wasIsolating && wasReleasing) || !isIsolated) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// else host was isolating or is isolated, then show isolation badge
|
|
||||||
else if ((!isIsolated && wasIsolating && !wasReleasing) || isIsolated) {
|
|
||||||
return isolatedBadge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 || (!hasPendingIsolate && !hasPendingUnIsolate)) {
|
|
||||||
return (
|
|
||||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj} iconType="plus" iconSide="right">
|
|
||||||
<EuiToolTip
|
|
||||||
display="block"
|
|
||||||
anchorClassName="eui-textTruncate"
|
|
||||||
anchorProps={{ 'data-test-subj': getTestId('tooltipTrigger') }}
|
|
||||||
content={
|
|
||||||
<div style={TOOLTIP_CONTENT_STYLES} data-test-subj={`${dataTestSubj}-tooltipContent`}>
|
|
||||||
<div>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.securitySolution.endpoint.agentAndActionsStatus.tooltipPendingActions"
|
|
||||||
defaultMessage="Pending actions:"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{actionList.map(({ count, label }) => {
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup gutterSize="none" key={label}>
|
|
||||||
<EuiFlexItem>{label}</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>{count}</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<EuiTextColor color="subdued" data-test-subj={`${dataTestSubj}-pending`}>
|
|
||||||
{badgeDisplayValue}
|
|
||||||
</EuiTextColor>
|
|
||||||
</EuiToolTip>
|
|
||||||
</EuiBadge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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')}>
|
|
||||||
{badgeDisplayValue}
|
|
||||||
</EuiTextColor>
|
|
||||||
</EuiBadge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
EndpointHostResponseActionsStatus.displayName = 'EndpointHostResponseActionsStatus';
|
|
|
@ -7,6 +7,7 @@
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||||
|
import type { Platform } from '../../../management/components/endpoint_responder/components/header_info/platforms';
|
||||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||||
import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check';
|
import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check';
|
||||||
import type { ThirdPartyAgentInfo } from '../../../../common/types';
|
import type { ThirdPartyAgentInfo } from '../../../../common/types';
|
||||||
|
@ -165,6 +166,7 @@ export const useResponderActionData = ({
|
||||||
agentType: 'endpoint',
|
agentType: 'endpoint',
|
||||||
capabilities: (hostInfo.metadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [],
|
capabilities: (hostInfo.metadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [],
|
||||||
hostName: hostInfo.metadata.host.name,
|
hostName: hostInfo.metadata.host.name,
|
||||||
|
platform: hostInfo.metadata.host.os.name.toLowerCase() as Platform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (onClick) onClick();
|
if (onClick) onClick();
|
||||||
|
|
|
@ -14,10 +14,10 @@ import {
|
||||||
useAgentStatusHook,
|
useAgentStatusHook,
|
||||||
useGetAgentStatus,
|
useGetAgentStatus,
|
||||||
useGetSentinelOneAgentStatus,
|
useGetSentinelOneAgentStatus,
|
||||||
} from './use_sentinelone_host_isolation';
|
} from '../../../management/hooks/agents/use_get_agent_status';
|
||||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||||
|
|
||||||
jest.mock('./use_sentinelone_host_isolation');
|
jest.mock('../../../management/hooks/agents/use_get_agent_status');
|
||||||
jest.mock('../../../common/hooks/use_experimental_features');
|
jest.mock('../../../common/hooks/use_experimental_features');
|
||||||
|
|
||||||
type AgentType = 'endpoint' | 'sentinel_one' | 'crowdstrike';
|
type AgentType = 'endpoint' | 'sentinel_one' | 'crowdstrike';
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from './translations';
|
||||||
import { getFieldValue } from './helpers';
|
import { getFieldValue } from './helpers';
|
||||||
import { useUserPrivileges } from '../../../common/components/user_privileges';
|
import { useUserPrivileges } from '../../../common/components/user_privileges';
|
||||||
import type { AlertTableContextMenuItem } from '../alerts_table/types';
|
import type { AlertTableContextMenuItem } from '../alerts_table/types';
|
||||||
import { useAgentStatusHook } from './use_sentinelone_host_isolation';
|
import { useAgentStatusHook } from '../../../management/hooks/agents/use_get_agent_status';
|
||||||
|
|
||||||
interface UseHostIsolationActionProps {
|
interface UseHostIsolationActionProps {
|
||||||
closePopover: () => void;
|
closePopover: () => void;
|
||||||
|
|
|
@ -23,11 +23,11 @@ import {
|
||||||
useAgentStatusHook,
|
useAgentStatusHook,
|
||||||
useGetAgentStatus,
|
useGetAgentStatus,
|
||||||
useGetSentinelOneAgentStatus,
|
useGetSentinelOneAgentStatus,
|
||||||
} from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
|
} from '../../../../management/hooks/agents/use_get_agent_status';
|
||||||
import { type ExpandableFlyoutApi, useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
import { type ExpandableFlyoutApi, useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||||
|
|
||||||
jest.mock('../../../../management/hooks');
|
jest.mock('../../../../management/hooks');
|
||||||
jest.mock('../../../../detections/components/host_isolation/use_sentinelone_host_isolation');
|
jest.mock('../../../../management/hooks/agents/use_get_agent_status');
|
||||||
|
|
||||||
jest.mock('@kbn/expandable-flyout', () => ({
|
jest.mock('@kbn/expandable-flyout', () => ({
|
||||||
useExpandableFlyoutApi: jest.fn(),
|
useExpandableFlyoutApi: jest.fn(),
|
||||||
|
@ -54,9 +54,11 @@ const panelContextValue = {
|
||||||
|
|
||||||
const renderHighlightedFieldsCell = (values: string[], field: string) =>
|
const renderHighlightedFieldsCell = (values: string[], field: string) =>
|
||||||
render(
|
render(
|
||||||
<RightPanelContext.Provider value={panelContextValue}>
|
<TestProviders>
|
||||||
<HighlightedFieldsCell values={values} field={field} />
|
<RightPanelContext.Provider value={panelContextValue}>
|
||||||
</RightPanelContext.Provider>
|
<HighlightedFieldsCell values={values} field={field} />
|
||||||
|
</RightPanelContext.Provider>
|
||||||
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('<HighlightedFieldsCell />', () => {
|
describe('<HighlightedFieldsCell />', () => {
|
||||||
|
@ -65,7 +67,11 @@ describe('<HighlightedFieldsCell />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a basic cell', () => {
|
it('should render a basic cell', () => {
|
||||||
const { getByTestId } = render(<HighlightedFieldsCell values={['value']} field={'field'} />);
|
const { getByTestId } = render(
|
||||||
|
<TestProviders>
|
||||||
|
<HighlightedFieldsCell values={['value']} field={'field'} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
expect(getByTestId(HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID)).toBeInTheDocument();
|
expect(getByTestId(HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -108,7 +114,7 @@ describe('<HighlightedFieldsCell />', () => {
|
||||||
expect(getByTestId(HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID)).toBeInTheDocument();
|
expect(getByTestId(HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: 8.15 simplify when `agentStatusClientEnabled` FF is enabled/removed
|
// TODO: 8.15 simplify when `agentStatusClientEnabled` FF is enabled and removed
|
||||||
it.each(Object.keys(hooksToMock))(
|
it.each(Object.keys(hooksToMock))(
|
||||||
'should render SentinelOne agent status cell if field is agent.status and `origialField` is `observer.serial_number` with %s hook',
|
'should render SentinelOne agent status cell if field is agent.status and `origialField` is `observer.serial_number` with %s hook',
|
||||||
(hookName) => {
|
(hookName) => {
|
||||||
|
@ -135,7 +141,11 @@ describe('<HighlightedFieldsCell />', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
it('should not render if values is null', () => {
|
it('should not render if values is null', () => {
|
||||||
const { container } = render(<HighlightedFieldsCell values={null} field={'field'} />);
|
const { container } = render(
|
||||||
|
<TestProviders>
|
||||||
|
<HighlightedFieldsCell values={null} field={'field'} />
|
||||||
|
</TestProviders>
|
||||||
|
);
|
||||||
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,12 +6,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { VFC } from 'react';
|
import type { VFC } from 'react';
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import { EuiFlexItem, EuiLink } from '@elastic/eui';
|
import { EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||||
import { SentinelOneAgentStatus } from '../../../../detections/components/host_isolation/sentinel_one_agent_status';
|
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||||
import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../common/utils/sentinelone_alert_check';
|
import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../common/utils/sentinelone_alert_check';
|
||||||
import { EndpointAgentStatusById } from '../../../../common/components/endpoint/endpoint_agent_status';
|
import {
|
||||||
|
AgentStatus,
|
||||||
|
EndpointAgentStatusById,
|
||||||
|
} from '../../../../common/components/agents/agent_status';
|
||||||
import { useRightPanelContext } from '../context';
|
import { useRightPanelContext } from '../context';
|
||||||
import {
|
import {
|
||||||
AGENT_STATUS_FIELD_NAME,
|
AGENT_STATUS_FIELD_NAME,
|
||||||
|
@ -77,41 +80,74 @@ export interface HighlightedFieldsCellProps {
|
||||||
values: string[] | null | undefined;
|
values: string[] | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FieldsAgentStatus = memo(
|
||||||
|
({
|
||||||
|
value,
|
||||||
|
isSentinelOneAgentIdField,
|
||||||
|
}: {
|
||||||
|
value: string | undefined;
|
||||||
|
isSentinelOneAgentIdField: boolean;
|
||||||
|
}) => {
|
||||||
|
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||||
|
if (isSentinelOneAgentIdField || agentStatusClientEnabled) {
|
||||||
|
return (
|
||||||
|
<AgentStatus
|
||||||
|
agentId={String(value ?? '')}
|
||||||
|
agentType={isSentinelOneAgentIdField ? 'sentinel_one' : 'endpoint'}
|
||||||
|
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// TODO: remove usage of `EndpointAgentStatusById` when `agentStatusClientEnabled` FF is enabled and removed
|
||||||
|
return (
|
||||||
|
<EndpointAgentStatusById
|
||||||
|
endpointAgentId={String(value ?? '')}
|
||||||
|
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
FieldsAgentStatus.displayName = 'FieldsAgentStatus';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* console.log('c::*, values != null
|
||||||
* Renders a component in the highlighted fields table cell based on the field name
|
* Renders a component in the highlighted fields table cell based on the field name
|
||||||
*/
|
*/
|
||||||
export const HighlightedFieldsCell: VFC<HighlightedFieldsCellProps> = ({
|
export const HighlightedFieldsCell: VFC<HighlightedFieldsCellProps> = ({
|
||||||
values,
|
values,
|
||||||
field,
|
field,
|
||||||
originalField,
|
originalField,
|
||||||
}) => (
|
}) => {
|
||||||
<>
|
const isSentinelOneAgentIdField = useMemo(
|
||||||
{values != null &&
|
() => originalField === SENTINEL_ONE_AGENT_ID_FIELD,
|
||||||
values.map((value, i) => {
|
[originalField]
|
||||||
return (
|
);
|
||||||
<EuiFlexItem
|
|
||||||
grow={false}
|
return (
|
||||||
key={`${i}-${value}`}
|
<>
|
||||||
data-test-subj={`${value}-${HIGHLIGHTED_FIELDS_CELL_TEST_ID}`}
|
{values != null &&
|
||||||
>
|
values.map((value, i) => {
|
||||||
{field === HOST_NAME_FIELD_NAME || field === USER_NAME_FIELD_NAME ? (
|
return (
|
||||||
<LinkFieldCell value={value} />
|
<EuiFlexItem
|
||||||
) : field === AGENT_STATUS_FIELD_NAME &&
|
grow={false}
|
||||||
originalField === SENTINEL_ONE_AGENT_ID_FIELD ? (
|
key={`${i}-${value}`}
|
||||||
<SentinelOneAgentStatus
|
data-test-subj={`${value}-${HIGHLIGHTED_FIELDS_CELL_TEST_ID}`}
|
||||||
agentId={String(value ?? '')}
|
>
|
||||||
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
|
{field === HOST_NAME_FIELD_NAME || field === USER_NAME_FIELD_NAME ? (
|
||||||
/>
|
<LinkFieldCell value={value} />
|
||||||
) : field === AGENT_STATUS_FIELD_NAME ? (
|
) : field === AGENT_STATUS_FIELD_NAME ? (
|
||||||
<EndpointAgentStatusById
|
<FieldsAgentStatus
|
||||||
endpointAgentId={String(value ?? '')}
|
value={value}
|
||||||
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
|
isSentinelOneAgentIdField={isSentinelOneAgentIdField}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span data-test-subj={HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID}>{value}</span>
|
<span data-test-subj={HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID}>{value}</span>
|
||||||
)}
|
)}
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { EuiHealth } from '@elastic/eui';
|
||||||
|
|
||||||
import type { EntityTableRows } from '../../shared/components/entity_table/types';
|
import type { EntityTableRows } from '../../shared/components/entity_table/types';
|
||||||
import type { ObservedEntityData } from '../../shared/components/observed_entity/types';
|
import type { ObservedEntityData } from '../../shared/components/observed_entity/types';
|
||||||
import { EndpointAgentStatus } from '../../../../common/components/endpoint/endpoint_agent_status';
|
import { EndpointAgentStatus } from '../../../../common/components/agents/agent_status';
|
||||||
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
||||||
import type { HostItem } from '../../../../../common/search_strategy';
|
import type { HostItem } from '../../../../../common/search_strategy';
|
||||||
import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy';
|
import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy';
|
||||||
|
|
|
@ -19,7 +19,7 @@ import type { CommandExecutionComponentProps } from '../../console/types';
|
||||||
import { FormattedError } from '../../formatted_error';
|
import { FormattedError } from '../../formatted_error';
|
||||||
import { ConsoleCodeBlock } from '../../console/components/console_code_block';
|
import { ConsoleCodeBlock } from '../../console/components/console_code_block';
|
||||||
import { POLICY_STATUS_TO_TEXT } from '../../../pages/endpoint_hosts/view/host_constants';
|
import { POLICY_STATUS_TO_TEXT } from '../../../pages/endpoint_hosts/view/host_constants';
|
||||||
import { getAgentStatusText } from '../../../../common/components/endpoint/agent_status_text';
|
import { getAgentStatusText } from '../../../../common/components/agents/agent_status_text';
|
||||||
|
|
||||||
export const EndpointStatusActionResult = memo<
|
export const EndpointStatusActionResult = memo<
|
||||||
CommandExecutionComponentProps<
|
CommandExecutionComponentProps<
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||||
|
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||||
|
import { AgentInfo } from './agent_info';
|
||||||
|
import {
|
||||||
|
useAgentStatusHook,
|
||||||
|
useGetAgentStatus,
|
||||||
|
} from '../../../../../hooks/agents/use_get_agent_status';
|
||||||
|
import type { ResponseActionAgentType } from '../../../../../../../common/endpoint/service/response_actions/constants';
|
||||||
|
import { RESPONSE_ACTION_AGENT_TYPE } from '../../../../../../../common/endpoint/service/response_actions/constants';
|
||||||
|
import type { Platform } from '../platforms';
|
||||||
|
import { HostStatus } from '../../../../../../../common/endpoint/types';
|
||||||
|
|
||||||
|
jest.mock('../../../../../hooks/agents/use_get_agent_status');
|
||||||
|
|
||||||
|
const getAgentStatusMock = useGetAgentStatus as jest.Mock;
|
||||||
|
const useAgentStatusHookMock = useAgentStatusHook as jest.Mock;
|
||||||
|
|
||||||
|
describe('Responder header Agent Info', () => {
|
||||||
|
let render: (
|
||||||
|
agentType?: ResponseActionAgentType,
|
||||||
|
platform?: Platform
|
||||||
|
) => ReturnType<AppContextTestRender['render']>;
|
||||||
|
let renderResult: ReturnType<typeof render>;
|
||||||
|
let mockedContext: AppContextTestRender;
|
||||||
|
const agentId = 'agent-id-1234';
|
||||||
|
const baseData = {
|
||||||
|
agentId,
|
||||||
|
found: true,
|
||||||
|
isolated: false,
|
||||||
|
lastSeen: new Date().toISOString(),
|
||||||
|
pendingActions: {},
|
||||||
|
status: HostStatus.HEALTHY,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockedContext = createAppRootMockRenderer();
|
||||||
|
render = (agentType?: ResponseActionAgentType, platform?: Platform) =>
|
||||||
|
(renderResult = mockedContext.render(
|
||||||
|
<AgentInfo
|
||||||
|
agentId={agentId}
|
||||||
|
agentType={agentType || 'endpoint'}
|
||||||
|
hostName={'test-agent'}
|
||||||
|
platform={platform || 'linux'}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
getAgentStatusMock.mockReturnValue({ data: {} });
|
||||||
|
useAgentStatusHookMock.mockImplementation(() => useGetAgentStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(RESPONSE_ACTION_AGENT_TYPE)('`%s` agentType', (agentType) => {
|
||||||
|
it('should show endpoint name', async () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: { ...baseData, agentType, status: HostStatus.OFFLINE },
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
render(agentType);
|
||||||
|
|
||||||
|
const name = await renderResult.findByTestId('responderHeaderHostName');
|
||||||
|
expect(name.textContent).toBe('test-agent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show agent and isolation status', async () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: {
|
||||||
|
...baseData,
|
||||||
|
agentType,
|
||||||
|
status: HostStatus.HEALTHY,
|
||||||
|
pendingActions: { isolate: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
render(agentType);
|
||||||
|
|
||||||
|
const agentStatus = await renderResult.findByTestId(
|
||||||
|
`responderHeader-${agentType}-agentIsolationStatus`
|
||||||
|
);
|
||||||
|
expect(agentStatus.textContent).toBe(`HealthyIsolating`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show last checkin time', async () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: { ...baseData, agentType, status: HostStatus.HEALTHY },
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
render(agentType);
|
||||||
|
|
||||||
|
const lastUpdated = await renderResult.findByTestId('responderHeaderLastSeen');
|
||||||
|
expect(lastUpdated).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show platform icon', async () => {
|
||||||
|
getAgentStatusMock.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
[agentId]: { ...baseData, agentType, status: HostStatus.OFFLINE },
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
});
|
||||||
|
render(agentType);
|
||||||
|
|
||||||
|
const platformIcon = await renderResult.findByTestId('responderHeaderHostPlatformIcon');
|
||||||
|
expect(platformIcon).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { AgentStatus } from '../../../../../../common/components/agents/agent_status';
|
||||||
|
import { useAgentStatusHook } from '../../../../../hooks/agents/use_get_agent_status';
|
||||||
|
import type { ThirdPartyAgentInfo } from '../../../../../../../common/types';
|
||||||
|
import { HeaderAgentInfo } from '../header_agent_info';
|
||||||
|
import type { Platform } from '../platforms';
|
||||||
|
|
||||||
|
interface AgentInfoProps {
|
||||||
|
agentId: ThirdPartyAgentInfo['agent']['id'];
|
||||||
|
agentType: ThirdPartyAgentInfo['agent']['type'];
|
||||||
|
platform: ThirdPartyAgentInfo['host']['os']['family'];
|
||||||
|
hostName: ThirdPartyAgentInfo['host']['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AgentInfo = memo<AgentInfoProps>(({ agentId, platform, hostName, agentType }) => {
|
||||||
|
const getAgentStatus = useAgentStatusHook();
|
||||||
|
const { data } = getAgentStatus([agentId], agentType);
|
||||||
|
const agentStatus = data?.[agentId];
|
||||||
|
const lastCheckin = agentStatus ? agentStatus.lastSeen : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeaderAgentInfo
|
||||||
|
platform={platform.toLowerCase() as Platform}
|
||||||
|
hostName={hostName}
|
||||||
|
lastCheckin={lastCheckin}
|
||||||
|
>
|
||||||
|
<AgentStatus
|
||||||
|
agentId={agentId}
|
||||||
|
agentType={agentType}
|
||||||
|
data-test-subj={`responderHeader-${agentType}-agentIsolationStatus`}
|
||||||
|
/>
|
||||||
|
</HeaderAgentInfo>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
AgentInfo.displayName = 'AgentInfo';
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { EuiSkeletonText } from '@elastic/eui';
|
import { EuiSkeletonText } from '@elastic/eui';
|
||||||
import { EndpointAgentStatus } from '../../../../../../common/components/endpoint/endpoint_agent_status';
|
import { EndpointAgentStatus } from '../../../../../../common/components/agents/agent_status';
|
||||||
import { HeaderAgentInfo } from '../header_agent_info';
|
import { HeaderAgentInfo } from '../header_agent_info';
|
||||||
import { useGetEndpointDetails } from '../../../../../hooks';
|
import { useGetEndpointDetails } from '../../../../../hooks';
|
||||||
import type { Platform } from '../platforms';
|
import type { Platform } from '../platforms';
|
||||||
|
|
|
@ -6,21 +6,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
|
import { AgentStatus } from '../../../../../../common/components/agents/agent_status';
|
||||||
|
import { useAgentStatusHook } from '../../../../../hooks/agents/use_get_agent_status';
|
||||||
import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features';
|
import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features';
|
||||||
import { useAgentStatusHook } from '../../../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
|
|
||||||
import { SentinelOneAgentStatus } from '../../../../../../detections/components/host_isolation/sentinel_one_agent_status';
|
|
||||||
import type { ThirdPartyAgentInfo } from '../../../../../../../common/types';
|
import type { ThirdPartyAgentInfo } from '../../../../../../../common/types';
|
||||||
import { HeaderAgentInfo } from '../header_agent_info';
|
import { HeaderAgentInfo } from '../header_agent_info';
|
||||||
import type { Platform } from '../platforms';
|
import type { Platform } from '../platforms';
|
||||||
|
|
||||||
interface HeaderSentinelOneInfoProps {
|
interface HeaderSentinelOneInfoProps {
|
||||||
agentId: ThirdPartyAgentInfo['agent']['id'];
|
agentId: ThirdPartyAgentInfo['agent']['id'];
|
||||||
|
agentType: ThirdPartyAgentInfo['agent']['type'];
|
||||||
platform: ThirdPartyAgentInfo['host']['os']['family'];
|
platform: ThirdPartyAgentInfo['host']['os']['family'];
|
||||||
hostName: ThirdPartyAgentInfo['host']['name'];
|
hostName: ThirdPartyAgentInfo['host']['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderSentinelOneInfo = memo<HeaderSentinelOneInfoProps>(
|
export const HeaderSentinelOneInfo = memo<HeaderSentinelOneInfoProps>(
|
||||||
({ agentId, platform, hostName }) => {
|
({ agentId, agentType, platform, hostName }) => {
|
||||||
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
||||||
'sentinelOneManualHostActionsEnabled'
|
'sentinelOneManualHostActionsEnabled'
|
||||||
);
|
);
|
||||||
|
@ -35,8 +36,9 @@ export const HeaderSentinelOneInfo = memo<HeaderSentinelOneInfoProps>(
|
||||||
hostName={hostName}
|
hostName={hostName}
|
||||||
lastCheckin={lastCheckin}
|
lastCheckin={lastCheckin}
|
||||||
>
|
>
|
||||||
<SentinelOneAgentStatus
|
<AgentStatus
|
||||||
agentId={agentId}
|
agentId={agentId}
|
||||||
|
agentType={agentType}
|
||||||
data-test-subj="responderHeaderSentinelOneAgentIsolationStatus"
|
data-test-subj="responderHeaderSentinelOneAgentIsolationStatus"
|
||||||
/>
|
/>
|
||||||
</HeaderAgentInfo>
|
</HeaderAgentInfo>
|
||||||
|
|
|
@ -15,13 +15,13 @@ import {
|
||||||
useAgentStatusHook,
|
useAgentStatusHook,
|
||||||
useGetAgentStatus,
|
useGetAgentStatus,
|
||||||
useGetSentinelOneAgentStatus,
|
useGetSentinelOneAgentStatus,
|
||||||
} from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
|
} from '../../../hooks/agents/use_get_agent_status';
|
||||||
import { useGetEndpointDetails } from '../../../hooks/endpoint/use_get_endpoint_details';
|
import { useGetEndpointDetails } from '../../../hooks/endpoint/use_get_endpoint_details';
|
||||||
import { mockEndpointDetailsApiResult } from '../../../pages/endpoint_hosts/store/mock_endpoint_result_list';
|
import { mockEndpointDetailsApiResult } from '../../../pages/endpoint_hosts/store/mock_endpoint_result_list';
|
||||||
import { OfflineCallout } from './offline_callout';
|
import { OfflineCallout } from './offline_callout';
|
||||||
|
|
||||||
jest.mock('../../../hooks/endpoint/use_get_endpoint_details');
|
jest.mock('../../../hooks/endpoint/use_get_endpoint_details');
|
||||||
jest.mock('../../../../detections/components/host_isolation/use_sentinelone_host_isolation');
|
jest.mock('../../../hooks/agents/use_get_agent_status');
|
||||||
|
|
||||||
const getEndpointDetails = useGetEndpointDetails as jest.Mock;
|
const getEndpointDetails = useGetEndpointDetails as jest.Mock;
|
||||||
const getSentinelOneAgentStatus = useGetSentinelOneAgentStatus as jest.Mock;
|
const getSentinelOneAgentStatus = useGetSentinelOneAgentStatus as jest.Mock;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||||
import { useAgentStatusHook } from '../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
|
import { useAgentStatusHook } from '../../../hooks/agents/use_get_agent_status';
|
||||||
import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
|
import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||||
import { useGetEndpointDetails } from '../../../hooks';
|
import { useGetEndpointDetails } from '../../../hooks';
|
||||||
import { HostStatus } from '../../../../../common/endpoint/types';
|
import { HostStatus } from '../../../../../common/endpoint/types';
|
||||||
|
|
|
@ -8,4 +8,5 @@
|
||||||
export { getEndpointConsoleCommands } from './lib/console_commands_definition';
|
export { getEndpointConsoleCommands } from './lib/console_commands_definition';
|
||||||
export { ActionLogButton } from './components/action_log_button';
|
export { ActionLogButton } from './components/action_log_button';
|
||||||
export { HeaderEndpointInfo } from './components/header_info/endpoint/header_endpoint_info';
|
export { HeaderEndpointInfo } from './components/header_info/endpoint/header_endpoint_info';
|
||||||
|
export { AgentInfo } from './components/header_info/agent_info/agent_info';
|
||||||
export { OfflineCallout } from './components/offline_callout';
|
export { OfflineCallout } from './components/offline_callout';
|
||||||
|
|
|
@ -47,6 +47,7 @@ export const useGetSentinelOneAgentStatus = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 8.14, 8.15 used for fetching agent status
|
||||||
export const useGetAgentStatus = (
|
export const useGetAgentStatus = (
|
||||||
agentIds: string[],
|
agentIds: string[],
|
||||||
agentType: string,
|
agentType: string,
|
|
@ -12,7 +12,7 @@ import { useLicense } from '../../common/hooks/use_license';
|
||||||
import type { MaybeImmutable } from '../../../common/endpoint/types';
|
import type { MaybeImmutable } from '../../../common/endpoint/types';
|
||||||
import type { EndpointCapabilities } from '../../../common/endpoint/service/response_actions/constants';
|
import type { EndpointCapabilities } from '../../../common/endpoint/service/response_actions/constants';
|
||||||
import { type ResponseActionAgentType } from '../../../common/endpoint/service/response_actions/constants';
|
import { type ResponseActionAgentType } from '../../../common/endpoint/service/response_actions/constants';
|
||||||
import { HeaderSentinelOneInfo } from '../components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info';
|
import { AgentInfo } from '../components/endpoint_responder/components/header_info/agent_info/agent_info';
|
||||||
|
|
||||||
import { useUserPrivileges } from '../../common/components/user_privileges';
|
import { useUserPrivileges } from '../../common/components/user_privileges';
|
||||||
import {
|
import {
|
||||||
|
@ -33,6 +33,7 @@ export interface BasicConsoleProps {
|
||||||
hostName: string;
|
hostName: string;
|
||||||
/** Required for Endpoint agents. */
|
/** Required for Endpoint agents. */
|
||||||
capabilities: MaybeImmutable<EndpointCapabilities[]>;
|
capabilities: MaybeImmutable<EndpointCapabilities[]>;
|
||||||
|
platform: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponderInfoProps =
|
type ResponderInfoProps =
|
||||||
|
@ -41,7 +42,6 @@ type ResponderInfoProps =
|
||||||
})
|
})
|
||||||
| (BasicConsoleProps & {
|
| (BasicConsoleProps & {
|
||||||
agentType: Exclude<ResponseActionAgentType, 'endpoint'>;
|
agentType: Exclude<ResponseActionAgentType, 'endpoint'>;
|
||||||
platform: string;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
||||||
|
@ -51,10 +51,11 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
||||||
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
||||||
'responseActionsSentinelOneV1Enabled'
|
'responseActionsSentinelOneV1Enabled'
|
||||||
);
|
);
|
||||||
|
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(props: ResponderInfoProps) => {
|
(props: ResponderInfoProps) => {
|
||||||
const { agentId, agentType, capabilities, hostName } = props;
|
const { agentId, agentType, capabilities, hostName, platform } = props;
|
||||||
// If no authz, just exit and log something to the console
|
// If no authz, just exit and log something to the console
|
||||||
if (agentType === 'endpoint' && !endpointPrivileges.canAccessResponseConsole) {
|
if (agentType === 'endpoint' && !endpointPrivileges.canAccessResponseConsole) {
|
||||||
window.console.error(new Error(`Access denied to ${agentType} response actions console`));
|
window.console.error(new Error(`Access denied to ${agentType} response actions console`));
|
||||||
|
@ -81,18 +82,21 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
||||||
'data-test-subj': `${agentType}ResponseActionsConsole`,
|
'data-test-subj': `${agentType}ResponseActionsConsole`,
|
||||||
storagePrefix: 'xpack.securitySolution.Responder',
|
storagePrefix: 'xpack.securitySolution.Responder',
|
||||||
TitleComponent: () => {
|
TitleComponent: () => {
|
||||||
if (agentType === 'endpoint') {
|
if (agentStatusClientEnabled || agentType !== 'endpoint') {
|
||||||
return <HeaderEndpointInfo endpointId={agentId} />;
|
|
||||||
}
|
|
||||||
if (agentType === 'sentinel_one') {
|
|
||||||
return (
|
return (
|
||||||
<HeaderSentinelOneInfo
|
<AgentInfo
|
||||||
agentId={agentId}
|
agentId={agentId}
|
||||||
|
agentType={agentType}
|
||||||
hostName={hostName}
|
hostName={hostName}
|
||||||
platform={props.platform}
|
platform={platform}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// TODO: 8.15 remove this if block when agentStatusClientEnabled is enabled/removed
|
||||||
|
if (agentType === 'endpoint') {
|
||||||
|
return <HeaderEndpointInfo endpointId={agentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -104,6 +108,7 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
||||||
agentId,
|
agentId,
|
||||||
hostName,
|
hostName,
|
||||||
capabilities,
|
capabilities,
|
||||||
|
platform,
|
||||||
},
|
},
|
||||||
consoleProps,
|
consoleProps,
|
||||||
PageTitleComponent: () => {
|
PageTitleComponent: () => {
|
||||||
|
@ -139,6 +144,12 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[endpointPrivileges, isEnterpriseLicense, isSentinelOneV1Enabled, consoleManager]
|
[
|
||||||
|
endpointPrivileges,
|
||||||
|
isEnterpriseLicense,
|
||||||
|
consoleManager,
|
||||||
|
agentStatusClientEnabled,
|
||||||
|
isSentinelOneV1Enabled,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,11 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { EndpointAgentStatus } from '../../../../../common/components/endpoint/endpoint_agent_status';
|
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||||
|
import {
|
||||||
|
AgentStatus,
|
||||||
|
EndpointAgentStatus,
|
||||||
|
} from '../../../../../common/components/agents/agent_status';
|
||||||
import { isPolicyOutOfDate } from '../../utils';
|
import { isPolicyOutOfDate } from '../../utils';
|
||||||
import type { HostInfo } from '../../../../../../common/endpoint/types';
|
import type { HostInfo } from '../../../../../../common/endpoint/types';
|
||||||
import { useEndpointSelector } from '../hooks';
|
import { useEndpointSelector } from '../hooks';
|
||||||
|
@ -54,6 +58,7 @@ interface EndpointDetailsContentProps {
|
||||||
|
|
||||||
export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
||||||
({ hostInfo, policyInfo }) => {
|
({ hostInfo, policyInfo }) => {
|
||||||
|
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||||
const queryParams = useEndpointSelector(uiQueryParams);
|
const queryParams = useEndpointSelector(uiQueryParams);
|
||||||
const policyStatus = useMemo(
|
const policyStatus = useMemo(
|
||||||
() => hostInfo.metadata.Endpoint.policy.applied.status,
|
() => hostInfo.metadata.Endpoint.policy.applied.status,
|
||||||
|
@ -95,7 +100,10 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
||||||
/>
|
/>
|
||||||
</ColumnTitle>
|
</ColumnTitle>
|
||||||
),
|
),
|
||||||
description: (
|
// TODO: 8.15 remove `EndpointAgentStatus` when `agentStatusClientEnabled` FF is enabled and removed
|
||||||
|
description: agentStatusClientEnabled ? (
|
||||||
|
<AgentStatus agentId={hostInfo.metadata.agent.id} agentType="endpoint" />
|
||||||
|
) : (
|
||||||
<EndpointAgentStatus
|
<EndpointAgentStatus
|
||||||
pendingActions={getHostPendingActions(hostInfo.metadata.agent.id)}
|
pendingActions={getHostPendingActions(hostInfo.metadata.agent.id)}
|
||||||
endpointHostInfo={hostInfo}
|
endpointHostInfo={hostInfo}
|
||||||
|
@ -219,6 +227,7 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [
|
}, [
|
||||||
|
agentStatusClientEnabled,
|
||||||
hostInfo,
|
hostInfo,
|
||||||
getHostPendingActions,
|
getHostPendingActions,
|
||||||
missingPolicies,
|
missingPolicies,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { pagePathGetters } from '@kbn/fleet-plugin/public';
|
import { pagePathGetters } from '@kbn/fleet-plugin/public';
|
||||||
|
import type { Platform } from '../../../../components/endpoint_responder/components/header_info/platforms';
|
||||||
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
|
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||||
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||||
import { useWithShowResponder } from '../../../../hooks';
|
import { useWithShowResponder } from '../../../../hooks';
|
||||||
|
@ -136,6 +137,7 @@ export const useEndpointActionItems = (
|
||||||
capabilities:
|
capabilities:
|
||||||
(endpointMetadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [],
|
(endpointMetadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [],
|
||||||
hostName: endpointMetadata.host.name,
|
hostName: endpointMetadata.host.name,
|
||||||
|
platform: endpointMetadata.host.os.name.toLowerCase() as Platform,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
children: (
|
children: (
|
||||||
|
|
|
@ -10,13 +10,13 @@ import styled from 'styled-components';
|
||||||
import type { CriteriaWithPagination } from '@elastic/eui';
|
import type { CriteriaWithPagination } from '@elastic/eui';
|
||||||
import {
|
import {
|
||||||
EuiBasicTable,
|
EuiBasicTable,
|
||||||
EuiEmptyPrompt,
|
|
||||||
EuiLoadingLogo,
|
|
||||||
type EuiBasicTableColumn,
|
type EuiBasicTableColumn,
|
||||||
|
EuiEmptyPrompt,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiHealth,
|
EuiHealth,
|
||||||
EuiHorizontalRule,
|
EuiHorizontalRule,
|
||||||
|
EuiLoadingLogo,
|
||||||
type EuiSelectableProps,
|
type EuiSelectableProps,
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiSuperDatePicker,
|
EuiSuperDatePicker,
|
||||||
|
@ -32,10 +32,14 @@ import type {
|
||||||
AgentPolicyDetailsDeployAgentAction,
|
AgentPolicyDetailsDeployAgentAction,
|
||||||
CreatePackagePolicyRouteState,
|
CreatePackagePolicyRouteState,
|
||||||
} from '@kbn/fleet-plugin/public';
|
} from '@kbn/fleet-plugin/public';
|
||||||
|
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||||
import { TransformFailedCallout } from './components/transform_failed_callout';
|
import { TransformFailedCallout } from './components/transform_failed_callout';
|
||||||
import type { EndpointIndexUIQueryParams } from '../types';
|
import type { EndpointIndexUIQueryParams } from '../types';
|
||||||
import { EndpointListNavLink } from './components/endpoint_list_nav_link';
|
import { EndpointListNavLink } from './components/endpoint_list_nav_link';
|
||||||
import { EndpointAgentStatus } from '../../../../common/components/endpoint/endpoint_agent_status';
|
import {
|
||||||
|
AgentStatus,
|
||||||
|
EndpointAgentStatus,
|
||||||
|
} from '../../../../common/components/agents/agent_status';
|
||||||
import { EndpointDetailsFlyout } from './details';
|
import { EndpointDetailsFlyout } from './details';
|
||||||
import * as selectors from '../store/selectors';
|
import * as selectors from '../store/selectors';
|
||||||
import { getEndpointPendingActionsCallback } from '../store/selectors';
|
import { getEndpointPendingActionsCallback } from '../store/selectors';
|
||||||
|
@ -78,6 +82,7 @@ const StyledDatePicker = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface GetEndpointListColumnsProps {
|
interface GetEndpointListColumnsProps {
|
||||||
|
agentStatusClientEnabled: boolean;
|
||||||
canReadPolicyManagement: boolean;
|
canReadPolicyManagement: boolean;
|
||||||
backToEndpointList: PolicyDetailsRouteState['backLink'];
|
backToEndpointList: PolicyDetailsRouteState['backLink'];
|
||||||
getHostPendingActions: ReturnType<typeof getEndpointPendingActionsCallback>;
|
getHostPendingActions: ReturnType<typeof getEndpointPendingActionsCallback>;
|
||||||
|
@ -102,6 +107,7 @@ const columnWidths: Record<
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEndpointListColumns = ({
|
const getEndpointListColumns = ({
|
||||||
|
agentStatusClientEnabled,
|
||||||
canReadPolicyManagement,
|
canReadPolicyManagement,
|
||||||
backToEndpointList,
|
backToEndpointList,
|
||||||
getHostPendingActions,
|
getHostPendingActions,
|
||||||
|
@ -152,7 +158,10 @@ const getEndpointListColumns = ({
|
||||||
}),
|
}),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (hostStatus: HostInfo['host_status'], endpointInfo) => {
|
render: (hostStatus: HostInfo['host_status'], endpointInfo) => {
|
||||||
return (
|
// TODO: 8.15 remove `EndpointAgentStatus` when `agentStatusClientEnabled` FF is enabled and removed
|
||||||
|
return agentStatusClientEnabled ? (
|
||||||
|
<AgentStatus agentId={endpointInfo.metadata.agent.id} agentType="endpoint" />
|
||||||
|
) : (
|
||||||
<EndpointAgentStatus
|
<EndpointAgentStatus
|
||||||
endpointHostInfo={endpointInfo}
|
endpointHostInfo={endpointInfo}
|
||||||
pendingActions={getHostPendingActions(endpointInfo.metadata.agent.id)}
|
pendingActions={getHostPendingActions(endpointInfo.metadata.agent.id)}
|
||||||
|
@ -341,6 +350,8 @@ const stateHandleDeployEndpointsClick: AgentPolicyDetailsDeployAgentAction = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EndpointList = () => {
|
export const EndpointList = () => {
|
||||||
|
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const {
|
const {
|
||||||
listData,
|
listData,
|
||||||
|
@ -528,6 +539,7 @@ export const EndpointList = () => {
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getEndpointListColumns({
|
getEndpointListColumns({
|
||||||
|
agentStatusClientEnabled,
|
||||||
canReadPolicyManagement,
|
canReadPolicyManagement,
|
||||||
backToEndpointList,
|
backToEndpointList,
|
||||||
getAppUrl,
|
getAppUrl,
|
||||||
|
@ -536,6 +548,7 @@ export const EndpointList = () => {
|
||||||
search,
|
search,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
|
agentStatusClientEnabled,
|
||||||
backToEndpointList,
|
backToEndpointList,
|
||||||
canReadPolicyManagement,
|
canReadPolicyManagement,
|
||||||
getAppUrl,
|
getAppUrl,
|
||||||
|
|
|
@ -9,7 +9,11 @@ import { EuiHealth } from '@elastic/eui';
|
||||||
import { getOr } from 'lodash/fp';
|
import { getOr } from 'lodash/fp';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { EndpointAgentStatus } from '../../../../common/components/endpoint/endpoint_agent_status';
|
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||||
|
import {
|
||||||
|
AgentStatus,
|
||||||
|
EndpointAgentStatus,
|
||||||
|
} from '../../../../common/components/agents/agent_status';
|
||||||
import { OverviewDescriptionList } from '../../../../common/components/overview_description_list';
|
import { OverviewDescriptionList } from '../../../../common/components/overview_description_list';
|
||||||
import type { DescriptionList } from '../../../../../common/utility_types';
|
import type { DescriptionList } from '../../../../../common/utility_types';
|
||||||
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
||||||
|
@ -25,6 +29,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EndpointOverview = React.memo<Props>(({ contextID, data, scopeId }) => {
|
export const EndpointOverview = React.memo<Props>(({ contextID, data, scopeId }) => {
|
||||||
|
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||||
const getDefaultRenderer = useCallback(
|
const getDefaultRenderer = useCallback(
|
||||||
(fieldName: string, fieldData: EndpointFields, attrName: string) => (
|
(fieldName: string, fieldData: EndpointFields, attrName: string) => (
|
||||||
<DefaultFieldRenderer
|
<DefaultFieldRenderer
|
||||||
|
@ -77,18 +82,23 @@ export const EndpointOverview = React.memo<Props>(({ contextID, data, scopeId })
|
||||||
{
|
{
|
||||||
title: i18n.FLEET_AGENT_STATUS,
|
title: i18n.FLEET_AGENT_STATUS,
|
||||||
description:
|
description:
|
||||||
|
// TODO: 8.15 remove `EndpointAgentStatus` when `agentStatusClientEnabled` FF is enabled and removed
|
||||||
data != null && data.hostInfo ? (
|
data != null && data.hostInfo ? (
|
||||||
<EndpointAgentStatus
|
agentStatusClientEnabled ? (
|
||||||
endpointHostInfo={data.hostInfo}
|
<AgentStatus agentId={data.hostInfo.metadata.agent.id} agentType="endpoint" />
|
||||||
data-test-subj="endpointHostAgentStatus"
|
) : (
|
||||||
/>
|
<EndpointAgentStatus
|
||||||
|
endpointHostInfo={data.hostInfo}
|
||||||
|
data-test-subj="endpointHostAgentStatus"
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
getEmptyTagValue()
|
getEmptyTagValue()
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}, [data, getDefaultRenderer]);
|
}, [agentStatusClientEnabled, data, getDefaultRenderer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -13,14 +13,17 @@ import { isEmpty, isNumber } from 'lodash/fp';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||||
import type { BrowserField } from '../../../../../common/containers/source';
|
import type { BrowserField } from '../../../../../common/containers/source';
|
||||||
import {
|
import {
|
||||||
ALERT_HOST_CRITICALITY,
|
ALERT_HOST_CRITICALITY,
|
||||||
ALERT_USER_CRITICALITY,
|
ALERT_USER_CRITICALITY,
|
||||||
} from '../../../../../../common/field_maps/field_names';
|
} from '../../../../../../common/field_maps/field_names';
|
||||||
import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../../common/utils/sentinelone_alert_check';
|
import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../../common/utils/sentinelone_alert_check';
|
||||||
import { SentinelOneAgentStatus } from '../../../../../detections/components/host_isolation/sentinel_one_agent_status';
|
import {
|
||||||
import { EndpointAgentStatusById } from '../../../../../common/components/endpoint/endpoint_agent_status';
|
AgentStatus,
|
||||||
|
EndpointAgentStatusById,
|
||||||
|
} from '../../../../../common/components/agents/agent_status';
|
||||||
import { INDICATOR_REFERENCE } from '../../../../../../common/cti/constants';
|
import { INDICATOR_REFERENCE } from '../../../../../../common/cti/constants';
|
||||||
import { DefaultDraggable } from '../../../../../common/components/draggables';
|
import { DefaultDraggable } from '../../../../../common/components/draggables';
|
||||||
import { Bytes, BYTES_FORMAT } from './bytes';
|
import { Bytes, BYTES_FORMAT } from './bytes';
|
||||||
|
@ -104,6 +107,8 @@ const FormattedFieldValueComponent: React.FC<{
|
||||||
value,
|
value,
|
||||||
linkValue,
|
linkValue,
|
||||||
}) => {
|
}) => {
|
||||||
|
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||||
|
|
||||||
if (isObjectArray || asPlainText) {
|
if (isObjectArray || asPlainText) {
|
||||||
return <span data-test-subj={`formatted-field-${fieldName}`}>{value}</span>;
|
return <span data-test-subj={`formatted-field-${fieldName}`}>{value}</span>;
|
||||||
} else if (fieldType === IP_FIELD_TYPE) {
|
} else if (fieldType === IP_FIELD_TYPE) {
|
||||||
|
@ -273,7 +278,7 @@ const FormattedFieldValueComponent: React.FC<{
|
||||||
fieldName === AGENT_STATUS_FIELD_NAME &&
|
fieldName === AGENT_STATUS_FIELD_NAME &&
|
||||||
fieldFromBrowserField?.name === SENTINEL_ONE_AGENT_ID_FIELD
|
fieldFromBrowserField?.name === SENTINEL_ONE_AGENT_ID_FIELD
|
||||||
) {
|
) {
|
||||||
return <SentinelOneAgentStatus agentId={String(value ?? '')} />;
|
return <AgentStatus agentId={String(value ?? '')} agentType="sentinel_one" />;
|
||||||
} else if (fieldName === ALERT_HOST_CRITICALITY || fieldName === ALERT_USER_CRITICALITY) {
|
} else if (fieldName === ALERT_HOST_CRITICALITY || fieldName === ALERT_USER_CRITICALITY) {
|
||||||
return (
|
return (
|
||||||
<AssetCriticalityLevel
|
<AssetCriticalityLevel
|
||||||
|
@ -287,7 +292,13 @@ const FormattedFieldValueComponent: React.FC<{
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (fieldName === AGENT_STATUS_FIELD_NAME) {
|
} else if (fieldName === AGENT_STATUS_FIELD_NAME) {
|
||||||
return (
|
return agentStatusClientEnabled ? (
|
||||||
|
<AgentStatus
|
||||||
|
agentId={String(value ?? '')}
|
||||||
|
agentType="endpoint"
|
||||||
|
data-test-subj="endpointHostAgentStatus"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<EndpointAgentStatusById
|
<EndpointAgentStatusById
|
||||||
endpointAgentId={String(value ?? '')}
|
endpointAgentId={String(value ?? '')}
|
||||||
data-test-subj="endpointHostAgentStatus"
|
data-test-subj="endpointHostAgentStatus"
|
||||||
|
|
|
@ -33018,7 +33018,6 @@
|
||||||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "Une fois que vous avez activé cette fonctionnalité, vous pouvez obtenir un accès rapide aux scores de risque de {riskEntity} dans cette section. Les données pourront prendre jusqu'à une heure pour être générées après l'activation du module.",
|
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "Une fois que vous avez activé cette fonctionnalité, vous pouvez obtenir un accès rapide aux scores de risque de {riskEntity} dans cette section. Les données pourront prendre jusqu'à une heure pour être générées après l'activation du module.",
|
||||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "Mettre à niveau le score de risque de {riskEntity}",
|
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "Mettre à niveau le score de risque de {riskEntity}",
|
||||||
"xpack.securitySolution.endpoint.actions.unsupported.message": "La version actuelle de l'agent {agentType} ne prend pas en charge {command}. Mettez à niveau votre Elastic Agent via Fleet vers la dernière version pour activer cette action de réponse.",
|
"xpack.securitySolution.endpoint.actions.unsupported.message": "La version actuelle de l'agent {agentType} ne prend pas en charge {command}. Mettez à niveau votre Elastic Agent via Fleet vers la dernière version pour activer cette action de réponse.",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.multiplePendingActions": "{count} {count, plural, one {action} other {actions}} en attente",
|
|
||||||
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "rév. {revNumber}",
|
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "rév. {revNumber}",
|
||||||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {Succès} warning {Avertissement} failure {Échec} other {Inconnu}}",
|
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {Succès} warning {Avertissement} failure {Échec} other {Inconnu}}",
|
||||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "Une erreur s'est produite lors de la tentative de récupération des statistiques d'artefacts : \"{error}\"",
|
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "Une erreur s'est produite lors de la tentative de récupération des statistiques d'artefacts : \"{error}\"",
|
||||||
|
@ -35523,7 +35522,6 @@
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "Isolation",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "Isolation",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "Isolé",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "Isolé",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "Libération",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "Libération",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.tooltipPendingActions": "Actions en attente :",
|
|
||||||
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "Liste noire",
|
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "Liste noire",
|
||||||
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "Liste noire",
|
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "Liste noire",
|
||||||
"xpack.securitySolution.endpoint.details.agentStatus": "Statut de l'agent",
|
"xpack.securitySolution.endpoint.details.agentStatus": "Statut de l'agent",
|
||||||
|
@ -45103,4 +45101,4 @@
|
||||||
"xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet",
|
"xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet",
|
||||||
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32986,7 +32986,6 @@
|
||||||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "この機能を有効化すると、このセクションで{riskEntity}リスクスコアにすばやくアクセスできます。モジュールを有効化した後、データの生成までに1時間かかる場合があります。",
|
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "この機能を有効化すると、このセクションで{riskEntity}リスクスコアにすばやくアクセスできます。モジュールを有効化した後、データの生成までに1時間かかる場合があります。",
|
||||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "{riskEntity}リスクスコアをアップグレード",
|
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "{riskEntity}リスクスコアをアップグレード",
|
||||||
"xpack.securitySolution.endpoint.actions.unsupported.message": "現在のバージョンの{agentType}エージェントは、{command}をサポートしていません。この応答アクションを有効化するには、Fleet経由でElasticエージェントを最新バージョンにアップグレードしてください。",
|
"xpack.securitySolution.endpoint.actions.unsupported.message": "現在のバージョンの{agentType}エージェントは、{command}をサポートしていません。この応答アクションを有効化するには、Fleet経由でElasticエージェントを最新バージョンにアップグレードしてください。",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.multiplePendingActions": "{count} {count, plural, other {個のアクション}}が保留中です",
|
|
||||||
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "rev. {revNumber}",
|
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "rev. {revNumber}",
|
||||||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}",
|
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}",
|
||||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "アーティファクト統計情報の取得中にエラーが発生しました:\"{error}\"",
|
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "アーティファクト統計情報の取得中にエラーが発生しました:\"{error}\"",
|
||||||
|
@ -35492,7 +35491,6 @@
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "分離中",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "分離中",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "分離済み",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "分離済み",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "リリース中",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "リリース中",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.tooltipPendingActions": "保留中のアクション:",
|
|
||||||
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "ブロックリスト",
|
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "ブロックリスト",
|
||||||
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "ブロックリスト",
|
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "ブロックリスト",
|
||||||
"xpack.securitySolution.endpoint.details.agentStatus": "エージェントステータス",
|
"xpack.securitySolution.endpoint.details.agentStatus": "エージェントステータス",
|
||||||
|
@ -45073,4 +45071,4 @@
|
||||||
"xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定",
|
"xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定",
|
||||||
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33029,7 +33029,6 @@
|
||||||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "一旦启用此功能,您将可以在此部分快速访问{riskEntity}风险分数。启用此模板后,可能需要一小时才能生成数据。",
|
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "一旦启用此功能,您将可以在此部分快速访问{riskEntity}风险分数。启用此模板后,可能需要一小时才能生成数据。",
|
||||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "升级{riskEntity}风险分数",
|
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "升级{riskEntity}风险分数",
|
||||||
"xpack.securitySolution.endpoint.actions.unsupported.message": "当前版本的 {agentType} 代理不支持 {command}。通过 Fleet 将您的 Elastic 代理升级到最新版本以启用此响应操作。",
|
"xpack.securitySolution.endpoint.actions.unsupported.message": "当前版本的 {agentType} 代理不支持 {command}。通过 Fleet 将您的 Elastic 代理升级到最新版本以启用此响应操作。",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.multiplePendingActions": "{count} 个{count, plural, other {操作}}待处理",
|
|
||||||
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "修订版 {revNumber}",
|
"xpack.securitySolution.endpoint.details.policy.revisionNumber": "修订版 {revNumber}",
|
||||||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}",
|
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}",
|
||||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "尝试提取项目统计时出错:“{error}”",
|
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "尝试提取项目统计时出错:“{error}”",
|
||||||
|
@ -35535,7 +35534,6 @@
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "正在隔离",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "正在隔离",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "已隔离",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "已隔离",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "正在释放",
|
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "正在释放",
|
||||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.tooltipPendingActions": "未决操作:",
|
|
||||||
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "阻止列表",
|
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "阻止列表",
|
||||||
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "阻止列表",
|
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "阻止列表",
|
||||||
"xpack.securitySolution.endpoint.details.agentStatus": "代理状态",
|
"xpack.securitySolution.endpoint.details.agentStatus": "代理状态",
|
||||||
|
@ -45121,4 +45119,4 @@
|
||||||
"xpack.serverlessObservability.nav.projectSettings": "项目设置",
|
"xpack.serverlessObservability.nav.projectSettings": "项目设置",
|
||||||
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue