diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx new file mode 100644 index 0000000000000..dd0060f773b49 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx @@ -0,0 +1,93 @@ +/* + * 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 { EuiTabbedContent } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPanel } from '@elastic/eui'; +import React, { CSSProperties, useMemo } from 'react'; +import { EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; +import { euiStyled } from '../../../../../../../observability/public'; +import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib'; +import { InventoryItemType } from '../../../../../../common/inventory_models/types'; +import { MetricsTab } from './tabs/metrics'; +import { LogsTab } from './tabs/logs'; +import { ProcessesTab } from './tabs/processes'; +import { PropertiesTab } from './tabs/properties'; + +interface Props { + isOpen: boolean; + onClose(): void; + options: InfraWaffleMapOptions; + currentTime: number; + node: InfraWaffleMapNode; + nodeType: InventoryItemType; +} +export const NodeContextPopover = ({ + isOpen, + node, + nodeType, + currentTime, + options, + onClose, +}: Props) => { + const tabConfigs = [MetricsTab, LogsTab, ProcessesTab, PropertiesTab]; + + const tabs = useMemo(() => { + return tabConfigs.map((m) => { + const TabContent = m.content; + return { + ...m, + content: ( + + ), + }; + }); + }, [tabConfigs, node, nodeType, currentTime, options]); + + if (!isOpen) { + return null; + } + + return ( + + + + + +

{node.name}

+
+
+ + + + + +
+
+ +
+ ); +}; + +const OverlayHeader = euiStyled.div` + border-color: ${(props) => props.theme.eui.euiBorderColor}; + border-bottom-width: ${(props) => props.theme.eui.euiBorderWidthThick}; + padding: ${(props) => props.theme.eui.euiSizeS}; + padding-bottom: 0; + overflow: hidden; +`; + +const panelStyle: CSSProperties = { + position: 'absolute', + right: 10, + top: -100, + width: '50%', + maxWidth: 600, + zIndex: 2, + height: '50vh', + overflow: 'hidden', +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx new file mode 100644 index 0000000000000..1a8bc374e79a3 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx @@ -0,0 +1,21 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { TabContent, TabProps } from './shared'; + +const TabComponent = (props: TabProps) => { + return Logs Placeholder; +}; + +export const LogsTab = { + id: 'logs', + name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', { + defaultMessage: 'Logs', + }), + content: TabComponent, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics.tsx new file mode 100644 index 0000000000000..e329a5771c41d --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics.tsx @@ -0,0 +1,21 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { TabContent, TabProps } from './shared'; + +const TabComponent = (props: TabProps) => { + return Metrics Placeholder; +}; + +export const MetricsTab = { + id: 'metrics', + name: i18n.translate('xpack.infra.nodeDetails.tabs.metrics', { + defaultMessage: 'Metrics', + }), + content: TabComponent, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx new file mode 100644 index 0000000000000..94ba1150c20dd --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx @@ -0,0 +1,21 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { TabContent, TabProps } from './shared'; + +const TabComponent = (props: TabProps) => { + return Processes Placeholder; +}; + +export const ProcessesTab = { + id: 'processes', + name: i18n.translate('xpack.infra.nodeDetails.tabs.processes', { + defaultMessage: 'Processes', + }), + content: TabComponent, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties.tsx new file mode 100644 index 0000000000000..8157aca9b1410 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/properties.tsx @@ -0,0 +1,21 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { TabContent, TabProps } from './shared'; + +const TabComponent = (props: TabProps) => { + return Properties Placeholder; +}; + +export const PropertiesTab = { + id: 'properties', + name: i18n.translate('xpack.infra.nodeDetails.tabs.properties', { + defaultMessage: 'Properties', + }), + content: TabComponent, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx new file mode 100644 index 0000000000000..241ad7104836e --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx @@ -0,0 +1,20 @@ +/* + * 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 { InventoryItemType } from '../../../../../../../common/inventory_models/types'; +import { InfraWaffleMapOptions, InfraWaffleMapNode } from '../../../../../../lib/lib'; +import { euiStyled } from '../../../../../../../../observability/public'; + +export interface TabProps { + options: InfraWaffleMapOptions; + currentTime: number; + node: InfraWaffleMapNode; + nodeType: InventoryItemType; +} + +export const TabContent = euiStyled.div` + padding: ${(props) => props.theme.eui.paddingSizes.l}; +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx index cc177b895ca50..f2d9da960df81 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { first } from 'lodash'; -import { ConditionalToolTip } from './conditional_tooltip'; import { euiStyled } from '../../../../../../../observability/public'; import { InfraWaffleMapBounds, @@ -18,11 +17,14 @@ import { InfraWaffleMapOptions, } from '../../../../../lib/lib'; import { colorFromValue } from '../../lib/color_from_value'; -import { NodeContextMenu } from './node_context_menu'; import { InventoryItemType } from '../../../../../../common/inventory_models/types'; +import { NodeContextPopover } from '../node_details/overlay'; + +import { NodeContextMenu } from './node_context_menu'; const initialState = { isPopoverOpen: false, + isOverlayOpen: false, }; type State = Readonly; @@ -53,22 +55,16 @@ export const Node = class extends React.PureComponent { values: { nodeName: node.name }, }); return ( - - - + + + ); } @@ -101,6 +105,13 @@ export const Node = class extends React.PureComponent { this.setState((prevState) => ({ isPopoverOpen: !prevState.isPopoverOpen })); }; + private toggleNewOverlay = () => { + this.setState((prevState) => ({ + isPopoverOpen: !prevState.isOverlayOpen === true ? false : prevState.isPopoverOpen, + isOverlayOpen: !prevState.isOverlayOpen, + })); + }; + private closePopover = () => { if (this.state.isPopoverOpen) { this.setState({ isPopoverOpen: false }); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx index d913261521383..91c6ad801000a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx @@ -37,6 +37,7 @@ interface Props { isPopoverOpen: boolean; closePopover: () => void; popoverPosition: EuiPopoverProps['anchorPosition']; + openNewOverlay?: () => void; } export const NodeContextMenu: React.FC = withTheme( @@ -50,6 +51,7 @@ export const NodeContextMenu: React.FC = withTheme nodeType, popoverPosition, theme, + openNewOverlay, }) => { const [flyoutVisible, setFlyoutVisible] = useState(false); const inventoryModel = findInventoryModel(nodeType); @@ -159,6 +161,14 @@ export const NodeContextMenu: React.FC = withTheme }, }; + const openNewOverlayMenuItem: SectionLinkProps = { + label: i18n.translate('xpack.infra.nodeContextMenu.openNewOverlay', { + defaultMessage: '**** [NEW] Overlay ***', + }), + style: { color: theme?.eui.euiLinkColor || '#006BB4', fontWeight: 500, padding: 0 }, + onClick: openNewOverlay, + }; + return ( <> = withTheme + diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 4cafe049fcbc9..aabbb5d860b92 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -15,7 +15,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const globalNav = getService('globalNav'); - const retry = getService('retry'); describe('infrastructure security', () => { describe('global infrastructure all privileges', () => { @@ -97,24 +96,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('~waffleMap'); }); - describe('context menu', () => { - before(async () => { - await testSubjects.click('~nodeContainer'); - }); - - it(`does not show link to view logs`, async () => { - await retry.waitFor('context menu', () => testSubjects.exists('~nodeContextMenu')); - const link = await testSubjects.find('~viewLogsContextMenuItem'); - expect(await link.isEnabled()).to.be(false); - }); - - it(`does not show link to view apm traces`, async () => { - await retry.waitFor('context menu', () => testSubjects.exists('~nodeContextMenu')); - const link = await testSubjects.find('~viewApmTracesContextMenuItem'); - expect(await link.isEnabled()).to.be(false); - }); - }); - it(`doesn't show read-only badge`, async () => { await globalNav.badgeMissingOrFail(); }); @@ -213,24 +194,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('~waffleMap'); }); - describe('context menu', () => { - before(async () => { - await testSubjects.click('~nodeContainer'); - }); - - it(`does not show link to view logs`, async () => { - await retry.waitFor('context menu', () => testSubjects.exists('~nodeContextMenu')); - const link = await testSubjects.find('~viewLogsContextMenuItem'); - expect(await link.isEnabled()).to.be(false); - }); - - it(`does not show link to view apm traces`, async () => { - await retry.waitFor('context menu', () => testSubjects.exists('~nodeContextMenu')); - const link = await testSubjects.find('~viewApmTracesContextMenuItem'); - expect(await link.isEnabled()).to.be(false); - }); - }); - it(`shows read-only badge`, async () => { await globalNav.badgeExistsOrFail('Read only'); }); @@ -300,19 +263,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { after(async () => { await esArchiver.unload('infra/metrics_and_logs'); }); - - it(`context menu allows user to view logs`, async () => { - await PageObjects.common.navigateToUrlWithBrowserHistory('infraOps', '', undefined, { - ensureCurrentUrl: true, - shouldLoginIfPrompted: false, - }); - await PageObjects.infraHome.goToTime(DATE_WITH_DATA); - await testSubjects.existOrFail('~waffleMap'); - await testSubjects.click('~nodeContainer'); - await retry.waitFor('context menu', () => testSubjects.exists('nodeContextMenu')); - await testSubjects.click('~viewLogsContextMenuItem'); - await testSubjects.existOrFail('~infraLogsPage'); - }); }); }); @@ -366,19 +316,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { after(async () => { await esArchiver.unload('infra/metrics_and_logs'); }); - - it(`context menu allows user to view APM traces`, async () => { - await PageObjects.common.navigateToUrlWithBrowserHistory('infraOps', '', undefined, { - ensureCurrentUrl: true, - shouldLoginIfPrompted: false, - }); - await PageObjects.infraHome.goToTime(DATE_WITH_DATA); - await testSubjects.existOrFail('~waffleMap'); - await testSubjects.click('~nodeContainer'); - await retry.waitFor('context menu', () => testSubjects.exists('~nodeContextMenu')); - await testSubjects.click('~viewApmTracesContextMenuItem'); - await testSubjects.existOrFail('~apmMainContainer'); - }); }); });