From 0124977830a7995bbfb493a475e6bcd2cd0dc79e Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Tue, 12 May 2020 14:53:48 +0100 Subject: [PATCH 1/2] [ML] Fixes anomaly charts for rare detectors when model plot is enabled (#66075) --- x-pack/plugins/ml/common/util/job_utils.d.ts | 2 ++ x-pack/plugins/ml/common/util/job_utils.js | 22 ++++++++++--------- .../plugins/ml/common/util/job_utils.test.js | 13 ++++++++++- .../explorer_charts_container_service.js | 3 ++- .../application/explorer/explorer_utils.js | 6 ++++- .../timeseries_search_service.ts | 10 +++++++-- .../timeseriesexplorer/timeseriesexplorer.js | 9 ++++---- 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/ml/common/util/job_utils.d.ts b/x-pack/plugins/ml/common/util/job_utils.d.ts index 4528fbfbb774d..170e42aabc67d 100644 --- a/x-pack/plugins/ml/common/util/job_utils.d.ts +++ b/x-pack/plugins/ml/common/util/job_utils.d.ts @@ -44,6 +44,8 @@ export function mlFunctionToESAggregation(functionName: string): string | null; export function isModelPlotEnabled(job: Job, detectorIndex: number, entityFields: any[]): boolean; +export function isModelPlotChartableForDetector(job: Job, detectorIndex: number): boolean; + export function getSafeAggregationName(fieldName: string, index: number): string; export function getLatestDataOrBucketTimestamp( diff --git a/x-pack/plugins/ml/common/util/job_utils.js b/x-pack/plugins/ml/common/util/job_utils.js index 8fe5733ce67bd..1217139872fc1 100644 --- a/x-pack/plugins/ml/common/util/job_utils.js +++ b/x-pack/plugins/ml/common/util/job_utils.js @@ -105,18 +105,20 @@ export function isModelPlotChartableForDetector(job, detectorIndex) { const dtr = dtrs[detectorIndex]; const functionName = dtr.function; - // Model plot can be charted for any of the functions which map to ES aggregations, + // Model plot can be charted for any of the functions which map to ES aggregations + // (except rare, for which no model plot results are generated), // plus varp and info_content functions. isModelPlotChartable = - mlFunctionToESAggregation(functionName) !== null || - [ - 'varp', - 'high_varp', - 'low_varp', - 'info_content', - 'high_info_content', - 'low_info_content', - ].includes(functionName) === true; + functionName !== 'rare' && + (mlFunctionToESAggregation(functionName) !== null || + [ + 'varp', + 'high_varp', + 'low_varp', + 'info_content', + 'high_info_content', + 'low_info_content', + ].includes(functionName) === true); } return isModelPlotChartable; diff --git a/x-pack/plugins/ml/common/util/job_utils.test.js b/x-pack/plugins/ml/common/util/job_utils.test.js index a5df160bdf5ca..de269676a96ed 100644 --- a/x-pack/plugins/ml/common/util/job_utils.test.js +++ b/x-pack/plugins/ml/common/util/job_utils.test.js @@ -307,7 +307,14 @@ describe('ML - job utils', () => { const job2 = { analysis_config: { - detectors: [{ function: 'count' }, { function: 'info_content' }], + detectors: [ + { function: 'count' }, + { function: 'info_content' }, + { + function: 'rare', + by_field_name: 'mlcategory', + }, + ], }, model_plot_config: { enabled: true, @@ -325,6 +332,10 @@ describe('ML - job utils', () => { test('returns true for info_content detector when model plot is enabled', () => { expect(isModelPlotChartableForDetector(job2, 1)).toBe(true); }); + + test('returns false for rare by mlcategory when model plot is enabled', () => { + expect(isModelPlotChartableForDetector(job2, 2)).toBe(false); + }); }); describe('getPartitioningFieldNames', () => { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index e0fb97a81f587..fe7e436b61117 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -19,6 +19,7 @@ import { chartLimits, getChartType } from '../../util/chart_utils'; import { getEntityFieldList } from '../../../../common/util/anomaly_utils'; import { isSourceDataChartableForDetector, + isModelPlotChartableForDetector, isModelPlotEnabled, } from '../../../../common/util/job_utils'; import { mlResultsService } from '../../services/results_service'; @@ -420,7 +421,7 @@ function processRecordsForDisplay(anomalyRecords) { // is chartable, and if model plot is enabled for the job. const job = mlJobService.getJob(record.job_id); let isChartable = isSourceDataChartableForDetector(job, record.detector_index); - if (isChartable === false) { + if (isChartable === false && isModelPlotChartableForDetector(job, record.detector_index)) { // Check if model plot is enabled for this job. // Need to check the entity fields for the record in case the model plot config has a terms list. const entityFields = getEntityFieldList(record); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js index 3fcb3c351e666..aaf9ff491ce32 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.js @@ -20,6 +20,7 @@ import { import { getEntityFieldList } from '../../../common/util/anomaly_utils'; import { isSourceDataChartableForDetector, + isModelPlotChartableForDetector, isModelPlotEnabled, } from '../../../common/util/job_utils'; import { parseInterval } from '../../../common/util/parse_interval'; @@ -636,7 +637,10 @@ export async function loadAnomaliesTableData( // TODO - when job_service is moved server_side, move this to server endpoint. const job = mlJobService.getJob(jobId); let isChartable = isSourceDataChartableForDetector(job, anomaly.detectorIndex); - if (isChartable === false) { + if ( + isChartable === false && + isModelPlotChartableForDetector(job, anomaly.detectorIndex) + ) { // Check if model plot is enabled for this job. // Need to check the entity fields for the record in case the model plot config has a terms list. // If terms is specified, model plot is only stored if both the partition and by fields appear in the list. diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index f973d41ad7754..6e46ab0023ce4 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -9,7 +9,10 @@ import _ from 'lodash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ml } from '../services/ml_api_service'; -import { isModelPlotEnabled } from '../../../common/util/job_utils'; +import { + isModelPlotChartableForDetector, + isModelPlotEnabled, +} from '../../../common/util/job_utils'; // @ts-ignore import { buildConfigFromDetector } from '../util/chart_config_builder'; import { mlResultsService } from '../services/results_service'; @@ -24,7 +27,10 @@ function getMetricData( latestMs: number, interval: string ): Observable { - if (isModelPlotEnabled(job, detectorIndex, entityFields)) { + if ( + isModelPlotChartableForDetector(job, detectorIndex) && + isModelPlotEnabled(job, detectorIndex, entityFields) + ) { // Extract the partition, by, over fields on which to filter. const criteriaFields = []; const detector = job.analysis_config.detectors[detectorIndex]; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 45f495299bc69..8bf42fe545152 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -36,6 +36,7 @@ import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; import { isModelPlotEnabled, + isModelPlotChartableForDetector, isSourceDataChartableForDetector, isTimeSeriesViewDetector, mlFunctionToESAggregation, @@ -506,11 +507,9 @@ export class TimeSeriesExplorer extends React.Component { contextForecastData: undefined, focusChartData: undefined, focusForecastData: undefined, - modelPlotEnabled: isModelPlotEnabled( - currentSelectedJob, - selectedDetectorIndex, - entityControls - ), + modelPlotEnabled: + isModelPlotChartableForDetector(currentSelectedJob, selectedDetectorIndex) && + isModelPlotEnabled(currentSelectedJob, selectedDetectorIndex, entityControls), hasResults: false, dataNotChartable: false, } From 45790d035bb5197e87b1539d1454f3544bca6969 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Tue, 12 May 2020 10:08:15 -0400 Subject: [PATCH 2/2] [endpoint] connect policy response ui to api (#66093) [Endpoint] Connect policy response UI to API --- .../endpoint/store/hosts/action.ts | 8 ++- .../endpoint/store/hosts/index.test.ts | 3 ++ .../endpoint/store/hosts/middleware.ts | 53 ++++++------------ .../endpoint/store/hosts/reducer.ts | 15 ++++++ .../endpoint/store/hosts/selectors.ts | 5 ++ .../public/applications/endpoint/types.ts | 4 ++ .../view/hosts/details/host_details.tsx | 2 +- .../endpoint/view/hosts/details/index.tsx | 24 ++++++--- .../view/hosts/details/policy_response.tsx | 21 +++++++- .../details/policy_response_friendly_names.ts | 12 +++++ .../hosts/{details => }/host_constants.ts | 12 ++++- .../endpoint/view/hosts/index.tsx | 54 +++++++++++++------ 12 files changed, 148 insertions(+), 65 deletions(-) rename x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/{details => }/host_constants.ts (59%) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 16a1f96c926b8..ac10adcda0306 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -32,9 +32,15 @@ interface ServerReturnedHostPolicyResponse { payload: GetHostPolicyResponse; } +interface ServerFailedToReturnHostPolicyResponse { + type: 'serverFailedToReturnHostPolicyResponse'; + payload: ServerApiError; +} + export type HostAction = | ServerReturnedHostList | ServerFailedToReturnHostList | ServerReturnedHostDetails | ServerFailedToReturnHostDetails - | ServerReturnedHostPolicyResponse; + | ServerReturnedHostPolicyResponse + | ServerFailedToReturnHostPolicyResponse; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts index 863ffc50d0155..f60a69a471684 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts @@ -41,6 +41,9 @@ describe('HostList store concerns', () => { details: undefined, detailsLoading: false, detailsError: undefined, + policyResponse: undefined, + policyResponseLoading: false, + policyResponseError: undefined, location: undefined, }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index a5378a02ed6fb..9a28423d6adc4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList, HostPolicyResponseActionStatus } from '../../../../../common/types'; +import { HostResultList } from '../../../../../common/types'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../../types'; import { ImmutableMiddlewareFactory } from '../../types'; -import { HostPolicyResponse } from '../../../../../common/types'; export const hostMiddlewareFactory: ImmutableMiddlewareFactory = coreStart => { return ({ getState, dispatch }) => next => async action => { @@ -70,47 +69,25 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = core type: 'serverReturnedHostDetails', payload: response, }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnHostDetails', + payload: error, + }); + } + + // call the policy response api + try { + const policyResponse = await coreStart.http.get(`/api/endpoint/policy_response`, { + query: { hostId: selectedHost }, + }); dispatch({ type: 'serverReturnedHostPolicyResponse', - payload: { - policy_response: ({ - endpoint: { - policy: { - applied: { - version: '1.0.0', - status: HostPolicyResponseActionStatus.success, - id: '17d4b81d-9940-4b64-9de5-3e03ef1fb5cf', - actions: { - download_model: { - status: 'success', - message: 'Model downloaded', - }, - ingest_events_config: { - status: 'failure', - message: 'No action taken', - }, - }, - response: { - configurations: { - malware: { - status: 'success', - concerned_actions: ['download_model'], - }, - events: { - status: 'failure', - concerned_actions: ['ingest_events_config'], - }, - }, - }, - }, - }, - }, - } as unknown) as HostPolicyResponse, // Temporary until we get API - }, + payload: policyResponse, }); } catch (error) { dispatch({ - type: 'serverFailedToReturnHostDetails', + type: 'serverFailedToReturnHostPolicyResponse', payload: error, }); } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index 93e995194353b..18bc6b0bea3da 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -21,6 +21,8 @@ const initialState = (): HostState => { detailsLoading: false, detailsError: undefined, policyResponse: undefined, + policyResponseLoading: false, + policyResponseError: undefined, location: undefined, }; }; @@ -68,6 +70,14 @@ export const hostListReducer: ImmutableReducer = ( return { ...state, policyResponse: action.payload.policy_response, + policyResponseLoading: false, + policyResponseError: undefined, + }; + } else if (action.type === 'serverFailedToReturnHostPolicyResponse') { + return { + ...state, + policyResponseError: action.payload, + policyResponseLoading: false, }; } else if (action.type === 'userChangedUrl') { const newState: Immutable = { @@ -97,8 +107,10 @@ export const hostListReducer: ImmutableReducer = ( ...state, location: action.payload, detailsLoading: true, + policyResponseLoading: true, error: undefined, detailsError: undefined, + policyResponseError: undefined, }; } else { // if previous page was not host list or host details, load both list and details @@ -107,8 +119,10 @@ export const hostListReducer: ImmutableReducer = ( location: action.payload, loading: true, detailsLoading: true, + policyResponseLoading: true, error: undefined, detailsError: undefined, + policyResponseError: undefined, }; } } @@ -118,6 +132,7 @@ export const hostListReducer: ImmutableReducer = ( location: action.payload, error: undefined, detailsError: undefined, + policyResponseError: undefined, }; } return state; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index e16d4ff5d18c2..1ba7549c00f4e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -88,6 +88,11 @@ export const policyResponseActions: ( } ); +export const policyResponseLoading = (state: Immutable): boolean => + state.policyResponseLoading; + +export const policyResponseError = (state: Immutable) => state.policyResponseError; + export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 58e706f20ec8e..8b401f80b2fdd 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -110,6 +110,10 @@ export interface HostState { detailsError?: ServerApiError; /** Holds the Policy Response for the Host currently being displayed in the details */ policyResponse?: HostPolicyResponse; + /** policyResponse is being retrieved */ + policyResponseLoading: boolean; + /** api error from retrieving the policy response */ + policyResponseError?: ServerApiError; /** current location info */ location?: Immutable; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx index ee1c7543d7e0a..2ded0e4b3123d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx @@ -23,7 +23,7 @@ import { useHostSelector, useHostLogsUrl } from '../hooks'; import { urlFromQueryParams } from '../url_from_query_params'; import { policyResponseStatus, uiQueryParams } from '../../../store/hosts/selectors'; import { useNavigateByRouterEventHandler } from '../../hooks/use_navigate_by_router_event_handler'; -import { POLICY_STATUS_TO_HEALTH_COLOR } from './host_constants'; +import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx index 017ce9a66f8c5..5c8e1a58087ee 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx @@ -13,6 +13,7 @@ import { EuiTitle, EuiText, EuiSpacer, + EuiEmptyPrompt, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -29,6 +30,8 @@ import { policyResponseConfigurations, policyResponseActions, policyResponseFailedOrWarningActionCount, + policyResponseError, + policyResponseLoading, } from '../../../store/hosts/selectors'; import { HostDetails } from './host_details'; import { PolicyResponse } from './policy_response'; @@ -108,6 +111,8 @@ const PolicyResponseFlyoutPanel = memo<{ const responseConfig = useHostSelector(policyResponseConfigurations); const responseActionStatus = useHostSelector(policyResponseActions); const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount); + const loading = useHostSelector(policyResponseLoading); + const error = useHostSelector(policyResponseError); const detailsUri = useMemo( () => urlFromQueryParams({ @@ -142,17 +147,24 @@ const PolicyResponseFlyoutPanel = memo<{ /> - {responseConfig !== undefined && responseActionStatus !== undefined ? ( + {error && ( + + } + /> + )} + {loading && } + + {responseConfig !== undefined && responseActionStatus !== undefined && ( - ) : ( - )} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx index 8714141364e7d..5fc4a9bcde33d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx @@ -14,7 +14,7 @@ import { Immutable, } from '../../../../../../common/types'; import { formatResponse } from './policy_response_friendly_names'; -import { POLICY_STATUS_TO_HEALTH_COLOR } from './host_constants'; +import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants'; /** * Nested accordion in the policy response detailing any concerned @@ -43,6 +43,17 @@ const PolicyResponseConfigAccordion = styled(EuiAccordion)` :hover:not(.euiAccordion-isOpen) { background-color: ${props => props.theme.eui.euiColorLightestShade}; } + + .policyResponseActionsAccordion { + svg { + height: ${props => props.theme.eui.euiIconSizes.small}; + width: ${props => props.theme.eui.euiIconSizes.small}; + } + } + + .policyResponseStatusHealth { + width: 100px; + } `; const ResponseActions = memo( @@ -65,8 +76,13 @@ const ResponseActions = memo( id={action + index} key={action + index} data-test-subj="hostDetailsPolicyResponseActionsAccordion" + className="policyResponseActionsAccordion" buttonContent={ - +

{formatResponse(action)}

} @@ -75,6 +91,7 @@ const ResponseActions = memo(

{formatResponse(statuses.status)}

diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts index 502aa66b24421..8eaacb31b4f87 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts @@ -25,6 +25,18 @@ responseMap.set( defaultMessage: 'Failed', }) ); +responseMap.set( + 'logging', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.logging', { + defaultMessage: 'Logging', + }) +); +responseMap.set( + 'streaming', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.streaming', { + defaultMessage: 'Streaming', + }) +); responseMap.set( 'malware', i18n.translate('xpack.endpoint.hostDetails.policyResponse.malware', { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_constants.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/host_constants.ts similarity index 59% rename from x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_constants.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/host_constants.ts index 5250eeaf028d5..08b2608698a66 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_constants.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/host_constants.ts @@ -4,7 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostPolicyResponseActionStatus } from '../../../../../../common/types'; +import { HostPolicyResponseActionStatus, HostStatus } from '../../../../../common/types'; + +export const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze< + { + [key in HostStatus]: string; + } +>({ + [HostStatus.ERROR]: 'danger', + [HostStatus.ONLINE]: 'success', + [HostStatus.OFFLINE]: 'subdued', +}); export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< { [key in keyof typeof HostPolicyResponseActionStatus]: string } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 026ba2ff15126..638dd190dcbce 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -5,7 +5,14 @@ */ import React, { useMemo, useCallback, memo } from 'react'; -import { EuiHorizontalRule, EuiBasicTable, EuiText, EuiLink, EuiHealth } from '@elastic/eui'; +import { + EuiHorizontalRule, + EuiBasicTable, + EuiText, + EuiLink, + EuiHealth, + EuiToolTip, +} from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -16,19 +23,10 @@ import * as selectors from '../../store/hosts/selectors'; import { useHostSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; -import { HostInfo, HostStatus, Immutable } from '../../../../../common/types'; +import { HostInfo, Immutable } from '../../../../../common/types'; import { PageView } from '../components/page_view'; import { useNavigateByRouterEventHandler } from '../hooks/use_navigate_by_router_event_handler'; - -const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze< - { - [key in HostStatus]: string; - } ->({ - [HostStatus.ERROR]: 'danger', - [HostStatus.ONLINE]: 'success', - [HostStatus.OFFLINE]: 'subdued', -}); +import { HOST_STATUS_TO_HEALTH_COLOR } from './host_constants'; const HostLink = memo<{ name: string; @@ -39,7 +37,12 @@ const HostLink = memo<{ return ( // eslint-disable-next-line @elastic/eui/href-or-on-click - + {name} ); @@ -107,6 +110,7 @@ export const HostList = () => { { }), truncateText: true, render: () => { - return 'Policy Name'; + return Policy Name; }, }, { @@ -133,7 +137,14 @@ export const HostList = () => { defaultMessage: 'Policy Status', }), render: () => { - return Policy Status; + return ( + + + + ); }, }, { @@ -151,13 +162,24 @@ export const HostList = () => { name: i18n.translate('xpack.endpoint.host.list.os', { defaultMessage: 'Operating System', }), + truncateText: true, }, { field: 'metadata.host.ip', name: i18n.translate('xpack.endpoint.host.list.ip', { defaultMessage: 'IP Address', }), - truncateText: true, + render: (ip: string[]) => { + return ( + + + + {ip.toString().replace(',', ', ')} + + + + ); + }, }, { field: 'metadata.agent.version',