mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution][Endpoint][Responder] Endpoint responder console host info header (#134278)
This commit is contained in:
parent
a8b62ea157
commit
bb485f893a
4 changed files with 174 additions and 4 deletions
|
@ -31,12 +31,18 @@ export const ConsoleHeader = memo<ConsoleHeaderProps>(({ TitleComponent }) => {
|
|||
}, [dispatch, isHelpOpen]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow className="eui-textTruncate">
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={1} className="eui-textTruncate">
|
||||
{TitleComponent ? <TitleComponent /> : ''}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiButtonIcon
|
||||
style={{ marginLeft: 'auto' }}
|
||||
onClick={handleHelpButtonOnClick}
|
||||
iconType="help"
|
||||
title={HELP_LABEL}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator';
|
||||
import { HostInfo } from '../../../../common/endpoint/types';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import { useGetEndpointDetails } from '../../hooks/endpoint/use_get_endpoint_details';
|
||||
import { useGetEndpointPendingActionsSummary } from '../../hooks/endpoint/use_get_endpoint_pending_actions_summary';
|
||||
import { mockEndpointDetailsApiResult } from '../../pages/endpoint_hosts/store/mock_endpoint_result_list';
|
||||
import { HeaderEndpointInfo } from './header_endpoint_info';
|
||||
|
||||
jest.mock('../../hooks/endpoint/use_get_endpoint_details');
|
||||
jest.mock('../../hooks/endpoint/use_get_endpoint_pending_actions_summary');
|
||||
|
||||
const getEndpointDetails = useGetEndpointDetails as jest.Mock;
|
||||
const getPendingActions = useGetEndpointPendingActionsSummary as jest.Mock;
|
||||
|
||||
describe('Responder header endpoint info', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let mockedContext: AppContextTestRender;
|
||||
let endpointDetails: HostInfo;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<HeaderEndpointInfo endpointId={'1234'} />));
|
||||
endpointDetails = mockEndpointDetailsApiResult();
|
||||
getEndpointDetails.mockReturnValue({ data: endpointDetails });
|
||||
getPendingActions.mockReturnValue({
|
||||
data: {
|
||||
data: [
|
||||
new EndpointActionGenerator('seed').generateAgentPendingActionsSummary({
|
||||
agent_id: '1234',
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
render();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should show endpoint name', async () => {
|
||||
const name = await renderResult.findByTestId('responderHeaderEndpointName');
|
||||
expect(name.textContent).toBe(`ENDPOINT ${endpointDetails.metadata.host.name}`);
|
||||
});
|
||||
it('should show agent and isolation status', async () => {
|
||||
const agentStatus = await renderResult.findByTestId(
|
||||
'responderHeaderEndpointAgentIsolationStatus'
|
||||
);
|
||||
expect(agentStatus.textContent).toBe(`UnhealthyIsolating`);
|
||||
});
|
||||
it('should show last updated time', async () => {
|
||||
const lastUpdated = await renderResult.findByTestId('responderHeaderLastSeen');
|
||||
expect(lastUpdated).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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, useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingContent, EuiToolTip } from '@elastic/eui';
|
||||
import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react';
|
||||
import { useGetEndpointDetails } from '../../hooks/endpoint/use_get_endpoint_details';
|
||||
import { useGetEndpointPendingActionsSummary } from '../../hooks/endpoint/use_get_endpoint_pending_actions_summary';
|
||||
import { EndpointHostIsolationStatusProps } from '../../../common/components/endpoint/host_isolation';
|
||||
import { EndpointAgentAndIsolationStatus } from '../endpoint_agent_and_isolation_status';
|
||||
|
||||
interface HeaderEndpointInfoProps {
|
||||
endpointId: string;
|
||||
}
|
||||
|
||||
export const HeaderEndpointInfo = memo<HeaderEndpointInfoProps>(({ endpointId }) => {
|
||||
const { data: endpointDetails, isFetching } = useGetEndpointDetails(endpointId, {
|
||||
refetchInterval: 10000,
|
||||
});
|
||||
const { data: endpointPendingActions } = useGetEndpointPendingActionsSummary([endpointId], {
|
||||
refetchInterval: 10000,
|
||||
});
|
||||
|
||||
const pendingIsolationActions = useMemo<
|
||||
Pick<Required<EndpointHostIsolationStatusProps>, 'pendingIsolate' | 'pendingUnIsolate'>
|
||||
>(() => {
|
||||
if (endpointPendingActions?.data.length) {
|
||||
const pendingActions = endpointPendingActions.data[0].pending_actions;
|
||||
|
||||
return {
|
||||
pendingIsolate: pendingActions.isolate ?? 0,
|
||||
pendingUnIsolate: pendingActions.unisolate ?? 0,
|
||||
};
|
||||
}
|
||||
return {
|
||||
pendingIsolate: 0,
|
||||
pendingUnIsolate: 0,
|
||||
};
|
||||
}, [endpointPendingActions?.data]);
|
||||
|
||||
if (isFetching && endpointPendingActions === undefined) {
|
||||
return <EuiLoadingContent lines={2} />;
|
||||
}
|
||||
|
||||
if (!endpointDetails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="xs">
|
||||
<EuiFlexItem grow={false} className="eui-textTruncate">
|
||||
<EuiToolTip
|
||||
content={endpointDetails.metadata.host.name}
|
||||
anchorClassName="eui-textTruncate"
|
||||
>
|
||||
<EuiText size="s" data-test-subj="responderHeaderEndpointName">
|
||||
<h6 className="eui-textTruncate">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.responder.header.endpointName"
|
||||
defaultMessage="ENDPOINT {name}"
|
||||
values={{ name: endpointDetails.metadata.host.name }}
|
||||
/>
|
||||
</h6>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EndpointAgentAndIsolationStatus
|
||||
status={endpointDetails.host_status}
|
||||
isIsolated={endpointDetails.metadata.Endpoint.state?.isolation}
|
||||
{...pendingIsolationActions}
|
||||
data-test-subj="responderHeaderEndpointAgentIsolationStatus"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued" size="s" data-test-subj="responderHeaderLastSeen">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.responder.header.lastSeen"
|
||||
defaultMessage="Last seen {date}"
|
||||
values={{
|
||||
date: <FormattedRelative value={endpointDetails.metadata['@timestamp']} />,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
HeaderEndpointInfo.displayName = 'HeaderEndpointInfo';
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '../../components/endpoint_responder';
|
||||
import { useConsoleManager } from '../../components/console';
|
||||
import type { HostMetadata } from '../../../../common/endpoint/types';
|
||||
import { HeaderEndpointInfo } from '../../components/endpoint_responder/header_endpoint_info';
|
||||
|
||||
type ShowEndpointResponseActionsConsole = (endpointMetadata: HostMetadata) => void;
|
||||
|
||||
|
@ -40,7 +41,8 @@ export const useWithShowEndpointResponder = (): ShowEndpointResponseActionsConso
|
|||
consoleProps: {
|
||||
commands: getEndpointResponseActionsConsoleCommands(endpointAgentId),
|
||||
'data-test-subj': 'endpointResponseActionsConsole',
|
||||
TitleComponent: () => <>{endpointMetadata.host.name}</>,
|
||||
prompt: `endpoint-${endpointMetadata.agent.version}`,
|
||||
TitleComponent: () => <HeaderEndpointInfo endpointId={endpointAgentId} />,
|
||||
},
|
||||
PageTitleComponent: () => <>{RESPONDER_PAGE_TITLE}</>,
|
||||
ActionComponents: [ActionLogButton],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue