[Security Solution][Endpoint][Admin] Match endpoint list host status with fleet agent status (#95243)

This commit is contained in:
Candace Park 2021-03-26 16:17:12 -04:00 committed by GitHub
parent 2aae753c54
commit 4ff9bfd113
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 210 additions and 154 deletions

View file

@ -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 {

View file

@ -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,
};
};

View file

@ -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;
}
);

View file

@ -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"

View file

@ -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<

View file

@ -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');

View file

@ -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>
);
},
},

View file

@ -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);
});
});

View file

@ -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`);

View file

@ -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()),
])
)
),

View file

@ -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 () => {

View file

@ -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 () => {

View file

@ -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}`)

View file

@ -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(

View file

@ -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',

View file

@ -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);

View file

@ -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);