[Security Solution][Endpoint][Admin] Match endpoint list host status with fleet agent status (#95243)
This commit is contained in:
parent
2aae753c54
commit
4ff9bfd113
|
@ -380,12 +380,12 @@ export enum HostStatus {
|
|||
* Default state of the host when no host information is present or host information cannot
|
||||
* be retrieved. e.g. API error
|
||||
*/
|
||||
ERROR = 'error',
|
||||
UNHEALTHY = 'unhealthy',
|
||||
|
||||
/**
|
||||
* Host is online as indicated by its checkin status during the last checkin window
|
||||
*/
|
||||
ONLINE = 'online',
|
||||
HEALTHY = 'healthy',
|
||||
|
||||
/**
|
||||
* Host is offline as indicated by its checkin status during the last checkin window
|
||||
|
@ -393,9 +393,14 @@ export enum HostStatus {
|
|||
OFFLINE = 'offline',
|
||||
|
||||
/**
|
||||
* Host is unenrolling as indicated by its checkin status during the last checkin window
|
||||
* Host is unenrolling, enrolling or updating as indicated by its checkin status during the last checkin window
|
||||
*/
|
||||
UNENROLLING = 'unenrolling',
|
||||
UPDATING = 'updating',
|
||||
|
||||
/**
|
||||
* Host is inactive as indicated by its checkin status during the last checkin window
|
||||
*/
|
||||
INACTIVE = 'inactive',
|
||||
}
|
||||
|
||||
export enum MetadataQueryStrategyVersions {
|
||||
|
|
|
@ -54,7 +54,7 @@ export const mockEndpointResultList: (options?: {
|
|||
for (let index = 0; index < actualCountToReturn; index++) {
|
||||
hosts.push({
|
||||
metadata: generator.generateHostMetadata(),
|
||||
host_status: HostStatus.ERROR,
|
||||
host_status: HostStatus.UNHEALTHY,
|
||||
query_strategy_version: queryStrategyVersion,
|
||||
});
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export const mockEndpointResultList: (options?: {
|
|||
export const mockEndpointDetailsApiResult = (): HostInfo => {
|
||||
return {
|
||||
metadata: generator.generateHostMetadata(),
|
||||
host_status: HostStatus.ERROR,
|
||||
host_status: HostStatus.UNHEALTHY,
|
||||
query_strategy_version: MetadataQueryStrategyVersions.VERSION_2,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -231,7 +231,7 @@ export const showView: (state: EndpointState) => 'policy_response' | 'details' =
|
|||
export const hostStatusInfo: (state: Immutable<EndpointState>) => HostStatus = createSelector(
|
||||
(state) => state.hostStatus,
|
||||
(hostStatus) => {
|
||||
return hostStatus ? hostStatus : HostStatus.ERROR;
|
||||
return hostStatus ? hostStatus : HostStatus.UNHEALTHY;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import styled from 'styled-components';
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiHealth,
|
||||
EuiHorizontalRule,
|
||||
EuiListGroup,
|
||||
EuiListGroupItem,
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBadge,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -26,11 +26,7 @@ import { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/end
|
|||
import { useEndpointSelector, useAgentDetailsIngestUrl } from '../hooks';
|
||||
import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
|
||||
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
|
||||
import {
|
||||
POLICY_STATUS_TO_HEALTH_COLOR,
|
||||
POLICY_STATUS_TO_BADGE_COLOR,
|
||||
HOST_STATUS_TO_HEALTH_COLOR,
|
||||
} from '../host_constants';
|
||||
import { POLICY_STATUS_TO_BADGE_COLOR, HOST_STATUS_TO_BADGE_COLOR } from '../host_constants';
|
||||
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
|
||||
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
|
||||
|
@ -48,17 +44,6 @@ const HostIds = styled(EuiListGroupItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
const LinkToExternalApp = styled.div`
|
||||
margin-top: ${(props) => props.theme.eui.ruleMargins.marginMedium};
|
||||
.linkToAppIcon {
|
||||
margin-right: ${(props) => props.theme.eui.ruleMargins.marginXSmall};
|
||||
vertical-align: top;
|
||||
}
|
||||
.linkToAppPopoutIcon {
|
||||
margin-left: ${(props) => props.theme.eui.ruleMargins.marginXSmall};
|
||||
}
|
||||
`;
|
||||
|
||||
const openReassignFlyoutSearch = '?openReassignFlyout=true';
|
||||
|
||||
export const EndpointDetails = memo(
|
||||
|
@ -80,7 +65,7 @@ export const EndpointDetails = memo(
|
|||
const queryParams = useEndpointSelector(uiQueryParams);
|
||||
const policyStatus = useEndpointSelector(
|
||||
policyResponseStatus
|
||||
) as keyof typeof POLICY_STATUS_TO_HEALTH_COLOR;
|
||||
) as keyof typeof POLICY_STATUS_TO_BADGE_COLOR;
|
||||
const { formatUrl } = useFormatUrl(SecurityPageName.administration);
|
||||
|
||||
const detailsResultsUpper = useMemo(() => {
|
||||
|
@ -89,32 +74,37 @@ export const EndpointDetails = memo(
|
|||
title: i18n.translate('xpack.securitySolution.endpoint.details.os', {
|
||||
defaultMessage: 'OS',
|
||||
}),
|
||||
description: details.host.os.full,
|
||||
description: <EuiText>{details.host.os.full}</EuiText>,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.details.agentStatus', {
|
||||
defaultMessage: 'Agent Status',
|
||||
}),
|
||||
description: (
|
||||
<EuiHealth
|
||||
color={HOST_STATUS_TO_HEALTH_COLOR[hostStatus]}
|
||||
data-test-subj="agentStatusHealth"
|
||||
<EuiBadge
|
||||
color={HOST_STATUS_TO_BADGE_COLOR[hostStatus] || 'warning'}
|
||||
data-test-subj="hostDetailsAgentStatusBadge"
|
||||
>
|
||||
<EuiText size="m">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.list.hostStatusValue"
|
||||
defaultMessage="{hostStatus, select, online {Online} error {Error} unenrolling {Unenrolling} other {Offline}}"
|
||||
defaultMessage="{hostStatus, select, healthy {Healthy} unhealthy {Unhealthy} updating {Updating} offline {Offline} inactive {Inactive} other {Unhealthy}}"
|
||||
values={{ hostStatus }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiHealth>
|
||||
</EuiBadge>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.details.lastSeen', {
|
||||
defaultMessage: 'Last Seen',
|
||||
}),
|
||||
description: <FormattedDateAndTime date={new Date(details['@timestamp'])} />,
|
||||
description: (
|
||||
<EuiText>
|
||||
{' '}
|
||||
<FormattedDateAndTime date={new Date(details['@timestamp'])} />
|
||||
</EuiText>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [details, hostStatus]);
|
||||
|
@ -169,12 +159,14 @@ export const EndpointDetails = memo(
|
|||
description: (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EndpointPolicyLink
|
||||
policyId={details.Endpoint.policy.applied.id}
|
||||
data-test-subj="policyDetailsValue"
|
||||
>
|
||||
{details.Endpoint.policy.applied.name}
|
||||
</EndpointPolicyLink>
|
||||
<EuiText>
|
||||
<EndpointPolicyLink
|
||||
policyId={details.Endpoint.policy.applied.id}
|
||||
data-test-subj="policyDetailsValue"
|
||||
>
|
||||
{details.Endpoint.policy.applied.name}
|
||||
</EndpointPolicyLink>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="baseline">
|
||||
{details.Endpoint.policy.applied.endpoint_policy_version && (
|
||||
|
@ -241,9 +233,11 @@ export const EndpointDetails = memo(
|
|||
}),
|
||||
description: (
|
||||
<EuiListGroup flush>
|
||||
{details.host.ip.map((ip: string, index: number) => (
|
||||
<HostIds key={index} label={ip} />
|
||||
))}
|
||||
<EuiText size="xs">
|
||||
{details.host.ip.map((ip: string, index: number) => (
|
||||
<HostIds key={index} label={ip} />
|
||||
))}
|
||||
</EuiText>
|
||||
</EuiListGroup>
|
||||
),
|
||||
},
|
||||
|
@ -251,13 +245,13 @@ export const EndpointDetails = memo(
|
|||
title: i18n.translate('xpack.securitySolution.endpoint.details.hostname', {
|
||||
defaultMessage: 'Hostname',
|
||||
}),
|
||||
description: details.host.hostname,
|
||||
description: <EuiText>{details.host.hostname}</EuiText>,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.securitySolution.endpoint.details.endpointVersion', {
|
||||
defaultMessage: 'Endpoint Version',
|
||||
}),
|
||||
description: details.agent.version,
|
||||
description: <EuiText>{details.agent.version}</EuiText>,
|
||||
},
|
||||
];
|
||||
}, [details.agent.version, details.host.hostname, details.host.ip]);
|
||||
|
@ -275,22 +269,36 @@ export const EndpointDetails = memo(
|
|||
listItems={detailsResultsPolicy}
|
||||
data-test-subj="endpointDetailsPolicyList"
|
||||
/>
|
||||
<LinkToExternalApp>
|
||||
<LinkToApp
|
||||
appId={ingestAppId}
|
||||
appPath={agentDetailsWithFlyoutPath}
|
||||
href={agentDetailsWithFlyoutUrl}
|
||||
onClick={handleReassignEndpointsClick}
|
||||
data-test-subj="endpointDetailsLinkToIngest"
|
||||
<EuiSpacer size="m" />
|
||||
<LinkToApp
|
||||
appId={ingestAppId}
|
||||
appPath={agentDetailsWithFlyoutPath}
|
||||
href={agentDetailsWithFlyoutUrl}
|
||||
onClick={handleReassignEndpointsClick}
|
||||
data-test-subj="endpointDetailsLinkToIngest"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="xs"
|
||||
justifyContent="flexStart"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiIcon type="managementApp" className="linkToAppIcon" />
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.details.linkToIngestTitle"
|
||||
defaultMessage="Reassign Policy"
|
||||
/>
|
||||
<EuiIcon type="popout" className="linkToAppPopoutIcon" />
|
||||
</LinkToApp>
|
||||
</LinkToExternalApp>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="managementApp" className="linkToAppIcon" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.details.linkToIngestTitle"
|
||||
defaultMessage="Reassign Policy"
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="popout" className="linkToAppPopoutIcon" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</LinkToApp>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
|
|
|
@ -8,24 +8,25 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { HostStatus, HostPolicyResponseActionStatus } from '../../../../../common/endpoint/types';
|
||||
|
||||
export const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze<
|
||||
export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze<
|
||||
{
|
||||
[key in HostStatus]: string;
|
||||
}
|
||||
>({
|
||||
[HostStatus.ERROR]: 'danger',
|
||||
[HostStatus.ONLINE]: 'success',
|
||||
[HostStatus.OFFLINE]: 'subdued',
|
||||
[HostStatus.UNENROLLING]: 'warning',
|
||||
[HostStatus.HEALTHY]: 'secondary',
|
||||
[HostStatus.UNHEALTHY]: 'warning',
|
||||
[HostStatus.UPDATING]: 'primary',
|
||||
[HostStatus.OFFLINE]: 'default',
|
||||
[HostStatus.INACTIVE]: 'default',
|
||||
});
|
||||
|
||||
export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
|
||||
{ [key in keyof typeof HostPolicyResponseActionStatus]: string }
|
||||
>({
|
||||
success: 'success',
|
||||
success: 'secondary',
|
||||
warning: 'warning',
|
||||
failure: 'danger',
|
||||
unsupported: 'subdued',
|
||||
unsupported: 'default',
|
||||
});
|
||||
|
||||
export const POLICY_STATUS_TO_BADGE_COLOR = Object.freeze<
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
MetadataQueryStrategyVersions,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
|
||||
import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants';
|
||||
import { POLICY_STATUS_TO_TEXT } from './host_constants';
|
||||
import { mockPolicyResultList } from '../../policy/store/test_mock_utils';
|
||||
|
||||
// not sure why this can't be imported from '../../../../common/mock/formatted_relative';
|
||||
|
@ -232,9 +232,10 @@ describe('when on the list page', () => {
|
|||
> = [];
|
||||
let firstPolicyID: string;
|
||||
let firstPolicyRev: number;
|
||||
|
||||
beforeEach(() => {
|
||||
reactTestingLibrary.act(() => {
|
||||
const mockedEndpointData = mockEndpointResultList({ total: 4 });
|
||||
const mockedEndpointData = mockEndpointResultList({ total: 5 });
|
||||
const hostListData = mockedEndpointData.hosts;
|
||||
const queryStrategyVersion = mockedEndpointData.query_strategy_version;
|
||||
|
||||
|
@ -259,9 +260,9 @@ describe('when on the list page', () => {
|
|||
};
|
||||
|
||||
[
|
||||
{ status: HostStatus.ERROR, policy: (p: Policy) => p },
|
||||
{ status: HostStatus.UNHEALTHY, policy: (p: Policy) => p },
|
||||
{
|
||||
status: HostStatus.ONLINE,
|
||||
status: HostStatus.HEALTHY,
|
||||
policy: (p: Policy) => {
|
||||
p.endpoint.id = 'xyz'; // represents change in endpoint policy assignment
|
||||
p.endpoint.revision = 1;
|
||||
|
@ -276,7 +277,14 @@ describe('when on the list page', () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
status: HostStatus.UNENROLLING,
|
||||
status: HostStatus.UPDATING,
|
||||
policy: (p: Policy) => {
|
||||
p.agent.configured.revision += 1; // agent policy change, not propagated to agent yet
|
||||
return p;
|
||||
},
|
||||
},
|
||||
{
|
||||
status: HostStatus.INACTIVE,
|
||||
policy: (p: Policy) => {
|
||||
p.agent.configured.revision += 1; // agent policy change, not propagated to agent yet
|
||||
return p;
|
||||
|
@ -317,7 +325,7 @@ describe('when on the list page', () => {
|
|||
await middlewareSpy.waitForAction('serverReturnedEndpointList');
|
||||
});
|
||||
const rows = await renderResult.findAllByRole('row');
|
||||
expect(rows).toHaveLength(5);
|
||||
expect(rows).toHaveLength(6);
|
||||
});
|
||||
it('should show total', async () => {
|
||||
const renderResult = render();
|
||||
|
@ -325,7 +333,7 @@ describe('when on the list page', () => {
|
|||
await middlewareSpy.waitForAction('serverReturnedEndpointList');
|
||||
});
|
||||
const total = await renderResult.findByTestId('endpointListTableTotal');
|
||||
expect(total.textContent).toEqual('4 Hosts');
|
||||
expect(total.textContent).toEqual('5 Hosts');
|
||||
});
|
||||
it('should display correct status', async () => {
|
||||
const renderResult = render();
|
||||
|
@ -334,23 +342,30 @@ describe('when on the list page', () => {
|
|||
});
|
||||
const hostStatuses = await renderResult.findAllByTestId('rowHostStatus');
|
||||
|
||||
expect(hostStatuses[0].textContent).toEqual('Error');
|
||||
expect(hostStatuses[0].querySelector('[data-euiicon-type][color="danger"]')).not.toBeNull();
|
||||
expect(hostStatuses[0].textContent).toEqual('Unhealthy');
|
||||
expect(hostStatuses[0].getAttribute('style')).toMatch(
|
||||
/background-color\: rgb\(241\, 216\, 111\)\;/
|
||||
);
|
||||
|
||||
expect(hostStatuses[1].textContent).toEqual('Online');
|
||||
expect(
|
||||
hostStatuses[1].querySelector('[data-euiicon-type][color="success"]')
|
||||
).not.toBeNull();
|
||||
expect(hostStatuses[1].textContent).toEqual('Healthy');
|
||||
expect(hostStatuses[1].getAttribute('style')).toMatch(
|
||||
/background-color\: rgb\(109\, 204\, 177\)\;/
|
||||
);
|
||||
|
||||
expect(hostStatuses[2].textContent).toEqual('Offline');
|
||||
expect(
|
||||
hostStatuses[2].querySelector('[data-euiicon-type][color="subdued"]')
|
||||
).not.toBeNull();
|
||||
expect(hostStatuses[2].getAttribute('style')).toMatch(
|
||||
/background-color\: rgb\(211\, 218\, 230\)\;/
|
||||
);
|
||||
|
||||
expect(hostStatuses[3].textContent).toEqual('Unenrolling');
|
||||
expect(
|
||||
hostStatuses[3].querySelector('[data-euiicon-type][color="warning"]')
|
||||
).not.toBeNull();
|
||||
expect(hostStatuses[3].textContent).toEqual('Updating');
|
||||
expect(hostStatuses[3].getAttribute('style')).toMatch(
|
||||
/background-color\: rgb\(121\, 170\, 217\)\;/
|
||||
);
|
||||
|
||||
expect(hostStatuses[4].textContent).toEqual('Inactive');
|
||||
expect(hostStatuses[4].getAttribute('style')).toMatch(
|
||||
/background-color\: rgb\(211\, 218\, 230\)\;/
|
||||
);
|
||||
});
|
||||
|
||||
it('should display correct policy status', async () => {
|
||||
|
@ -361,14 +376,18 @@ describe('when on the list page', () => {
|
|||
const policyStatuses = await renderResult.findAllByTestId('rowPolicyStatus');
|
||||
|
||||
policyStatuses.forEach((status, index) => {
|
||||
const policyStatusToRGBColor: Array<[string, string]> = [
|
||||
['Success', 'background-color: rgb(109, 204, 177);'],
|
||||
['Warning', 'background-color: rgb(241, 216, 111);'],
|
||||
['Failure', 'background-color: rgb(255, 126, 98);'],
|
||||
['Unsupported', 'background-color: rgb(211, 218, 230);'],
|
||||
];
|
||||
const policyStatusStyleMap: ReadonlyMap<string, string> = new Map<string, string>(
|
||||
policyStatusToRGBColor
|
||||
);
|
||||
const expectedStatusColor: string = policyStatusStyleMap.get(status.textContent!) ?? '';
|
||||
expect(status.textContent).toEqual(POLICY_STATUS_TO_TEXT[generatedPolicyStatuses[index]]);
|
||||
expect(
|
||||
status.querySelector(
|
||||
`[data-euiicon-type][color=${
|
||||
POLICY_STATUS_TO_HEALTH_COLOR[generatedPolicyStatuses[index]]
|
||||
}]`
|
||||
)
|
||||
).not.toBeNull();
|
||||
expect(status.getAttribute('style')).toMatch(expectedStatusColor);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -378,7 +397,7 @@ describe('when on the list page', () => {
|
|||
await middlewareSpy.waitForAction('serverReturnedEndpointList');
|
||||
});
|
||||
const outOfDates = await renderResult.findAllByTestId('rowPolicyOutOfDate');
|
||||
expect(outOfDates).toHaveLength(3);
|
||||
expect(outOfDates).toHaveLength(4);
|
||||
|
||||
outOfDates.forEach((item, index) => {
|
||||
expect(item.textContent).toEqual('Out-of-date');
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useCallback, memo, useState } from 'react';
|
||||
import React, { useMemo, useCallback, memo, useState, useContext } from 'react';
|
||||
import {
|
||||
EuiHorizontalRule,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
EuiHealth,
|
||||
EuiBadge,
|
||||
EuiToolTip,
|
||||
EuiSelectableProps,
|
||||
EuiSuperDatePicker,
|
||||
|
@ -33,13 +33,14 @@ import { createStructuredSelector } from 'reselect';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { EuiContextMenuItemProps } from '@elastic/eui/src/components/context_menu/context_menu_item';
|
||||
import { NavigateToAppOptions } from 'kibana/public';
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import { EndpointDetailsFlyout } from './details';
|
||||
import * as selectors from '../store/selectors';
|
||||
import { useEndpointSelector } from './hooks';
|
||||
import { isPolicyOutOfDate } from '../utils';
|
||||
import {
|
||||
HOST_STATUS_TO_HEALTH_COLOR,
|
||||
POLICY_STATUS_TO_HEALTH_COLOR,
|
||||
HOST_STATUS_TO_BADGE_COLOR,
|
||||
POLICY_STATUS_TO_BADGE_COLOR,
|
||||
POLICY_STATUS_TO_TEXT,
|
||||
} from './host_constants';
|
||||
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
|
||||
|
@ -72,11 +73,24 @@ const EndpointListNavLink = memo<{
|
|||
name: string;
|
||||
href: string;
|
||||
route: string;
|
||||
isBadge?: boolean;
|
||||
dataTestSubj: string;
|
||||
}>(({ name, href, route, dataTestSubj }) => {
|
||||
}>(({ name, href, route, isBadge = false, dataTestSubj }) => {
|
||||
const clickHandler = useNavigateByRouterEventHandler(route);
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
return isBadge ? (
|
||||
// eslint-disable-next-line @elastic/eui/href-or-on-click
|
||||
<EuiLink
|
||||
data-test-subj={dataTestSubj}
|
||||
className="eui-textTruncate"
|
||||
href={href}
|
||||
onClick={clickHandler}
|
||||
style={{ color: theme.eui.euiColorInk }}
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
) : (
|
||||
// eslint-disable-next-line @elastic/eui/href-or-on-click
|
||||
<EuiLink
|
||||
data-test-subj={dataTestSubj}
|
||||
|
@ -306,17 +320,17 @@ export const EndpointList = () => {
|
|||
// eslint-disable-next-line react/display-name
|
||||
render: (hostStatus: HostInfo['host_status']) => {
|
||||
return (
|
||||
<EuiHealth
|
||||
color={HOST_STATUS_TO_HEALTH_COLOR[hostStatus]}
|
||||
<EuiBadge
|
||||
color={HOST_STATUS_TO_BADGE_COLOR[hostStatus] || 'warning'}
|
||||
data-test-subj="rowHostStatus"
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.list.hostStatusValue"
|
||||
defaultMessage="{hostStatus, select, online {Online} error {Error} unenrolling {Unenrolling} other {Offline}}"
|
||||
defaultMessage="{hostStatus, select, healthy {Healthy} unhealthy {Unhealthy} updating {Updating} offline {Offline} inactive {Inactive} other {Unhealthy}}"
|
||||
values={{ hostStatus }}
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiBadge>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -375,8 +389,8 @@ export const EndpointList = () => {
|
|||
});
|
||||
const toRouteUrl = formatUrl(toRoutePath);
|
||||
return (
|
||||
<EuiHealth
|
||||
color={POLICY_STATUS_TO_HEALTH_COLOR[policy.status]}
|
||||
<EuiBadge
|
||||
color={POLICY_STATUS_TO_BADGE_COLOR[policy.status]}
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="rowPolicyStatus"
|
||||
>
|
||||
|
@ -384,9 +398,10 @@ export const EndpointList = () => {
|
|||
name={POLICY_STATUS_TO_TEXT[policy.status]}
|
||||
href={toRouteUrl}
|
||||
route={toRoutePath}
|
||||
isBadge
|
||||
dataTestSubj="policyStatusCellLink"
|
||||
/>
|
||||
</EuiHealth>
|
||||
</EuiBadge>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('test document enrichment', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return host online for online agent', async () => {
|
||||
it('should return host healthy for online agent', async () => {
|
||||
statusFn.mockImplementation(() => 'online');
|
||||
|
||||
const enrichedHostList = await enrichHostMetadata(
|
||||
|
@ -62,7 +62,7 @@ describe('test document enrichment', () => {
|
|||
metaReqCtx,
|
||||
MetadataQueryStrategyVersions.VERSION_2
|
||||
);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.ONLINE);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.HEALTHY);
|
||||
});
|
||||
|
||||
it('should return host offline for offline agent', async () => {
|
||||
|
@ -76,7 +76,7 @@ describe('test document enrichment', () => {
|
|||
expect(enrichedHostList.host_status).toEqual(HostStatus.OFFLINE);
|
||||
});
|
||||
|
||||
it('should return host unenrolling for unenrolling agent', async () => {
|
||||
it('should return host updating for unenrolling agent', async () => {
|
||||
statusFn.mockImplementation(() => 'unenrolling');
|
||||
|
||||
const enrichedHostList = await enrichHostMetadata(
|
||||
|
@ -84,10 +84,10 @@ describe('test document enrichment', () => {
|
|||
metaReqCtx,
|
||||
MetadataQueryStrategyVersions.VERSION_2
|
||||
);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.UNENROLLING);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.UPDATING);
|
||||
});
|
||||
|
||||
it('should return host error for degraded agent', async () => {
|
||||
it('should return host unhealthy for degraded agent', async () => {
|
||||
statusFn.mockImplementation(() => 'degraded');
|
||||
|
||||
const enrichedHostList = await enrichHostMetadata(
|
||||
|
@ -95,10 +95,10 @@ describe('test document enrichment', () => {
|
|||
metaReqCtx,
|
||||
MetadataQueryStrategyVersions.VERSION_2
|
||||
);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should return host error for erroring agent', async () => {
|
||||
it('should return host unhealthy for erroring agent', async () => {
|
||||
statusFn.mockImplementation(() => 'error');
|
||||
|
||||
const enrichedHostList = await enrichHostMetadata(
|
||||
|
@ -106,10 +106,10 @@ describe('test document enrichment', () => {
|
|||
metaReqCtx,
|
||||
MetadataQueryStrategyVersions.VERSION_2
|
||||
);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should return host error for warning agent', async () => {
|
||||
it('should return host unhealthy for warning agent', async () => {
|
||||
statusFn.mockImplementation(() => 'warning');
|
||||
|
||||
const enrichedHostList = await enrichHostMetadata(
|
||||
|
@ -117,10 +117,10 @@ describe('test document enrichment', () => {
|
|||
metaReqCtx,
|
||||
MetadataQueryStrategyVersions.VERSION_2
|
||||
);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should return host error for invalid agent', async () => {
|
||||
it('should return host unhealthy for invalid agent', async () => {
|
||||
statusFn.mockImplementation(() => 'asliduasofb');
|
||||
|
||||
const enrichedHostList = await enrichHostMetadata(
|
||||
|
@ -128,7 +128,7 @@ describe('test document enrichment', () => {
|
|||
metaReqCtx,
|
||||
MetadataQueryStrategyVersions.VERSION_2
|
||||
);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -33,9 +33,15 @@ export interface MetadataRequestContext {
|
|||
}
|
||||
|
||||
const HOST_STATUS_MAPPING = new Map<AgentStatus, HostStatus>([
|
||||
['online', HostStatus.ONLINE],
|
||||
['online', HostStatus.HEALTHY],
|
||||
['offline', HostStatus.OFFLINE],
|
||||
['unenrolling', HostStatus.UNENROLLING],
|
||||
['inactive', HostStatus.INACTIVE],
|
||||
['unenrolling', HostStatus.UPDATING],
|
||||
['enrolling', HostStatus.UPDATING],
|
||||
['updating', HostStatus.UPDATING],
|
||||
['warning', HostStatus.UNHEALTHY],
|
||||
['error', HostStatus.UNHEALTHY],
|
||||
['degraded', HostStatus.UNHEALTHY],
|
||||
]);
|
||||
|
||||
/**
|
||||
|
@ -257,7 +263,7 @@ export async function enrichHostMetadata(
|
|||
metadataRequestContext: MetadataRequestContext,
|
||||
metadataQueryStrategyVersion: MetadataQueryStrategyVersions
|
||||
): Promise<HostInfo> {
|
||||
let hostStatus = HostStatus.ERROR;
|
||||
let hostStatus = HostStatus.UNHEALTHY;
|
||||
let elasticAgentId = hostMetadata?.elastic?.agent?.id;
|
||||
const log = metadataRequestContext.logger;
|
||||
try {
|
||||
|
@ -276,7 +282,7 @@ export async function enrichHostMetadata(
|
|||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
elasticAgentId
|
||||
);
|
||||
hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.ERROR;
|
||||
hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.UNHEALTHY;
|
||||
} catch (e) {
|
||||
if (e instanceof AgentNotFoundError) {
|
||||
log.warn(`agent with id ${elasticAgentId} not found`);
|
||||
|
|
|
@ -24,10 +24,11 @@ export const endpointFilters = schema.object({
|
|||
host_status: schema.nullable(
|
||||
schema.arrayOf(
|
||||
schema.oneOf([
|
||||
schema.literal(HostStatus.ONLINE.toString()),
|
||||
schema.literal(HostStatus.HEALTHY.toString()),
|
||||
schema.literal(HostStatus.OFFLINE.toString()),
|
||||
schema.literal(HostStatus.UNENROLLING.toString()),
|
||||
schema.literal(HostStatus.ERROR.toString()),
|
||||
schema.literal(HostStatus.UPDATING.toString()),
|
||||
schema.literal(HostStatus.UNHEALTHY.toString()),
|
||||
schema.literal(HostStatus.INACTIVE.toString()),
|
||||
])
|
||||
)
|
||||
),
|
||||
|
|
|
@ -131,7 +131,7 @@ describe('test endpoint route', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status online', async () => {
|
||||
it('should return a single endpoint with status healthy', async () => {
|
||||
const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { id: response.hits.hits[0]._id },
|
||||
|
@ -161,7 +161,7 @@ describe('test endpoint route', () => {
|
|||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result).toHaveProperty('metadata.Endpoint');
|
||||
expect(result.host_status).toEqual(HostStatus.ONLINE);
|
||||
expect(result.host_status).toEqual(HostStatus.HEALTHY);
|
||||
expect(result.query_strategy_version).toEqual(MetadataQueryStrategyVersions.VERSION_1);
|
||||
});
|
||||
});
|
||||
|
@ -406,7 +406,7 @@ describe('test endpoint route', () => {
|
|||
expect(message).toEqual('Endpoint Not Found');
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status online', async () => {
|
||||
it('should return a single endpoint with status healthy', async () => {
|
||||
const response = createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { id: response.hits.hits[0]._id },
|
||||
|
@ -436,11 +436,11 @@ describe('test endpoint route', () => {
|
|||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result).toHaveProperty('metadata.Endpoint');
|
||||
expect(result.host_status).toEqual(HostStatus.ONLINE);
|
||||
expect(result.host_status).toEqual(HostStatus.HEALTHY);
|
||||
expect(result.query_strategy_version).toEqual(MetadataQueryStrategyVersions.VERSION_2);
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status error when AgentService throw 404', async () => {
|
||||
it('should return a single endpoint with status unhealthy when AgentService throw 404', async () => {
|
||||
const response = createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
|
@ -474,10 +474,10 @@ describe('test endpoint route', () => {
|
|||
});
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(result.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status error when status is not offline, online or enrolling', async () => {
|
||||
it('should return a single endpoint with status unhealthy when status is not offline, online or enrolling', async () => {
|
||||
const response = createV2SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
|
@ -507,7 +507,7 @@ describe('test endpoint route', () => {
|
|||
});
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(result.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should throw error when endpoint agent is not active', async () => {
|
||||
|
|
|
@ -293,7 +293,7 @@ describe('test endpoint route v1', () => {
|
|||
expect(message).toEqual('Endpoint Not Found');
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status online', async () => {
|
||||
it('should return a single endpoint with status healthy', async () => {
|
||||
const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { id: response.hits.hits[0]._id },
|
||||
|
@ -323,10 +323,10 @@ describe('test endpoint route v1', () => {
|
|||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result).toHaveProperty('metadata.Endpoint');
|
||||
expect(result.host_status).toEqual(HostStatus.ONLINE);
|
||||
expect(result.host_status).toEqual(HostStatus.HEALTHY);
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status error when AgentService throw 404', async () => {
|
||||
it('should return a single endpoint with status unhealthy when AgentService throw 404', async () => {
|
||||
const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
|
@ -360,10 +360,10 @@ describe('test endpoint route v1', () => {
|
|||
});
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(result.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should return a single endpoint with status error when status is not offline, online or enrolling', async () => {
|
||||
it('should return a single endpoint with status unhealthy when status is not offline, online or enrolling', async () => {
|
||||
const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata());
|
||||
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
|
@ -393,7 +393,7 @@ describe('test endpoint route v1', () => {
|
|||
});
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
|
||||
expect(result.host_status).toEqual(HostStatus.ERROR);
|
||||
expect(result.host_status).toEqual(HostStatus.UNHEALTHY);
|
||||
});
|
||||
|
||||
it('should throw error when endpoint agent is not active', async () => {
|
||||
|
|
|
@ -40,7 +40,7 @@ describe('test filtering endpoint hosts by agent status', () => {
|
|||
mockAgentService,
|
||||
mockSavedObjectClient,
|
||||
mockElasticsearchClient,
|
||||
['online']
|
||||
['healthy']
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
@ -101,9 +101,9 @@ describe('test filtering endpoint hosts by agent status', () => {
|
|||
mockAgentService,
|
||||
mockSavedObjectClient,
|
||||
mockElasticsearchClient,
|
||||
['unenrolling', 'error']
|
||||
['updating', 'unhealthy']
|
||||
);
|
||||
const unenrollKuery = AgentStatusKueryHelper.buildKueryForUnenrollingAgents();
|
||||
const unenrollKuery = AgentStatusKueryHelper.buildKueryForUpdatingAgents();
|
||||
const errorKuery = AgentStatusKueryHelper.buildKueryForErrorAgents();
|
||||
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
|
||||
expect.stringContaining(`${unenrollKuery} OR ${errorKuery}`)
|
||||
|
|
|
@ -12,10 +12,11 @@ import { Agent } from '../../../../../../fleet/common/types/models';
|
|||
import { HostStatus } from '../../../../../common/endpoint/types';
|
||||
|
||||
const STATUS_QUERY_MAP = new Map([
|
||||
[HostStatus.ONLINE.toString(), AgentStatusKueryHelper.buildKueryForOnlineAgents()],
|
||||
[HostStatus.HEALTHY.toString(), AgentStatusKueryHelper.buildKueryForOnlineAgents()],
|
||||
[HostStatus.OFFLINE.toString(), AgentStatusKueryHelper.buildKueryForOfflineAgents()],
|
||||
[HostStatus.ERROR.toString(), AgentStatusKueryHelper.buildKueryForErrorAgents()],
|
||||
[HostStatus.UNENROLLING.toString(), AgentStatusKueryHelper.buildKueryForUnenrollingAgents()],
|
||||
[HostStatus.UNHEALTHY.toString(), AgentStatusKueryHelper.buildKueryForErrorAgents()],
|
||||
[HostStatus.UPDATING.toString(), AgentStatusKueryHelper.buildKueryForUpdatingAgents()],
|
||||
[HostStatus.INACTIVE.toString(), AgentStatusKueryHelper.buildKueryForInactiveAgents()],
|
||||
]);
|
||||
|
||||
export async function findAgentIDsByStatus(
|
||||
|
|
|
@ -32,7 +32,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
],
|
||||
[
|
||||
'rezzani-7.example.com',
|
||||
'Error',
|
||||
'Unhealthy',
|
||||
'Default',
|
||||
'Failure',
|
||||
'windows 10.0',
|
||||
|
@ -43,7 +43,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
],
|
||||
[
|
||||
'cadmann-4.example.com',
|
||||
'Error',
|
||||
'Unhealthy',
|
||||
'Default',
|
||||
'Failure',
|
||||
'windows 10.0',
|
||||
|
@ -54,7 +54,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
],
|
||||
[
|
||||
'thurlow-9.example.com',
|
||||
'Error',
|
||||
'Unhealthy',
|
||||
'Default',
|
||||
'Success',
|
||||
'windows 10.0',
|
||||
|
@ -268,7 +268,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
],
|
||||
[
|
||||
'cadmann-4.example.com',
|
||||
'Error',
|
||||
'Unhealthy',
|
||||
'Default',
|
||||
'Failure',
|
||||
'windows 10.0',
|
||||
|
@ -279,7 +279,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
],
|
||||
[
|
||||
'thurlow-9.example.com',
|
||||
'Error',
|
||||
'Unhealthy',
|
||||
'Default',
|
||||
'Success',
|
||||
'windows 10.0',
|
||||
|
|
|
@ -251,7 +251,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(Array.from(statuses)).to.eql(['failure']);
|
||||
});
|
||||
|
||||
it('metadata api should return the endpoint based on the elastic agent id, and status should be error', async () => {
|
||||
it('metadata api should return the endpoint based on the elastic agent id, and status should be unhealthy', async () => {
|
||||
const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf';
|
||||
const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095';
|
||||
const { body } = await supertest
|
||||
|
@ -269,7 +269,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(resultHostId).to.eql(targetEndpointId);
|
||||
expect(resultElasticAgentId).to.eql(targetElasticAgentId);
|
||||
expect(body.hosts[0].metadata.event.created).to.eql(1579881969541);
|
||||
expect(body.hosts[0].host_status).to.eql('error');
|
||||
expect(body.hosts[0].host_status).to.eql('unhealthy');
|
||||
expect(body.hosts.length).to.eql(1);
|
||||
expect(body.request_page_size).to.eql(10);
|
||||
expect(body.request_page_index).to.eql(0);
|
||||
|
|
|
@ -240,7 +240,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(Array.from(statuses)).to.eql(['failure']);
|
||||
});
|
||||
|
||||
it('metadata api should return the endpoint based on the elastic agent id, and status should be error', async () => {
|
||||
it('metadata api should return the endpoint based on the elastic agent id, and status should be unhealthy', async () => {
|
||||
const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf';
|
||||
const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095';
|
||||
const { body } = await supertest
|
||||
|
@ -258,7 +258,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(resultHostId).to.eql(targetEndpointId);
|
||||
expect(resultElasticAgentId).to.eql(targetElasticAgentId);
|
||||
expect(body.hosts[0].metadata.event.created).to.eql(1579881969541);
|
||||
expect(body.hosts[0].host_status).to.eql('error');
|
||||
expect(body.hosts[0].host_status).to.eql('unhealthy');
|
||||
expect(body.hosts.length).to.eql(1);
|
||||
expect(body.request_page_size).to.eql(10);
|
||||
expect(body.request_page_index).to.eql(0);
|
||||
|
|
Loading…
Reference in a new issue