mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
parent
a06c02f606
commit
ce0d39bfdf
11 changed files with 501 additions and 31 deletions
|
@ -560,7 +560,7 @@ export class EndpointDocGenerator {
|
|||
applied: {
|
||||
actions: {
|
||||
configure_elasticsearch_connection: {
|
||||
message: 'elasticsearch comms configured successfully',
|
||||
message: 'elasticsearch comes configured successfully',
|
||||
status: HostPolicyResponseActionStatus.success,
|
||||
},
|
||||
configure_kernel: {
|
||||
|
@ -648,7 +648,7 @@ export class EndpointDocGenerator {
|
|||
response: {
|
||||
configurations: {
|
||||
events: {
|
||||
concerned_actions: this.randomHostPolicyResponseActions(),
|
||||
concerned_actions: ['download_model'],
|
||||
status: this.randomHostPolicyResponseActionStatus(),
|
||||
},
|
||||
logging: {
|
||||
|
|
|
@ -25,7 +25,7 @@ export type Immutable<T> = T extends undefined | null | boolean | string | numbe
|
|||
? ImmutableSet<M>
|
||||
: ImmutableObject<T>;
|
||||
|
||||
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
||||
export type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
||||
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
||||
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
||||
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
|
||||
|
@ -644,6 +644,8 @@ export interface HostPolicyResponseActions {
|
|||
read_malware_config: HostPolicyResponseActionDetails;
|
||||
}
|
||||
|
||||
export type HostPolicyResponseConfiguration = HostPolicyResponse['endpoint']['policy']['applied']['response']['configurations'];
|
||||
|
||||
interface HostPolicyResponseConfigurationStatus {
|
||||
status: HostPolicyResponseActionStatus;
|
||||
concerned_actions: Array<keyof HostPolicyResponseActions>;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostResultList } from '../../../../../common/types';
|
||||
import { HostResultList, HostPolicyResponseActionStatus } from '../../../../../common/types';
|
||||
import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors';
|
||||
import { HostState } from '../../types';
|
||||
import { ImmutableMiddlewareFactory } from '../../types';
|
||||
|
@ -77,7 +77,31 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory<HostState> = core
|
|||
endpoint: {
|
||||
policy: {
|
||||
applied: {
|
||||
status: 'success',
|
||||
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'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
*/
|
||||
import querystring from 'querystring';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Immutable } from '../../../../../common/types';
|
||||
import {
|
||||
Immutable,
|
||||
HostPolicyResponseActions,
|
||||
HostPolicyResponseConfiguration,
|
||||
HostPolicyResponseActionStatus,
|
||||
} from '../../../../../common/types';
|
||||
import { HostState, HostIndexUIQueryParams } from '../../types';
|
||||
|
||||
const PAGE_SIZES = Object.freeze([10, 20, 50]);
|
||||
|
@ -28,6 +33,61 @@ export const detailsLoading = (state: Immutable<HostState>): boolean => state.de
|
|||
|
||||
export const detailsError = (state: Immutable<HostState>) => state.detailsError;
|
||||
|
||||
/**
|
||||
* Returns the full policy response from the endpoint after a user modifies a policy.
|
||||
*/
|
||||
const detailsPolicyAppliedResponse = (state: Immutable<HostState>) =>
|
||||
state.policyResponse && state.policyResponse.endpoint.policy.applied;
|
||||
|
||||
/**
|
||||
* Returns the response configurations from the endpoint after a user modifies a policy.
|
||||
*/
|
||||
export const policyResponseConfigurations: (
|
||||
state: Immutable<HostState>
|
||||
) => undefined | Immutable<HostPolicyResponseConfiguration> = createSelector(
|
||||
detailsPolicyAppliedResponse,
|
||||
applied => {
|
||||
return applied?.response?.configurations;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a map of the number of failed and warning policy response actions per configuration.
|
||||
*/
|
||||
export const policyResponseFailedOrWarningActionCount: (
|
||||
state: Immutable<HostState>
|
||||
) => Map<string, number> = createSelector(detailsPolicyAppliedResponse, applied => {
|
||||
const failureOrWarningByConfigType = new Map<string, number>();
|
||||
if (applied?.response?.configurations !== undefined && applied?.actions !== undefined) {
|
||||
Object.entries(applied.response.configurations).map(([key, val]) => {
|
||||
let count = 0;
|
||||
for (const action of val.concerned_actions) {
|
||||
const actionStatus = applied.actions[action]?.status;
|
||||
if (
|
||||
actionStatus === HostPolicyResponseActionStatus.failure ||
|
||||
actionStatus === HostPolicyResponseActionStatus.warning
|
||||
) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return failureOrWarningByConfigType.set(key, count);
|
||||
});
|
||||
}
|
||||
return failureOrWarningByConfigType;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the actions taken by the endpoint for each response configuration after a user modifies a policy.
|
||||
*/
|
||||
export const policyResponseActions: (
|
||||
state: Immutable<HostState>
|
||||
) => undefined | Partial<HostPolicyResponseActions> = createSelector(
|
||||
detailsPolicyAppliedResponse,
|
||||
applied => {
|
||||
return applied?.actions;
|
||||
}
|
||||
);
|
||||
|
||||
export const isOnHostPage = (state: Immutable<HostState>) =>
|
||||
state.location ? state.location.pathname === '/hosts' : false;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiFlyoutHeader, CommonProps, EuiButtonEmpty } from '@elastic/eui';
|
|||
import styled from 'styled-components';
|
||||
|
||||
export type FlyoutSubHeaderProps = CommonProps & {
|
||||
children: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
backButton?: {
|
||||
title: string;
|
||||
onClick: MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
|
||||
|
@ -25,6 +25,9 @@ const StyledEuiFlyoutHeader = styled(EuiFlyoutHeader)`
|
|||
padding-bottom: ${props => props.theme.eui.paddingSizes.s};
|
||||
}
|
||||
|
||||
.flyoutSubHeaderBackButton {
|
||||
font-size: ${props => props.theme.eui.euiFontSizeXS};
|
||||
}
|
||||
.back-button-content {
|
||||
padding-left: 0;
|
||||
&-text {
|
||||
|
@ -48,7 +51,7 @@ const BUTTON_TEXT_PROPS = Object.freeze({ className: 'back-button-content-text'
|
|||
export const FlyoutSubHeader = memo<FlyoutSubHeaderProps>(
|
||||
({ children, backButton, ...otherProps }) => {
|
||||
return (
|
||||
<StyledEuiFlyoutHeader hasBorder {...otherProps} className={backButton && `hasButtons`}>
|
||||
<StyledEuiFlyoutHeader {...otherProps} className={backButton && `hasButtons`}>
|
||||
{backButton && (
|
||||
<div className="buttons">
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
|
@ -60,6 +63,7 @@ export const FlyoutSubHeader = memo<FlyoutSubHeaderProps>(
|
|||
size="xs"
|
||||
href={backButton?.href ?? ''}
|
||||
onClick={backButton?.onClick}
|
||||
className="flyoutSubHeaderBackButton"
|
||||
>
|
||||
{backButton?.title}
|
||||
</EuiButtonEmpty>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { HostPolicyResponseActionStatus } from '../../../../../../common/types';
|
||||
|
||||
export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
|
||||
{ [key in keyof typeof HostPolicyResponseActionStatus]: string }
|
||||
>({
|
||||
success: 'success',
|
||||
warning: 'warning',
|
||||
failure: 'danger',
|
||||
});
|
|
@ -16,13 +16,14 @@ import {
|
|||
import React, { memo, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HostMetadata, HostPolicyResponseActionStatus } from '../../../../../../common/types';
|
||||
import { HostMetadata } from '../../../../../../common/types';
|
||||
import { FormattedDateAndTime } from '../../formatted_date_time';
|
||||
import { LinkToApp } from '../../components/link_to_app';
|
||||
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';
|
||||
|
||||
const HostIds = styled(EuiListGroupItem)`
|
||||
margin-top: 0;
|
||||
|
@ -31,14 +32,6 @@ const HostIds = styled(EuiListGroupItem)`
|
|||
}
|
||||
`;
|
||||
|
||||
const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
|
||||
{ [key in keyof typeof HostPolicyResponseActionStatus]: string }
|
||||
>({
|
||||
success: 'success',
|
||||
warning: 'warning',
|
||||
failure: 'danger',
|
||||
});
|
||||
|
||||
export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
|
||||
const { appId, appPath, url } = useHostLogsUrl(details.host.id);
|
||||
const queryParams = useHostSelector(uiQueryParams);
|
||||
|
|
|
@ -9,8 +9,9 @@ import {
|
|||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiLoadingContent,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
@ -25,6 +26,9 @@ import {
|
|||
detailsError,
|
||||
showView,
|
||||
detailsLoading,
|
||||
policyResponseConfigurations,
|
||||
policyResponseActions,
|
||||
policyResponseFailedOrWarningActionCount,
|
||||
} from '../../../store/hosts/selectors';
|
||||
import { HostDetails } from './host_details';
|
||||
import { PolicyResponse } from './policy_response';
|
||||
|
@ -101,6 +105,9 @@ const PolicyResponseFlyoutPanel = memo<{
|
|||
hostMeta: HostMetadata;
|
||||
}>(({ hostMeta }) => {
|
||||
const { show, ...queryParams } = useHostSelector(uiQueryParams);
|
||||
const responseConfig = useHostSelector(policyResponseConfigurations);
|
||||
const responseActionStatus = useHostSelector(policyResponseActions);
|
||||
const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount);
|
||||
const detailsUri = useMemo(
|
||||
() =>
|
||||
urlFromQueryParams({
|
||||
|
@ -125,18 +132,28 @@ const PolicyResponseFlyoutPanel = memo<{
|
|||
<FlyoutSubHeader
|
||||
backButton={backButtonProp}
|
||||
data-test-subj="hostDetailsPolicyResponseFlyoutHeader"
|
||||
>
|
||||
<EuiTitle size="xxs" data-test-subj="hostDetailsPolicyResponseFlyoutTitle">
|
||||
<h3>
|
||||
/>
|
||||
<EuiFlyoutBody data-test-subj="hostDetailsPolicyResponseFlyoutBody">
|
||||
<EuiText data-test-subj="hostDetailsPolicyResponseFlyoutTitle">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.host.policyResponse.title"
|
||||
defaultMessage="Policy Response"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</FlyoutSubHeader>
|
||||
<EuiFlyoutBody data-test-subj="hostDetailsPolicyResponseFlyoutBody">
|
||||
<PolicyResponse />
|
||||
</h4>
|
||||
</EuiText>
|
||||
{responseConfig !== undefined && responseActionStatus !== undefined ? (
|
||||
<PolicyResponse
|
||||
responseConfig={responseConfig}
|
||||
responseActionStatus={responseActionStatus}
|
||||
responseAttentionCount={responseAttentionCount}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.hostDetails.noPolicyResponse"
|
||||
defaultMessage="No Policy Response Available"
|
||||
/>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -3,8 +3,145 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiAccordion, EuiNotificationBadge, EuiHealth } from '@elastic/eui';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { htmlIdGenerator } from '@elastic/eui';
|
||||
import {
|
||||
HostPolicyResponseActions,
|
||||
HostPolicyResponseConfiguration,
|
||||
Immutable,
|
||||
ImmutableArray,
|
||||
} from '../../../../../../common/types';
|
||||
import { formatResponse } from './policy_response_friendly_names';
|
||||
import { POLICY_STATUS_TO_HEALTH_COLOR } from './host_constants';
|
||||
|
||||
export const PolicyResponse = memo(() => {
|
||||
return <div>Policy Status to be displayed here soon.</div>;
|
||||
});
|
||||
/**
|
||||
* Nested accordion in the policy response detailing any concerned
|
||||
* actions the endpoint took to apply the policy configuration.
|
||||
*/
|
||||
const PolicyResponseConfigAccordion = styled(EuiAccordion)`
|
||||
> .euiAccordion__triggerWrapper {
|
||||
padding: ${props => props.theme.eui.paddingSizes.s};
|
||||
}
|
||||
&.euiAccordion-isOpen {
|
||||
background-color: ${props => props.theme.eui.euiFocusBackgroundColor};
|
||||
}
|
||||
.euiAccordion__childWrapper {
|
||||
background-color: ${props => props.theme.eui.euiColorLightestShade};
|
||||
}
|
||||
.policyResponseAttentionBadge {
|
||||
background-color: ${props => props.theme.eui.euiColorDanger};
|
||||
color: ${props => props.theme.eui.euiColorEmptyShade};
|
||||
}
|
||||
.euiAccordion__button {
|
||||
:hover,
|
||||
:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
:hover:not(.euiAccordion-isOpen) {
|
||||
background-color: ${props => props.theme.eui.euiColorLightestShade};
|
||||
}
|
||||
`;
|
||||
|
||||
const ResponseActions = memo(
|
||||
({
|
||||
actions,
|
||||
actionStatus,
|
||||
}: {
|
||||
actions: ImmutableArray<keyof HostPolicyResponseActions>;
|
||||
actionStatus: Partial<HostPolicyResponseActions>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{actions.map((action, index) => {
|
||||
const statuses = actionStatus[action];
|
||||
if (statuses === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<EuiAccordion
|
||||
id={action + index}
|
||||
key={action + index}
|
||||
data-test-subj="hostDetailsPolicyResponseActionsAccordion"
|
||||
buttonContent={
|
||||
<EuiText size="xs" data-test-subj="policyResponseAction">
|
||||
<h4>{formatResponse(action)}</h4>
|
||||
</EuiText>
|
||||
}
|
||||
paddingSize="s"
|
||||
extraAction={
|
||||
<EuiHealth
|
||||
color={POLICY_STATUS_TO_HEALTH_COLOR[statuses.status]}
|
||||
data-test-subj="policyResponseStatusHealth"
|
||||
>
|
||||
<EuiText size="xs">
|
||||
<p>{formatResponse(statuses.status)}</p>
|
||||
</EuiText>
|
||||
</EuiHealth>
|
||||
}
|
||||
>
|
||||
<EuiText size="xs" data-test-subj="policyResponseMessage">
|
||||
<p>{statuses.message}</p>
|
||||
</EuiText>
|
||||
</EuiAccordion>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* A policy response is returned by the endpoint and shown in the host details after a user modifies a policy
|
||||
*/
|
||||
export const PolicyResponse = memo(
|
||||
({
|
||||
responseConfig,
|
||||
responseActionStatus,
|
||||
responseAttentionCount,
|
||||
}: {
|
||||
responseConfig: Immutable<HostPolicyResponseConfiguration>;
|
||||
responseActionStatus: Partial<HostPolicyResponseActions>;
|
||||
responseAttentionCount: Map<string, number>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{Object.entries(responseConfig).map(([key, val]) => {
|
||||
const attentionCount = responseAttentionCount.get(key);
|
||||
return (
|
||||
<PolicyResponseConfigAccordion
|
||||
id={useMemo(() => htmlIdGenerator()(), [])}
|
||||
key={useMemo(() => htmlIdGenerator()(), [])}
|
||||
data-test-subj="hostDetailsPolicyResponseConfigAccordion"
|
||||
buttonContent={
|
||||
<EuiText size="s">
|
||||
<p>{formatResponse(key)}</p>
|
||||
</EuiText>
|
||||
}
|
||||
paddingSize="m"
|
||||
extraAction={
|
||||
attentionCount &&
|
||||
attentionCount > 0 && (
|
||||
<EuiNotificationBadge
|
||||
className="policyResponseAttentionBadge"
|
||||
data-test-subj="hostDetailsPolicyResponseAttentionBadge"
|
||||
>
|
||||
{attentionCount}
|
||||
</EuiNotificationBadge>
|
||||
)
|
||||
}
|
||||
>
|
||||
<ResponseActions
|
||||
actions={val.concerned_actions}
|
||||
actionStatus={responseActionStatus}
|
||||
/>
|
||||
</PolicyResponseConfigAccordion>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const responseMap = new Map();
|
||||
responseMap.set(
|
||||
'success',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.success', {
|
||||
defaultMessage: 'Success',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'warning',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.warning', {
|
||||
defaultMessage: 'Warning',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'failure',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.failed', {
|
||||
defaultMessage: 'Failed',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'malware',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.malware', {
|
||||
defaultMessage: 'Malware',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'events',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.events', {
|
||||
defaultMessage: 'Events',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'configure_elasticsearch_connection',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureElasticSearchConnection', {
|
||||
defaultMessage: 'Configure Elastic Search Connection',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'configure_logging',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureLogging', {
|
||||
defaultMessage: 'Configure Logging',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'configure_kernel',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureKernel', {
|
||||
defaultMessage: 'Configure Kernel',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'configure_malware',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureMalware', {
|
||||
defaultMessage: 'Configure Malware',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'connect_kernel',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.connectKernel', {
|
||||
defaultMessage: 'Connect Kernel',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'detect_file_open_events',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectFileOpenEvents', {
|
||||
defaultMessage: 'Detect File Open Events',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'detect_file_write_events',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectFileWriteEvents', {
|
||||
defaultMessage: 'Detect File Write Events',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'detect_image_load_events',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectImageLoadEvents', {
|
||||
defaultMessage: 'Detect Image Load Events',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'detect_process_events',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectProcessEvents', {
|
||||
defaultMessage: 'Detect Process Events',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'download_global_artifacts',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.downloadGlobalArtifacts', {
|
||||
defaultMessage: 'Download Global Artifacts',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'load_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.loadConfig', {
|
||||
defaultMessage: 'Load Config',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'load_malware_model',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.loadMalwareModel', {
|
||||
defaultMessage: 'Load Malware Model',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'read_elasticsearch_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.readElasticSearchConfig', {
|
||||
defaultMessage: 'Read ElasticSearch Config',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'read_events_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.readEventsConfig', {
|
||||
defaultMessage: 'Read Events Config',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'read_kernel_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.readKernelConfig', {
|
||||
defaultMessage: 'Read Kernel Config',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'read_logging_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.readLoggingConfig', {
|
||||
defaultMessage: 'Read Logging Config',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'read_malware_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.readMalwareConfig', {
|
||||
defaultMessage: 'Read Malware Config',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'workflow',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.workflow', {
|
||||
defaultMessage: 'Workflow',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'download_model',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.downloadModel', {
|
||||
defaultMessage: 'Download Model',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'ingest_events_config',
|
||||
i18n.translate('xpack.endpoint.hostDetails.policyResponse.injestEventsConfig', {
|
||||
defaultMessage: 'Injest Events Config',
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Takes in the snake-cased response from the API and
|
||||
* removes the underscores and capitalizes the string.
|
||||
*/
|
||||
export function formatResponse(responseString: string) {
|
||||
if (responseMap.has(responseString)) {
|
||||
return responseMap.get(responseString);
|
||||
}
|
||||
return responseString;
|
||||
}
|
|
@ -25,7 +25,7 @@ describe('when on the hosts page', () => {
|
|||
let coreStart: AppContextTestRender['coreStart'];
|
||||
let middlewareSpy: AppContextTestRender['middlewareSpy'];
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
({ history, store, coreStart, middlewareSpy } = mockedContext);
|
||||
render = () => mockedContext.render(<HostList />);
|
||||
|
@ -127,6 +127,14 @@ describe('when on the hosts page', () => {
|
|||
) => {
|
||||
const policyResponse = docGenerator.generatePolicyResponse();
|
||||
policyResponse.endpoint.policy.applied.status = overallStatus;
|
||||
policyResponse.endpoint.policy.applied.response.configurations.malware.status = overallStatus;
|
||||
policyResponse.endpoint.policy.applied.actions.download_model!.status = overallStatus;
|
||||
if (
|
||||
overallStatus === HostPolicyResponseActionStatus.failure ||
|
||||
overallStatus === HostPolicyResponseActionStatus.warning
|
||||
) {
|
||||
policyResponse.endpoint.policy.applied.actions.download_model!.message = 'no action taken';
|
||||
}
|
||||
store.dispatch({
|
||||
type: 'serverReturnedHostPolicyResponse',
|
||||
payload: {
|
||||
|
@ -281,6 +289,9 @@ describe('when on the hosts page', () => {
|
|||
fireEvent.click(policyStatusLink);
|
||||
});
|
||||
await userChangedUrlChecker;
|
||||
reactTestingLibrary.act(() => {
|
||||
dispatchServerReturnedHostPolicyResponse();
|
||||
});
|
||||
});
|
||||
it('should hide the host details panel', async () => {
|
||||
const hostDetailsFlyout = await renderResult.queryByTestId('hostDetailsFlyoutBody');
|
||||
|
@ -299,6 +310,43 @@ describe('when on the hosts page', () => {
|
|||
(await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutTitle')).textContent
|
||||
).toBe('Policy Response');
|
||||
});
|
||||
it('should show a configuration section for each protection', async () => {
|
||||
const configAccordions = await renderResult.findAllByTestId(
|
||||
'hostDetailsPolicyResponseConfigAccordion'
|
||||
);
|
||||
expect(configAccordions).not.toBeNull();
|
||||
});
|
||||
it('should show an actions section for each configuration', async () => {
|
||||
const actionAccordions = await renderResult.findAllByTestId(
|
||||
'hostDetailsPolicyResponseActionsAccordion'
|
||||
);
|
||||
const action = await renderResult.findAllByTestId('policyResponseAction');
|
||||
const statusHealth = await renderResult.findAllByTestId('policyResponseStatusHealth');
|
||||
const message = await renderResult.findAllByTestId('policyResponseMessage');
|
||||
expect(actionAccordions).not.toBeNull();
|
||||
expect(action).not.toBeNull();
|
||||
expect(statusHealth).not.toBeNull();
|
||||
expect(message).not.toBeNull();
|
||||
});
|
||||
it('should not show any numbered badges if all actions are succesful', () => {
|
||||
return renderResult.findByTestId('hostDetailsPolicyResponseAttentionBadge').catch(e => {
|
||||
expect(e).not.toBeNull();
|
||||
});
|
||||
});
|
||||
it('should show a numbered badge if at least one action failed', () => {
|
||||
reactTestingLibrary.act(() => {
|
||||
dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.failure);
|
||||
});
|
||||
const attentionBadge = renderResult.findByTestId('hostDetailsPolicyResponseAttentionBadge');
|
||||
expect(attentionBadge).not.toBeNull();
|
||||
});
|
||||
it('should show a numbered badge if at least one action has a warning', () => {
|
||||
reactTestingLibrary.act(() => {
|
||||
dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.warning);
|
||||
});
|
||||
const attentionBadge = renderResult.findByTestId('hostDetailsPolicyResponseAttentionBadge');
|
||||
expect(attentionBadge).not.toBeNull();
|
||||
});
|
||||
it('should include the back to details link', async () => {
|
||||
const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton');
|
||||
expect(subHeaderBackLink.textContent).toBe('Endpoint Details');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue