mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[endpoint] connect policy response ui to api (#66093)
[Endpoint] Connect policy response UI to API
This commit is contained in:
parent
0124977830
commit
45790d035b
12 changed files with 150 additions and 67 deletions
|
@ -32,9 +32,15 @@ interface ServerReturnedHostPolicyResponse {
|
|||
payload: GetHostPolicyResponse;
|
||||
}
|
||||
|
||||
interface ServerFailedToReturnHostPolicyResponse {
|
||||
type: 'serverFailedToReturnHostPolicyResponse';
|
||||
payload: ServerApiError;
|
||||
}
|
||||
|
||||
export type HostAction =
|
||||
| ServerReturnedHostList
|
||||
| ServerFailedToReturnHostList
|
||||
| ServerReturnedHostDetails
|
||||
| ServerFailedToReturnHostDetails
|
||||
| ServerReturnedHostPolicyResponse;
|
||||
| ServerReturnedHostPolicyResponse
|
||||
| ServerFailedToReturnHostPolicyResponse;
|
||||
|
|
|
@ -41,6 +41,9 @@ describe('HostList store concerns', () => {
|
|||
details: undefined,
|
||||
detailsLoading: false,
|
||||
detailsError: undefined,
|
||||
policyResponse: undefined,
|
||||
policyResponseLoading: false,
|
||||
policyResponseError: undefined,
|
||||
location: undefined,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<HostState> = coreStart => {
|
||||
return ({ getState, dispatch }) => next => async action => {
|
||||
|
@ -70,50 +69,28 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory<HostState> = core
|
|||
type: 'serverReturnedHostDetails',
|
||||
payload: response,
|
||||
});
|
||||
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
|
||||
},
|
||||
});
|
||||
} 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: policyResponse,
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'serverFailedToReturnHostPolicyResponse',
|
||||
payload: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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<HostState, AppAction> = (
|
|||
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<HostState> = {
|
||||
|
@ -97,8 +107,10 @@ export const hostListReducer: ImmutableReducer<HostState, AppAction> = (
|
|||
...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<HostState, AppAction> = (
|
|||
location: action.payload,
|
||||
loading: true,
|
||||
detailsLoading: true,
|
||||
policyResponseLoading: true,
|
||||
error: undefined,
|
||||
detailsError: undefined,
|
||||
policyResponseError: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +132,7 @@ export const hostListReducer: ImmutableReducer<HostState, AppAction> = (
|
|||
location: action.payload,
|
||||
error: undefined,
|
||||
detailsError: undefined,
|
||||
policyResponseError: undefined,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
|
|
|
@ -88,6 +88,11 @@ export const policyResponseActions: (
|
|||
}
|
||||
);
|
||||
|
||||
export const policyResponseLoading = (state: Immutable<HostState>): boolean =>
|
||||
state.policyResponseLoading;
|
||||
|
||||
export const policyResponseError = (state: Immutable<HostState>) => state.policyResponseError;
|
||||
|
||||
export const isOnHostPage = (state: Immutable<HostState>) =>
|
||||
state.location ? state.location.pathname === '/hosts' : false;
|
||||
|
||||
|
|
|
@ -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<EndpointAppLocation>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<{
|
|||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
{responseConfig !== undefined && responseActionStatus !== undefined ? (
|
||||
{error && (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.hostDetails.noPolicyResponse"
|
||||
defaultMessage="No policy response available"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{loading && <EuiLoadingContent lines={3} />}
|
||||
|
||||
{responseConfig !== undefined && responseActionStatus !== undefined && (
|
||||
<PolicyResponse
|
||||
responseConfig={responseConfig}
|
||||
responseActionStatus={responseActionStatus}
|
||||
responseAttentionCount={responseAttentionCount}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.hostDetails.noPolicyResponse"
|
||||
defaultMessage="No Policy Response Available"
|
||||
/>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
|
|
|
@ -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={
|
||||
<EuiText size="xs" data-test-subj="policyResponseAction">
|
||||
<EuiText
|
||||
size="xs"
|
||||
className="eui-textTruncate"
|
||||
data-test-subj="policyResponseAction"
|
||||
>
|
||||
<h4>{formatResponse(action)}</h4>
|
||||
</EuiText>
|
||||
}
|
||||
|
@ -75,6 +91,7 @@ const ResponseActions = memo(
|
|||
<EuiHealth
|
||||
color={POLICY_STATUS_TO_HEALTH_COLOR[statuses.status]}
|
||||
data-test-subj="policyResponseStatusHealth"
|
||||
className="policyResponseStatusHealth"
|
||||
>
|
||||
<EuiText size="xs">
|
||||
<p>{formatResponse(statuses.status)}</p>
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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 }
|
|
@ -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
|
||||
<EuiLink data-test-subj="hostnameCellLink" href={href} onClick={clickHandler}>
|
||||
<EuiLink
|
||||
data-test-subj="hostnameCellLink"
|
||||
className="eui-textTruncate"
|
||||
href={href}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
);
|
||||
|
@ -107,6 +110,7 @@ export const HostList = () => {
|
|||
<EuiHealth
|
||||
color={HOST_STATUS_TO_HEALTH_COLOR[hostStatus]}
|
||||
data-test-subj="rowHostStatus"
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.host.list.hostStatusValue"
|
||||
|
@ -124,7 +128,7 @@ export const HostList = () => {
|
|||
}),
|
||||
truncateText: true,
|
||||
render: () => {
|
||||
return 'Policy Name';
|
||||
return <span className="eui-textTruncate">Policy Name</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -133,7 +137,14 @@ export const HostList = () => {
|
|||
defaultMessage: 'Policy Status',
|
||||
}),
|
||||
render: () => {
|
||||
return <EuiHealth color="success">Policy Status</EuiHealth>;
|
||||
return (
|
||||
<EuiHealth color="success" className="eui-textTruncate">
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.host.list.policyStatus"
|
||||
defaultMessage="Policy Status"
|
||||
/>
|
||||
</EuiHealth>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -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 (
|
||||
<EuiToolTip content={ip.toString().replace(',', ', ')} anchorClassName="eui-fullWidth">
|
||||
<EuiText size="s" className="eui-fullWidth">
|
||||
<span className="eui-textTruncate eui-fullWidth">
|
||||
{ip.toString().replace(',', ', ')}
|
||||
</span>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'metadata.agent.version',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue