mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -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
|
||||
/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/components/agents/ @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/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 React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||
import { getAgentStatusText } from '../../../common/components/endpoint/agent_status_text';
|
||||
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants';
|
||||
import { useAgentStatusHook } from './use_sentinelone_host_isolation';
|
||||
import {
|
||||
ISOLATED_LABEL,
|
||||
ISOLATING_LABEL,
|
||||
RELEASING_LABEL,
|
||||
} from '../../../common/components/endpoint/endpoint_agent_status';
|
||||
import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { EndpointPendingActions } from '../../../../../common/endpoint/types';
|
||||
import { useAgentStatusHook } from '../../../../management/hooks/agents/use_get_agent_status';
|
||||
import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator';
|
||||
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../management/pages/endpoint_hosts/view/host_constants';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
import { getAgentStatusText } from '../agent_status_text';
|
||||
import { AgentResponseActionsStatus } from './agent_response_action_status';
|
||||
|
||||
export enum SENTINEL_ONE_NETWORK_STATUS {
|
||||
CONNECTING = 'connecting',
|
||||
|
@ -31,37 +30,39 @@ const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const SentinelOneAgentStatus = React.memo(
|
||||
({ agentId, 'data-test-subj': dataTestSubj }: { agentId: string; 'data-test-subj'?: string }) => {
|
||||
export const AgentStatus = React.memo(
|
||||
({
|
||||
agentId,
|
||||
agentType,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: {
|
||||
agentId: string;
|
||||
agentType: ResponseActionAgentType;
|
||||
'data-test-subj'?: string;
|
||||
}) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const useAgentStatus = useAgentStatusHook();
|
||||
|
||||
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneManualHostActionsEnabled'
|
||||
);
|
||||
|
||||
const { data, isLoading, isFetched } = useAgentStatus([agentId], 'sentinel_one', {
|
||||
const { data, isLoading, isFetched } = useAgentStatus([agentId], agentType, {
|
||||
enabled: sentinelOneManualHostActionsEnabled,
|
||||
});
|
||||
const agentStatus = data?.[`${agentId}`];
|
||||
const isCurrentlyIsolated = Boolean(agentStatus?.isolated);
|
||||
const pendingActions = agentStatus?.pendingActions;
|
||||
|
||||
const label = useMemo(() => {
|
||||
const currentNetworkStatus = agentStatus?.isolated;
|
||||
const pendingActions = agentStatus?.pendingActions;
|
||||
|
||||
if (pendingActions) {
|
||||
if (pendingActions.isolate > 0) {
|
||||
return ISOLATING_LABEL;
|
||||
}
|
||||
|
||||
if (pendingActions.unisolate > 0) {
|
||||
return RELEASING_LABEL;
|
||||
}
|
||||
const [hasPendingActions, hostPendingActions] = useMemo<
|
||||
[boolean, EndpointPendingActions['pending_actions']]
|
||||
>(() => {
|
||||
if (!pendingActions) {
|
||||
return [false, {}];
|
||||
}
|
||||
|
||||
if (currentNetworkStatus) {
|
||||
return ISOLATED_LABEL;
|
||||
}
|
||||
}, [agentStatus?.isolated, agentStatus?.pendingActions]);
|
||||
return [Object.keys(pendingActions).length > 0, pendingActions];
|
||||
}, [pendingActions]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroupStyled
|
||||
|
@ -75,6 +76,7 @@ export const SentinelOneAgentStatus = React.memo(
|
|||
<EuiBadge
|
||||
color={HOST_STATUS_TO_BADGE_COLOR[agentStatus.status]}
|
||||
className="eui-textTruncate"
|
||||
data-test-subj={getTestId('agentStatus')}
|
||||
>
|
||||
{getAgentStatusText(agentStatus.status)}
|
||||
</EuiBadge>
|
||||
|
@ -82,11 +84,13 @@ export const SentinelOneAgentStatus = React.memo(
|
|||
'-'
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{isFetched && !isLoading && label && (
|
||||
{(isCurrentlyIsolated || hasPendingActions) && (
|
||||
<EuiFlexItem grow={false} className="eui-textTruncate isolation-status">
|
||||
<EuiBadge color="hollow" data-test-subj={dataTestSubj}>
|
||||
<>{label}</>
|
||||
</EuiBadge>
|
||||
<AgentResponseActionsStatus
|
||||
data-test-subj={getTestId('actionStatuses')}
|
||||
isIsolated={isCurrentlyIsolated}
|
||||
pendingActions={hostPendingActions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroupStyled>
|
||||
|
@ -94,4 +98,4 @@ export const SentinelOneAgentStatus = React.memo(
|
|||
}
|
||||
);
|
||||
|
||||
SentinelOneAgentStatus.displayName = 'SentinelOneAgentStatus';
|
||||
AgentStatus.displayName = 'AgentStatus';
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppContextTestRender } from '../../../mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../mock/endpoint';
|
||||
import type { AppContextTestRender } from '../../../../mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../mock/endpoint';
|
||||
import type {
|
||||
EndpointAgentStatusByIdProps,
|
||||
EndpointAgentStatusProps,
|
||||
|
@ -15,18 +15,18 @@ import { EndpointAgentStatus, EndpointAgentStatusById } from './endpoint_agent_s
|
|||
import type {
|
||||
EndpointPendingActions,
|
||||
HostInfoInterface,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { HostStatus } from '../../../../../common/endpoint/types';
|
||||
} from '../../../../../../common/endpoint/types';
|
||||
import { HostStatus } from '../../../../../../common/endpoint/types';
|
||||
import React from 'react';
|
||||
import { EndpointActionGenerator } from '../../../../../common/endpoint/data_generators/endpoint_action_generator';
|
||||
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
|
||||
import { composeHttpHandlerMocks } from '../../../mock/endpoint/http_handler_mock_factory';
|
||||
import type { EndpointMetadataHttpMocksInterface } 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 { responseActionsHttpMocks } from '../../../../management/mocks/response_actions_http_mocks';
|
||||
import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator';
|
||||
import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data';
|
||||
import { composeHttpHandlerMocks } from '../../../../mock/endpoint/http_handler_mock_factory';
|
||||
import type { EndpointMetadataHttpMocksInterface } 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 { responseActionsHttpMocks } from '../../../../../management/mocks/response_actions_http_mocks';
|
||||
import { waitFor, within, fireEvent } from '@testing-library/react';
|
||||
import { getEmptyValue } from '../../empty_value';
|
||||
import { getEmptyValue } from '../../../empty_value';
|
||||
import { clone, set } from 'lodash';
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
export * from './endpoint_agent_status';
|
||||
export type { EndpointAgentStatusProps } from './endpoint_agent_status';
|
||||
export * from './endpoint/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 { useCallback, useMemo } from 'react';
|
||||
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 { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check';
|
||||
import type { ThirdPartyAgentInfo } from '../../../../common/types';
|
||||
|
@ -165,6 +166,7 @@ export const useResponderActionData = ({
|
|||
agentType: 'endpoint',
|
||||
capabilities: (hostInfo.metadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [],
|
||||
hostName: hostInfo.metadata.host.name,
|
||||
platform: hostInfo.metadata.host.os.name.toLowerCase() as Platform,
|
||||
});
|
||||
}
|
||||
if (onClick) onClick();
|
||||
|
|
|
@ -14,10 +14,10 @@ import {
|
|||
useAgentStatusHook,
|
||||
useGetAgentStatus,
|
||||
useGetSentinelOneAgentStatus,
|
||||
} from './use_sentinelone_host_isolation';
|
||||
} from '../../../management/hooks/agents/use_get_agent_status';
|
||||
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');
|
||||
|
||||
type AgentType = 'endpoint' | 'sentinel_one' | 'crowdstrike';
|
||||
|
|
|
@ -25,7 +25,7 @@ import { ISOLATE_HOST, UNISOLATE_HOST } from './translations';
|
|||
import { getFieldValue } from './helpers';
|
||||
import { useUserPrivileges } from '../../../common/components/user_privileges';
|
||||
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 {
|
||||
closePopover: () => void;
|
||||
|
|
|
@ -23,11 +23,11 @@ import {
|
|||
useAgentStatusHook,
|
||||
useGetAgentStatus,
|
||||
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';
|
||||
|
||||
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', () => ({
|
||||
useExpandableFlyoutApi: jest.fn(),
|
||||
|
@ -54,9 +54,11 @@ const panelContextValue = {
|
|||
|
||||
const renderHighlightedFieldsCell = (values: string[], field: string) =>
|
||||
render(
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<HighlightedFieldsCell values={values} field={field} />
|
||||
</RightPanelContext.Provider>
|
||||
<TestProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<HighlightedFieldsCell values={values} field={field} />
|
||||
</RightPanelContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
describe('<HighlightedFieldsCell />', () => {
|
||||
|
@ -65,7 +67,11 @@ describe('<HighlightedFieldsCell />', () => {
|
|||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
@ -108,7 +114,7 @@ describe('<HighlightedFieldsCell />', () => {
|
|||
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))(
|
||||
'should render SentinelOne agent status cell if field is agent.status and `origialField` is `observer.serial_number` with %s hook',
|
||||
(hookName) => {
|
||||
|
@ -135,7 +141,11 @@ describe('<HighlightedFieldsCell />', () => {
|
|||
);
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
*/
|
||||
|
||||
import type { VFC } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
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 { EndpointAgentStatusById } from '../../../../common/components/endpoint/endpoint_agent_status';
|
||||
import {
|
||||
AgentStatus,
|
||||
EndpointAgentStatusById,
|
||||
} from '../../../../common/components/agents/agent_status';
|
||||
import { useRightPanelContext } from '../context';
|
||||
import {
|
||||
AGENT_STATUS_FIELD_NAME,
|
||||
|
@ -77,41 +80,74 @@ export interface HighlightedFieldsCellProps {
|
|||
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
|
||||
*/
|
||||
export const HighlightedFieldsCell: VFC<HighlightedFieldsCellProps> = ({
|
||||
values,
|
||||
field,
|
||||
originalField,
|
||||
}) => (
|
||||
<>
|
||||
{values != null &&
|
||||
values.map((value, i) => {
|
||||
return (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key={`${i}-${value}`}
|
||||
data-test-subj={`${value}-${HIGHLIGHTED_FIELDS_CELL_TEST_ID}`}
|
||||
>
|
||||
{field === HOST_NAME_FIELD_NAME || field === USER_NAME_FIELD_NAME ? (
|
||||
<LinkFieldCell value={value} />
|
||||
) : field === AGENT_STATUS_FIELD_NAME &&
|
||||
originalField === SENTINEL_ONE_AGENT_ID_FIELD ? (
|
||||
<SentinelOneAgentStatus
|
||||
agentId={String(value ?? '')}
|
||||
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
|
||||
/>
|
||||
) : field === AGENT_STATUS_FIELD_NAME ? (
|
||||
<EndpointAgentStatusById
|
||||
endpointAgentId={String(value ?? '')}
|
||||
data-test-subj={HIGHLIGHTED_FIELDS_AGENT_STATUS_CELL_TEST_ID}
|
||||
/>
|
||||
) : (
|
||||
<span data-test-subj={HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID}>{value}</span>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}) => {
|
||||
const isSentinelOneAgentIdField = useMemo(
|
||||
() => originalField === SENTINEL_ONE_AGENT_ID_FIELD,
|
||||
[originalField]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{values != null &&
|
||||
values.map((value, i) => {
|
||||
return (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key={`${i}-${value}`}
|
||||
data-test-subj={`${value}-${HIGHLIGHTED_FIELDS_CELL_TEST_ID}`}
|
||||
>
|
||||
{field === HOST_NAME_FIELD_NAME || field === USER_NAME_FIELD_NAME ? (
|
||||
<LinkFieldCell value={value} />
|
||||
) : field === AGENT_STATUS_FIELD_NAME ? (
|
||||
<FieldsAgentStatus
|
||||
value={value}
|
||||
isSentinelOneAgentIdField={isSentinelOneAgentIdField}
|
||||
/>
|
||||
) : (
|
||||
<span data-test-subj={HIGHLIGHTED_FIELDS_BASIC_CELL_TEST_ID}>{value}</span>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiHealth } from '@elastic/eui';
|
|||
|
||||
import type { EntityTableRows } from '../../shared/components/entity_table/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 type { HostItem } 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 { ConsoleCodeBlock } from '../../console/components/console_code_block';
|
||||
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<
|
||||
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 { 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 { useGetEndpointDetails } from '../../../../../hooks';
|
||||
import type { Platform } from '../platforms';
|
||||
|
|
|
@ -6,21 +6,22 @@
|
|||
*/
|
||||
|
||||
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 { 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 { HeaderAgentInfo } from '../header_agent_info';
|
||||
import type { Platform } from '../platforms';
|
||||
|
||||
interface HeaderSentinelOneInfoProps {
|
||||
agentId: ThirdPartyAgentInfo['agent']['id'];
|
||||
agentType: ThirdPartyAgentInfo['agent']['type'];
|
||||
platform: ThirdPartyAgentInfo['host']['os']['family'];
|
||||
hostName: ThirdPartyAgentInfo['host']['name'];
|
||||
}
|
||||
|
||||
export const HeaderSentinelOneInfo = memo<HeaderSentinelOneInfoProps>(
|
||||
({ agentId, platform, hostName }) => {
|
||||
({ agentId, agentType, platform, hostName }) => {
|
||||
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneManualHostActionsEnabled'
|
||||
);
|
||||
|
@ -35,8 +36,9 @@ export const HeaderSentinelOneInfo = memo<HeaderSentinelOneInfoProps>(
|
|||
hostName={hostName}
|
||||
lastCheckin={lastCheckin}
|
||||
>
|
||||
<SentinelOneAgentStatus
|
||||
<AgentStatus
|
||||
agentId={agentId}
|
||||
agentType={agentType}
|
||||
data-test-subj="responderHeaderSentinelOneAgentIsolationStatus"
|
||||
/>
|
||||
</HeaderAgentInfo>
|
||||
|
|
|
@ -15,13 +15,13 @@ import {
|
|||
useAgentStatusHook,
|
||||
useGetAgentStatus,
|
||||
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 { mockEndpointDetailsApiResult } from '../../../pages/endpoint_hosts/store/mock_endpoint_result_list';
|
||||
import { OfflineCallout } from './offline_callout';
|
||||
|
||||
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 getSentinelOneAgentStatus = useGetSentinelOneAgentStatus as jest.Mock;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
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 { useGetEndpointDetails } from '../../../hooks';
|
||||
import { HostStatus } from '../../../../../common/endpoint/types';
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
export { getEndpointConsoleCommands } from './lib/console_commands_definition';
|
||||
export { ActionLogButton } from './components/action_log_button';
|
||||
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';
|
||||
|
|
|
@ -47,6 +47,7 @@ export const useGetSentinelOneAgentStatus = (
|
|||
});
|
||||
};
|
||||
|
||||
// 8.14, 8.15 used for fetching agent status
|
||||
export const useGetAgentStatus = (
|
||||
agentIds: string[],
|
||||
agentType: string,
|
|
@ -12,7 +12,7 @@ import { useLicense } from '../../common/hooks/use_license';
|
|||
import type { MaybeImmutable } from '../../../common/endpoint/types';
|
||||
import type { EndpointCapabilities } 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 {
|
||||
|
@ -33,6 +33,7 @@ export interface BasicConsoleProps {
|
|||
hostName: string;
|
||||
/** Required for Endpoint agents. */
|
||||
capabilities: MaybeImmutable<EndpointCapabilities[]>;
|
||||
platform: string;
|
||||
}
|
||||
|
||||
type ResponderInfoProps =
|
||||
|
@ -41,7 +42,6 @@ type ResponderInfoProps =
|
|||
})
|
||||
| (BasicConsoleProps & {
|
||||
agentType: Exclude<ResponseActionAgentType, 'endpoint'>;
|
||||
platform: string;
|
||||
});
|
||||
|
||||
export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
||||
|
@ -51,10 +51,11 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
|||
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
||||
'responseActionsSentinelOneV1Enabled'
|
||||
);
|
||||
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||
|
||||
return useCallback(
|
||||
(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 (agentType === 'endpoint' && !endpointPrivileges.canAccessResponseConsole) {
|
||||
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`,
|
||||
storagePrefix: 'xpack.securitySolution.Responder',
|
||||
TitleComponent: () => {
|
||||
if (agentType === 'endpoint') {
|
||||
return <HeaderEndpointInfo endpointId={agentId} />;
|
||||
}
|
||||
if (agentType === 'sentinel_one') {
|
||||
if (agentStatusClientEnabled || agentType !== 'endpoint') {
|
||||
return (
|
||||
<HeaderSentinelOneInfo
|
||||
<AgentInfo
|
||||
agentId={agentId}
|
||||
agentType={agentType}
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
@ -104,6 +108,7 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
|||
agentId,
|
||||
hostName,
|
||||
capabilities,
|
||||
platform,
|
||||
},
|
||||
consoleProps,
|
||||
PageTitleComponent: () => {
|
||||
|
@ -139,6 +144,12 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
|
|||
.show();
|
||||
}
|
||||
},
|
||||
[endpointPrivileges, isEnterpriseLicense, isSentinelOneV1Enabled, consoleManager]
|
||||
[
|
||||
endpointPrivileges,
|
||||
isEnterpriseLicense,
|
||||
consoleManager,
|
||||
agentStatusClientEnabled,
|
||||
isSentinelOneV1Enabled,
|
||||
]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,11 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { memo, useMemo } from '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 type { HostInfo } from '../../../../../../common/endpoint/types';
|
||||
import { useEndpointSelector } from '../hooks';
|
||||
|
@ -54,6 +58,7 @@ interface EndpointDetailsContentProps {
|
|||
|
||||
export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
||||
({ hostInfo, policyInfo }) => {
|
||||
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||
const queryParams = useEndpointSelector(uiQueryParams);
|
||||
const policyStatus = useMemo(
|
||||
() => hostInfo.metadata.Endpoint.policy.applied.status,
|
||||
|
@ -95,7 +100,10 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
|||
/>
|
||||
</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
|
||||
pendingActions={getHostPendingActions(hostInfo.metadata.agent.id)}
|
||||
endpointHostInfo={hostInfo}
|
||||
|
@ -219,6 +227,7 @@ export const EndpointDetailsContent = memo<EndpointDetailsContentProps>(
|
|||
},
|
||||
];
|
||||
}, [
|
||||
agentStatusClientEnabled,
|
||||
hostInfo,
|
||||
getHostPendingActions,
|
||||
missingPolicies,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
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 { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { useWithShowResponder } from '../../../../hooks';
|
||||
|
@ -136,6 +137,7 @@ export const useEndpointActionItems = (
|
|||
capabilities:
|
||||
(endpointMetadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [],
|
||||
hostName: endpointMetadata.host.name,
|
||||
platform: endpointMetadata.host.os.name.toLowerCase() as Platform,
|
||||
});
|
||||
},
|
||||
children: (
|
||||
|
|
|
@ -10,13 +10,13 @@ import styled from 'styled-components';
|
|||
import type { CriteriaWithPagination } from '@elastic/eui';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiEmptyPrompt,
|
||||
EuiLoadingLogo,
|
||||
type EuiBasicTableColumn,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHealth,
|
||||
EuiHorizontalRule,
|
||||
EuiLoadingLogo,
|
||||
type EuiSelectableProps,
|
||||
EuiSpacer,
|
||||
EuiSuperDatePicker,
|
||||
|
@ -32,10 +32,14 @@ import type {
|
|||
AgentPolicyDetailsDeployAgentAction,
|
||||
CreatePackagePolicyRouteState,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { TransformFailedCallout } from './components/transform_failed_callout';
|
||||
import type { EndpointIndexUIQueryParams } from '../types';
|
||||
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 * as selectors from '../store/selectors';
|
||||
import { getEndpointPendingActionsCallback } from '../store/selectors';
|
||||
|
@ -78,6 +82,7 @@ const StyledDatePicker = styled.div`
|
|||
`;
|
||||
|
||||
interface GetEndpointListColumnsProps {
|
||||
agentStatusClientEnabled: boolean;
|
||||
canReadPolicyManagement: boolean;
|
||||
backToEndpointList: PolicyDetailsRouteState['backLink'];
|
||||
getHostPendingActions: ReturnType<typeof getEndpointPendingActionsCallback>;
|
||||
|
@ -102,6 +107,7 @@ const columnWidths: Record<
|
|||
};
|
||||
|
||||
const getEndpointListColumns = ({
|
||||
agentStatusClientEnabled,
|
||||
canReadPolicyManagement,
|
||||
backToEndpointList,
|
||||
getHostPendingActions,
|
||||
|
@ -152,7 +158,10 @@ const getEndpointListColumns = ({
|
|||
}),
|
||||
sortable: true,
|
||||
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
|
||||
endpointHostInfo={endpointInfo}
|
||||
pendingActions={getHostPendingActions(endpointInfo.metadata.agent.id)}
|
||||
|
@ -341,6 +350,8 @@ const stateHandleDeployEndpointsClick: AgentPolicyDetailsDeployAgentAction = {
|
|||
};
|
||||
|
||||
export const EndpointList = () => {
|
||||
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||
|
||||
const history = useHistory();
|
||||
const {
|
||||
listData,
|
||||
|
@ -528,6 +539,7 @@ export const EndpointList = () => {
|
|||
const columns = useMemo(
|
||||
() =>
|
||||
getEndpointListColumns({
|
||||
agentStatusClientEnabled,
|
||||
canReadPolicyManagement,
|
||||
backToEndpointList,
|
||||
getAppUrl,
|
||||
|
@ -536,6 +548,7 @@ export const EndpointList = () => {
|
|||
search,
|
||||
}),
|
||||
[
|
||||
agentStatusClientEnabled,
|
||||
backToEndpointList,
|
||||
canReadPolicyManagement,
|
||||
getAppUrl,
|
||||
|
|
|
@ -9,7 +9,11 @@ import { EuiHealth } from '@elastic/eui';
|
|||
import { getOr } from 'lodash/fp';
|
||||
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 type { DescriptionList } from '../../../../../common/utility_types';
|
||||
import { getEmptyTagValue } from '../../../../common/components/empty_value';
|
||||
|
@ -25,6 +29,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export const EndpointOverview = React.memo<Props>(({ contextID, data, scopeId }) => {
|
||||
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||
const getDefaultRenderer = useCallback(
|
||||
(fieldName: string, fieldData: EndpointFields, attrName: string) => (
|
||||
<DefaultFieldRenderer
|
||||
|
@ -77,18 +82,23 @@ export const EndpointOverview = React.memo<Props>(({ contextID, data, scopeId })
|
|||
{
|
||||
title: i18n.FLEET_AGENT_STATUS,
|
||||
description:
|
||||
// TODO: 8.15 remove `EndpointAgentStatus` when `agentStatusClientEnabled` FF is enabled and removed
|
||||
data != null && data.hostInfo ? (
|
||||
<EndpointAgentStatus
|
||||
endpointHostInfo={data.hostInfo}
|
||||
data-test-subj="endpointHostAgentStatus"
|
||||
/>
|
||||
agentStatusClientEnabled ? (
|
||||
<AgentStatus agentId={data.hostInfo.metadata.agent.id} agentType="endpoint" />
|
||||
) : (
|
||||
<EndpointAgentStatus
|
||||
endpointHostInfo={data.hostInfo}
|
||||
data-test-subj="endpointHostAgentStatus"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
getEmptyTagValue()
|
||||
),
|
||||
},
|
||||
],
|
||||
];
|
||||
}, [data, getDefaultRenderer]);
|
||||
}, [agentStatusClientEnabled, data, getDefaultRenderer]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -13,14 +13,17 @@ import { isEmpty, isNumber } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import type { BrowserField } from '../../../../../common/containers/source';
|
||||
import {
|
||||
ALERT_HOST_CRITICALITY,
|
||||
ALERT_USER_CRITICALITY,
|
||||
} from '../../../../../../common/field_maps/field_names';
|
||||
import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../../common/utils/sentinelone_alert_check';
|
||||
import { SentinelOneAgentStatus } from '../../../../../detections/components/host_isolation/sentinel_one_agent_status';
|
||||
import { EndpointAgentStatusById } from '../../../../../common/components/endpoint/endpoint_agent_status';
|
||||
import {
|
||||
AgentStatus,
|
||||
EndpointAgentStatusById,
|
||||
} from '../../../../../common/components/agents/agent_status';
|
||||
import { INDICATOR_REFERENCE } from '../../../../../../common/cti/constants';
|
||||
import { DefaultDraggable } from '../../../../../common/components/draggables';
|
||||
import { Bytes, BYTES_FORMAT } from './bytes';
|
||||
|
@ -104,6 +107,8 @@ const FormattedFieldValueComponent: React.FC<{
|
|||
value,
|
||||
linkValue,
|
||||
}) => {
|
||||
const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled');
|
||||
|
||||
if (isObjectArray || asPlainText) {
|
||||
return <span data-test-subj={`formatted-field-${fieldName}`}>{value}</span>;
|
||||
} else if (fieldType === IP_FIELD_TYPE) {
|
||||
|
@ -273,7 +278,7 @@ const FormattedFieldValueComponent: React.FC<{
|
|||
fieldName === AGENT_STATUS_FIELD_NAME &&
|
||||
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) {
|
||||
return (
|
||||
<AssetCriticalityLevel
|
||||
|
@ -287,7 +292,13 @@ const FormattedFieldValueComponent: React.FC<{
|
|||
/>
|
||||
);
|
||||
} else if (fieldName === AGENT_STATUS_FIELD_NAME) {
|
||||
return (
|
||||
return agentStatusClientEnabled ? (
|
||||
<AgentStatus
|
||||
agentId={String(value ?? '')}
|
||||
agentType="endpoint"
|
||||
data-test-subj="endpointHostAgentStatus"
|
||||
/>
|
||||
) : (
|
||||
<EndpointAgentStatusById
|
||||
endpointAgentId={String(value ?? '')}
|
||||
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.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.agentAndActionsStatus.multiplePendingActions": "{count} {count, plural, one {action} other {actions}} en attente",
|
||||
"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.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.isolated": "Isolé",
|
||||
"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.blocklists.fleetIntegration.title": "Liste noire",
|
||||
"xpack.securitySolution.endpoint.details.agentStatus": "Statut de l'agent",
|
||||
|
@ -45103,4 +45101,4 @@
|
|||
"xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet",
|
||||
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32986,7 +32986,6 @@
|
|||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "この機能を有効化すると、このセクションで{riskEntity}リスクスコアにすばやくアクセスできます。モジュールを有効化した後、データの生成までに1時間かかる場合があります。",
|
||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "{riskEntity}リスクスコアをアップグレード",
|
||||
"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.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "アーティファクト統計情報の取得中にエラーが発生しました:\"{error}\"",
|
||||
|
@ -35492,7 +35491,6 @@
|
|||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "分離中",
|
||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "分離済み",
|
||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "リリース中",
|
||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.tooltipPendingActions": "保留中のアクション:",
|
||||
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "ブロックリスト",
|
||||
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "ブロックリスト",
|
||||
"xpack.securitySolution.endpoint.details.agentStatus": "エージェントステータス",
|
||||
|
@ -45073,4 +45071,4 @@
|
|||
"xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定",
|
||||
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33029,7 +33029,6 @@
|
|||
"xpack.securitySolution.enableRiskScore.enableRiskScoreDescription": "一旦启用此功能,您将可以在此部分快速访问{riskEntity}风险分数。启用此模板后,可能需要一小时才能生成数据。",
|
||||
"xpack.securitySolution.enableRiskScore.upgradeRiskScore": "升级{riskEntity}风险分数",
|
||||
"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.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.artifactsSummaryError": "尝试提取项目统计时出错:“{error}”",
|
||||
|
@ -35535,7 +35534,6 @@
|
|||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isIsolating": "正在隔离",
|
||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isolated": "已隔离",
|
||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.isUnIsolating": "正在释放",
|
||||
"xpack.securitySolution.endpoint.agentAndActionsStatus.tooltipPendingActions": "未决操作:",
|
||||
"xpack.securitySolution.endpoint.blocklist.fleetIntegration.title": "阻止列表",
|
||||
"xpack.securitySolution.endpoint.blocklists.fleetIntegration.title": "阻止列表",
|
||||
"xpack.securitySolution.endpoint.details.agentStatus": "代理状态",
|
||||
|
@ -45121,4 +45119,4 @@
|
|||
"xpack.serverlessObservability.nav.projectSettings": "项目设置",
|
||||
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue