mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Endpoint Responder] CallOut for offline endpoints (#135965)
This commit is contained in:
parent
000f39b4a0
commit
f3eedfb565
5 changed files with 123 additions and 8 deletions
|
@ -46,6 +46,7 @@ export const ConsolePageOverlay = memo<ConsolePageOverlayProps>(
|
|||
|
||||
return {
|
||||
pageTitle,
|
||||
pageBody: body,
|
||||
headerHasBottomBorder: false,
|
||||
'data-test-subj': getTestId('layout'),
|
||||
headerBackComponent: (
|
||||
|
@ -74,7 +75,7 @@ export const ConsolePageOverlay = memo<ConsolePageOverlayProps>(
|
|||
...(actions ?? []),
|
||||
],
|
||||
};
|
||||
}, [actions, getTestId, handleCloseOverlayOnClick, isHidden, pageTitle]);
|
||||
}, [actions, getTestId, handleCloseOverlayOnClick, isHidden, pageTitle, body]);
|
||||
|
||||
return (
|
||||
<PageOverlay
|
||||
|
@ -84,11 +85,7 @@ export const ConsolePageOverlay = memo<ConsolePageOverlayProps>(
|
|||
paddingSize="l"
|
||||
enableScrolling={false}
|
||||
>
|
||||
<PageLayout {...layoutProps}>
|
||||
{body}
|
||||
|
||||
{console}
|
||||
</PageLayout>
|
||||
<PageLayout {...layoutProps}>{console}</PageLayout>
|
||||
</PageOverlay>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ const EuiPanelStyled = styled(EuiPanel)`
|
|||
export type PageLayoutProps = PropsWithChildren<{
|
||||
pageTitle?: ReactNode;
|
||||
pageDescription?: ReactNode;
|
||||
pageBody?: ReactNode;
|
||||
actions?: ReactNode | ReactNode[];
|
||||
headerHasBottomBorder?: boolean;
|
||||
restrictWidth?: boolean | number | string;
|
||||
|
@ -52,6 +53,7 @@ export const PageLayout = memo<PageLayoutProps>(
|
|||
({
|
||||
pageTitle,
|
||||
pageDescription,
|
||||
pageBody,
|
||||
actions,
|
||||
headerHasBottomBorder,
|
||||
restrictWidth,
|
||||
|
@ -75,7 +77,7 @@ export const PageLayout = memo<PageLayoutProps>(
|
|||
};
|
||||
}, []);
|
||||
|
||||
const bodyClassName = useMemo(() => {
|
||||
const consoleBodyClassName = useMemo(() => {
|
||||
return classnames({
|
||||
'is-scrollable': scrollableBody,
|
||||
'is-not-scrollable': !scrollableBody,
|
||||
|
@ -132,7 +134,12 @@ export const PageLayout = memo<PageLayoutProps>(
|
|||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow className={bodyClassName} data-test-subj={getTestId('body')}>
|
||||
<EuiFlexItem grow={false}>{pageBody}</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow
|
||||
className={consoleBodyClassName}
|
||||
data-test-subj={getTestId('consoleBody')}
|
||||
>
|
||||
<div role="main" className="full-height">
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { HostInfo } from '../../../../common/endpoint/types';
|
||||
import { HostStatus } from '../../../../common/endpoint/types';
|
||||
import type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import { useGetEndpointDetails } from '../../hooks/endpoint/use_get_endpoint_details';
|
||||
import { mockEndpointDetailsApiResult } from '../../pages/endpoint_hosts/store/mock_endpoint_result_list';
|
||||
import { OfflineCallout } from './offline_callout';
|
||||
|
||||
jest.mock('../../hooks/endpoint/use_get_endpoint_details');
|
||||
|
||||
const getEndpointDetails = useGetEndpointDetails as jest.Mock;
|
||||
|
||||
describe('Responder offline callout', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let mockedContext: AppContextTestRender;
|
||||
let endpointDetails: HostInfo;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
render = () => (renderResult = mockedContext.render(<OfflineCallout endpointId={'1234'} />));
|
||||
endpointDetails = mockEndpointDetailsApiResult();
|
||||
getEndpointDetails.mockReturnValue({ data: endpointDetails });
|
||||
render();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should be visible when endpoint is offline', () => {
|
||||
getEndpointDetails.mockReturnValue({
|
||||
data: { ...endpointDetails, host_status: HostStatus.OFFLINE },
|
||||
});
|
||||
render();
|
||||
const callout = renderResult.queryByTestId('offlineCallout');
|
||||
expect(callout).toBeTruthy();
|
||||
});
|
||||
it('should not be visible when endpoint is online', () => {
|
||||
getEndpointDetails.mockReturnValue({
|
||||
data: { ...endpointDetails, host_status: HostStatus.HEALTHY },
|
||||
});
|
||||
render();
|
||||
const callout = renderResult.queryByTestId('offlineCallout');
|
||||
expect(callout).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useGetEndpointDetails } from '../../hooks';
|
||||
import { HostStatus } from '../../../../common/endpoint/types';
|
||||
|
||||
interface OfflineCalloutProps {
|
||||
endpointId: string;
|
||||
}
|
||||
|
||||
export const OfflineCallout = memo<OfflineCalloutProps>(({ endpointId }) => {
|
||||
const { data: endpointDetails } = useGetEndpointDetails(endpointId, {
|
||||
refetchInterval: 10000,
|
||||
});
|
||||
|
||||
if (!endpointDetails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (endpointDetails.host_status === HostStatus.OFFLINE) {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
iconType="offline"
|
||||
color="warning"
|
||||
data-test-subj="offlineCallout"
|
||||
title={i18n.translate('xpack.securitySolution.responder.hostOffline.callout.title', {
|
||||
defaultMessage: 'Host Offline',
|
||||
})}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.responder.hostOffline.callout.body"
|
||||
defaultMessage="The host {name} is offline, so its responses may be delayed. Pending commands will execute when the host reconnects."
|
||||
values={{ name: <strong>{endpointDetails.metadata.host.name}</strong> }}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
OfflineCallout.displayName = 'OfflineCallout';
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { useConsoleManager } from '../../components/console';
|
||||
import type { HostMetadata } from '../../../../common/endpoint/types';
|
||||
import { HeaderEndpointInfo } from '../../components/endpoint_responder/header_endpoint_info';
|
||||
import { OfflineCallout } from '../../components/endpoint_responder/offline_callout';
|
||||
|
||||
type ShowEndpointResponseActionsConsole = (endpointMetadata: HostMetadata) => void;
|
||||
|
||||
|
@ -52,6 +53,7 @@ export const useWithShowEndpointResponder = (): ShowEndpointResponseActionsConso
|
|||
TitleComponent: () => <HeaderEndpointInfo endpointId={endpointAgentId} />,
|
||||
},
|
||||
PageTitleComponent: () => <>{RESPONDER_PAGE_TITLE}</>,
|
||||
PageBodyComponent: () => <OfflineCallout endpointId={endpointAgentId} />,
|
||||
ActionComponents: [ActionLogButton],
|
||||
})
|
||||
.show();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue