mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Add Host risk information flyout to Host risk KPI panel (#121075)
* Create HoverVisibilityContainer component * Add Host risk information flyout to Host rik KPI panel * Update snapshot test * Fix cypress test * Code review improvements Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e1a86bac32
commit
653cbe1484
14 changed files with 3012 additions and 2617 deletions
|
@ -10,7 +10,7 @@ import { loginAndWaitForPage } from '../../tasks/login';
|
|||
import { HOSTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('RiskyHosts KPI', () => {
|
||||
it('it renders', () => {
|
||||
it('renders', () => {
|
||||
loginAndWaitForPage(HOSTS_URL);
|
||||
|
||||
cy.get('[data-test-subj="riskyHostsTotal"]').should('have.text', '0 Risky Hosts');
|
||||
|
|
|
@ -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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../mock';
|
||||
|
||||
import { HoverVisibilityContainer } from '.';
|
||||
|
||||
describe('HoverVisibilityContainer', () => {
|
||||
const targetClass1 = 'Component1';
|
||||
const targetClass2 = 'Component2';
|
||||
const Component1 = () => <div className={targetClass1} />;
|
||||
const Component2 = () => <div className={targetClass2} />;
|
||||
|
||||
test('it renders a transparent inspect button by default', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HoverVisibilityContainer targetClassNames={[targetClass1, targetClass2]}>
|
||||
<Component1 />
|
||||
<Component2 />
|
||||
</HoverVisibilityContainer>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`HoverVisibilityContainer`)).toHaveStyleRule('opacity', '0', {
|
||||
modifier: `.${targetClass1}`,
|
||||
});
|
||||
expect(wrapper.find(`HoverVisibilityContainer`)).toHaveStyleRule('opacity', '1', {
|
||||
modifier: `:hover .${targetClass2}`,
|
||||
});
|
||||
});
|
||||
|
||||
test('it renders an opaque inspect button when it has mouse focus', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HoverVisibilityContainer targetClassNames={[targetClass1, targetClass2]}>
|
||||
<Component1 />
|
||||
<Component2 />
|
||||
</HoverVisibilityContainer>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`HoverVisibilityContainer`)).toHaveStyleRule('opacity', '1', {
|
||||
modifier: `:hover .${targetClass1}`,
|
||||
});
|
||||
expect(wrapper.find(`HoverVisibilityContainer`)).toHaveStyleRule('opacity', '1', {
|
||||
modifier: `:hover .${targetClass2}`,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 styled, { css } from 'styled-components';
|
||||
import { getOr } from 'lodash/fp';
|
||||
|
||||
const StyledDiv = styled.div<{ targetClassNames: string[] }>`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
> * {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
${({ targetClassNames }) =>
|
||||
css`
|
||||
${targetClassNames.map((cn) => `.${cn}`).join(', ')} {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity ${(props) => getOr(250, 'theme.eui.euiAnimSpeedNormal', props)} ease;
|
||||
}
|
||||
|
||||
${targetClassNames.map((cn) => `&:hover .${cn}`).join(', ')} {
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
interface HoverVisibilityContainerProps {
|
||||
show?: boolean;
|
||||
children: React.ReactNode;
|
||||
targetClassNames: string[];
|
||||
}
|
||||
|
||||
export const HoverVisibilityContainer = React.memo<HoverVisibilityContainerProps>(
|
||||
({ show = true, targetClassNames, children }) => {
|
||||
if (!show) return <>{children}</>;
|
||||
|
||||
return (
|
||||
<StyledDiv data-test-subj="hoverVisibilityContainer" targetClassNames={targetClassNames}>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
HoverVisibilityContainer.displayName = 'HoverVisibilityContainer';
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { createStore, State } from '../../store';
|
||||
import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers';
|
||||
|
||||
import { InspectButton, InspectButtonContainer, BUTTON_CLASS } from '.';
|
||||
import { InspectButton } from '.';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
describe('Inspect Button', () => {
|
||||
|
@ -103,36 +103,6 @@ describe('Inspect Button', () => {
|
|||
);
|
||||
expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
describe('InspectButtonContainer', () => {
|
||||
test('it renders a transparent inspect button by default', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders store={store}>
|
||||
<InspectButtonContainer>
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</InspectButtonContainer>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`InspectButtonContainer`)).toHaveStyleRule('opacity', '0', {
|
||||
modifier: `.${BUTTON_CLASS}`,
|
||||
});
|
||||
});
|
||||
|
||||
test('it renders an opaque inspect button when it has mouse focus', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders store={store}>
|
||||
<InspectButtonContainer>
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</InspectButtonContainer>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`InspectButtonContainer`)).toHaveStyleRule('opacity', '1', {
|
||||
modifier: `:hover .${BUTTON_CLASS}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Modal Inspect - happy path', () => {
|
||||
|
|
|
@ -6,50 +6,34 @@
|
|||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
|
||||
import { getOr, omit } from 'lodash/fp';
|
||||
import { omit } from 'lodash/fp';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { inputsSelectors, State } from '../../store';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
import { inputsActions } from '../../store/inputs';
|
||||
|
||||
import { HoverVisibilityContainer } from '../hover_visibility_container';
|
||||
|
||||
import { ModalInspectQuery } from './modal';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const BUTTON_CLASS = 'inspectButtonComponent';
|
||||
|
||||
export const InspectButtonContainer = styled.div<{ show?: boolean }>`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
interface InspectButtonContainerProps {
|
||||
show?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
> * {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.${BUTTON_CLASS} {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity ${(props) => getOr(250, 'theme.eui.euiAnimSpeedNormal', props)} ease;
|
||||
}
|
||||
|
||||
${({ show }) =>
|
||||
show &&
|
||||
css`
|
||||
&:hover .${BUTTON_CLASS} {
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
InspectButtonContainer.displayName = 'InspectButtonContainer';
|
||||
|
||||
InspectButtonContainer.defaultProps = {
|
||||
show: true,
|
||||
};
|
||||
export const InspectButtonContainer: React.FC<InspectButtonContainerProps> = ({
|
||||
children,
|
||||
show = true,
|
||||
}) => (
|
||||
<HoverVisibilityContainer show={show} targetClassNames={[BUTTON_CLASS]}>
|
||||
{children}
|
||||
</HoverVisibilityContainer>
|
||||
);
|
||||
|
||||
interface OwnProps {
|
||||
compact?: boolean;
|
||||
|
|
|
@ -263,8 +263,13 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
|
|||
);
|
||||
|
||||
return (
|
||||
<InspectButtonContainer data-test-subj="alerts-histogram-panel" show={!isInitialLoading}>
|
||||
<KpiPanel height={PANEL_HEIGHT} hasBorder paddingSize={paddingSize}>
|
||||
<InspectButtonContainer show={!isInitialLoading}>
|
||||
<KpiPanel
|
||||
height={PANEL_HEIGHT}
|
||||
hasBorder
|
||||
paddingSize={paddingSize}
|
||||
data-test-subj="alerts-histogram-panel"
|
||||
>
|
||||
<HeaderSection
|
||||
id={uniqueQueryId}
|
||||
title={titleText}
|
||||
|
|
|
@ -99,4 +99,14 @@ describe('HostRiskScore', () => {
|
|||
context
|
||||
);
|
||||
});
|
||||
|
||||
it("doesn't render background-color when hideBackgroundColor is true", () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<HostRiskScore severity={HostRiskSeverity.critical} hideBackgroundColor />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByTestId('host-risk-score')).toHaveStyleRule('background-color', undefined);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,13 +21,14 @@ const HOST_RISK_SEVERITY_COLOUR = {
|
|||
Critical: euiLightVars.euiColorDanger,
|
||||
};
|
||||
|
||||
const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity }>`
|
||||
${({ theme, $severity }) => css`
|
||||
const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity; $hideBackgroundColor: boolean }>`
|
||||
${({ theme, $severity, $hideBackgroundColor }) => css`
|
||||
width: fit-content;
|
||||
padding-right: ${theme.eui.paddingSizes.s};
|
||||
padding-left: ${theme.eui.paddingSizes.xs};
|
||||
|
||||
${($severity === 'Critical' || $severity === 'High') &&
|
||||
!$hideBackgroundColor &&
|
||||
css`
|
||||
background-color: ${transparentize(theme.eui.euiColorDanger, 0.2)};
|
||||
border-radius: 999px; // pill shaped
|
||||
|
@ -35,8 +36,16 @@ const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity }>`
|
|||
`}
|
||||
`;
|
||||
|
||||
export const HostRiskScore: React.FC<{ severity: HostRiskSeverity }> = ({ severity }) => (
|
||||
<HostRiskBadge color={euiLightVars.euiColorDanger} $severity={severity}>
|
||||
export const HostRiskScore: React.FC<{
|
||||
severity: HostRiskSeverity;
|
||||
hideBackgroundColor?: boolean;
|
||||
}> = ({ severity, hideBackgroundColor = false }) => (
|
||||
<HostRiskBadge
|
||||
color={euiLightVars.euiColorDanger}
|
||||
$severity={severity}
|
||||
$hideBackgroundColor={hideBackgroundColor}
|
||||
data-test-subj="host-risk-score"
|
||||
>
|
||||
<EuiHealth className="eui-alignMiddle" color={HOST_RISK_SEVERITY_COLOUR[severity]}>
|
||||
{severity}
|
||||
</EuiHealth>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { render, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { HostRiskInformation } from '.';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
|
||||
describe('Host Risk Flyout', () => {
|
||||
it('renders', () => {
|
||||
const { queryByTestId } = render(<HostRiskInformation />);
|
||||
|
||||
expect(queryByTestId('open-risk-information-flyout')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens and displays table with 5 rows', () => {
|
||||
const NUMBER_OF_ROWS = 1 + 5; // 1 header row + 5 severity rows
|
||||
const { getByTestId, queryByTestId, queryAllByRole } = render(
|
||||
<TestProviders>
|
||||
<HostRiskInformation />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('open-risk-information-flyout'));
|
||||
|
||||
expect(queryByTestId('risk-information-table')).toBeInTheDocument();
|
||||
expect(queryAllByRole('row')).toHaveLength(NUMBER_OF_ROWS);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 {
|
||||
useGeneratedHtmlId,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiLink,
|
||||
EuiBasicTable,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutFooter,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiBasicTableColumn,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { HostRiskSeverity } from '../../../../common/search_strategy';
|
||||
import { RISKY_HOSTS_DOC_LINK } from '../../../overview/components/overview_risky_host_links/risky_hosts_disabled_module';
|
||||
import { HostRiskScore } from '../common/host_risk_score';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const tableColumns: Array<EuiBasicTableColumn<TableItem>> = [
|
||||
{
|
||||
field: 'classification',
|
||||
name: i18n.INFORMATION_CLASSIFICATION_HEADER,
|
||||
render: (riskScore?: HostRiskSeverity) => {
|
||||
if (riskScore != null) {
|
||||
return <HostRiskScore severity={riskScore} hideBackgroundColor />;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'range',
|
||||
name: i18n.INFORMATION_RISK_HEADER,
|
||||
},
|
||||
];
|
||||
|
||||
interface TableItem {
|
||||
range?: string;
|
||||
classification: HostRiskSeverity;
|
||||
}
|
||||
|
||||
const tableItems: TableItem[] = [
|
||||
{ classification: HostRiskSeverity.critical, range: i18n.CRITICAL_RISK_DESCRIPTION },
|
||||
{ classification: HostRiskSeverity.high, range: '70 - 90 ' },
|
||||
{ classification: HostRiskSeverity.moderate, range: '40 - 70' },
|
||||
{ classification: HostRiskSeverity.low, range: '20 - 40' },
|
||||
{ classification: HostRiskSeverity.unknown, range: i18n.UNKNOWN_RISK_DESCRIPTION },
|
||||
];
|
||||
|
||||
export const HOST_RISK_INFO_BUTTON_CLASS = 'HostRiskInformation__button';
|
||||
|
||||
export const HostRiskInformation = () => {
|
||||
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
|
||||
|
||||
const handleOnClose = useCallback(() => {
|
||||
setIsFlyoutVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleOnOpen = useCallback(() => {
|
||||
setIsFlyoutVisible(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
iconSize="m"
|
||||
iconType="iInCircle"
|
||||
aria-label={i18n.INFORMATION_ARIA_LABEL}
|
||||
onClick={handleOnOpen}
|
||||
className={HOST_RISK_INFO_BUTTON_CLASS}
|
||||
data-test-subj="open-risk-information-flyout"
|
||||
/>
|
||||
{isFlyoutVisible && <HostRiskInformationFlyout handleOnClose={handleOnClose} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const HostRiskInformationFlyout = ({ handleOnClose }: { handleOnClose: () => void }) => {
|
||||
const simpleFlyoutTitleId = useGeneratedHtmlId({
|
||||
prefix: 'HostRiskInformation',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlyout ownFocus onClose={handleOnClose} aria-labelledby={simpleFlyoutTitleId} size={450}>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={simpleFlyoutTitleId}>{i18n.TITLE}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiText size="s">
|
||||
<p>{i18n.INTRODUCTION}</p>
|
||||
<p>{i18n.EXPLANATION_MESSAGE}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiBasicTable
|
||||
columns={tableColumns}
|
||||
items={tableItems}
|
||||
data-test-subj="risk-information-table"
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hosts.hostRiskInformation.learnMore"
|
||||
defaultMessage="You can learn more about host risk {hostsRiskScoreDocumentationLink}"
|
||||
values={{
|
||||
hostsRiskScoreDocumentationLink: (
|
||||
<EuiLink href={RISKY_HOSTS_DOC_LINK} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hosts.hostRiskInformation.link"
|
||||
defaultMessage="here"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={handleOnClose}>{i18n.CLOSE_BUTTON_LTEXT}</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const INFORMATION_ARIA_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.informationAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Information',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_CLASSIFICATION_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.classificationHeader',
|
||||
{
|
||||
defaultMessage: 'Classification',
|
||||
}
|
||||
);
|
||||
|
||||
export const INFORMATION_RISK_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.riskHeader',
|
||||
{
|
||||
defaultMessage: 'Host risk score range',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNKNOWN_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.unknownRiskDescription',
|
||||
{
|
||||
defaultMessage: 'Less than 20',
|
||||
}
|
||||
);
|
||||
|
||||
export const CRITICAL_RISK_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.criticalRiskDescription',
|
||||
{
|
||||
defaultMessage: '90 and above',
|
||||
}
|
||||
);
|
||||
|
||||
export const TITLE = i18n.translate('xpack.securitySolution.hosts.hostRiskInformation.title', {
|
||||
defaultMessage: 'How is host risk calculated?',
|
||||
});
|
||||
|
||||
export const INTRODUCTION = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.introduction',
|
||||
{
|
||||
defaultMessage:
|
||||
'The Host Risk Score capability surfaces risky hosts from within your environment.',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPLANATION_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.explanation',
|
||||
{
|
||||
defaultMessage:
|
||||
'This feature utilizes a transform, with a scripted metric aggregation to calculate host risk scores based on detection rule alerts with an "open" status, within a 5 day time window. The transform runs hourly to keep the score updated as new detection rule alerts stream in.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLOSE_BUTTON_LTEXT = i18n.translate(
|
||||
'xpack.securitySolution.hosts.hostRiskInformation.closeBtn',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
);
|
|
@ -17,7 +17,11 @@ import {
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { euiLightVars } from '@kbn/ui-shared-deps-src/theme';
|
||||
import { InspectButtonContainer, InspectButton } from '../../../../common/components/inspect';
|
||||
import {
|
||||
InspectButton,
|
||||
BUTTON_CLASS as INPECT_BUTTON_CLASS,
|
||||
} from '../../../../common/components/inspect';
|
||||
|
||||
import { HostsKpiBaseComponentLoader } from '../common';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -29,6 +33,8 @@ import {
|
|||
import { useInspectQuery } from '../../../../common/hooks/use_inspect_query';
|
||||
import { useErrorToast } from '../../../../common/hooks/use_error_toast';
|
||||
import { HostRiskScore } from '../../common/host_risk_score';
|
||||
import { HostRiskInformation, HOST_RISK_INFO_BUTTON_CLASS } from '../../host_risk_information';
|
||||
import { HoverVisibilityContainer } from '../../../../common/components/hover_visibility_container';
|
||||
|
||||
const QUERY_ID = 'hostsKpiRiskyHostsQuery';
|
||||
|
||||
|
@ -63,7 +69,7 @@ const RiskyHostsComponent: React.FC<{
|
|||
const totalCount = criticalRiskCount + hightlRiskCount;
|
||||
|
||||
return (
|
||||
<InspectButtonContainer>
|
||||
<HoverVisibilityContainer targetClassNames={[INPECT_BUTTON_CLASS, HOST_RISK_INFO_BUTTON_CLASS]}>
|
||||
<EuiPanel hasBorder data-test-subj="risky-hosts">
|
||||
<EuiFlexGroup gutterSize={'none'}>
|
||||
<EuiFlexItem>
|
||||
|
@ -72,7 +78,16 @@ const RiskyHostsComponent: React.FC<{
|
|||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{data?.inspect && <InspectButton queryId={QUERY_ID} title={i18n.INSPECT_RISKY_HOSTS} />}
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<HostRiskInformation />
|
||||
</EuiFlexItem>
|
||||
{data?.inspect && (
|
||||
<EuiFlexItem>
|
||||
<InspectButton queryId={QUERY_ID} title={i18n.INSPECT_RISKY_HOSTS} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
|
@ -117,7 +132,7 @@ const RiskyHostsComponent: React.FC<{
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</InspectButtonContainer>
|
||||
</HoverVisibilityContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue