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