-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Infrastructure UI] Show metadata for a single host on the host UI (#…
…152956) Closes [#150893](#150893) ## Summary This PR adds a flyout with single host metadata with an option to open/close it with a click on an expand/minimize icon in the table.⚠️ This PR doesn't include metadata filtering/actions, or processes tab inside flyout (they will be added in follow-up issues). For now, the metadata will be displayed and no actions will be available. This PR will unblock [#151010](#151010), [#150907](#150907) and [#150985](#150985) ## Testing - Open the hosts view and click on the expand icon for a single host in the table <img width="1464" alt="image" src="https://user-images.githubusercontent.com/14139027/224077010-71aece78-40d1-4a3a-90a6-8e699001b37a.png"> - The flyout should be visible with a preselected metadata tab containing - Host name as the flyout title - Metadata in a table view with field and value columns <img width="1807" alt="image" src="https://user-images.githubusercontent.com/14139027/224048634-cd49aa0f-f1a5-4442-9fd0-f16cd4cb84da.png"> - The flyout can be closed using the close icon and the minimize icon or can show a different host when another host is expanded <img width="1727" alt="image" src="https://user-images.githubusercontent.com/14139027/224084969-daa525c5-4ec4-4504-b072-4711db63fe18.png"> --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: Marco Antonio Ghiani <[email protected]>
- Loading branch information
1 parent
be71713
commit 6591aa9
Showing
7 changed files
with
462 additions
and
19 deletions.
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* 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, { useMemo, useState } from 'react'; | ||
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; | ||
import { EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui'; | ||
import { MetadataTab } from './metadata/metadata'; | ||
import type { HostNodeRow } from '../../hooks/use_hosts_table'; | ||
import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; | ||
|
||
interface Props { | ||
node: HostNodeRow; | ||
closeFlyout: () => void; | ||
} | ||
|
||
const flyoutTabs = [MetadataTab]; | ||
|
||
export const Flyout = ({ node, closeFlyout }: Props) => { | ||
const { getDateRangeAsTimestamp } = useUnifiedSearchContext(); | ||
|
||
const tabs = useMemo(() => { | ||
const currentTimeRange = { | ||
...getDateRangeAsTimestamp(), | ||
interval: '1m', | ||
}; | ||
|
||
return flyoutTabs.map((m) => { | ||
const TabContent = m.content; | ||
return { | ||
...m, | ||
content: <TabContent node={node} currentTimeRange={currentTimeRange} />, | ||
}; | ||
}); | ||
}, [getDateRangeAsTimestamp, node]); | ||
|
||
const [selectedTab, setSelectedTab] = useState(0); | ||
|
||
return ( | ||
<EuiFlyout onClose={closeFlyout} ownFocus={false}> | ||
<EuiFlyoutHeader hasBorder> | ||
<EuiTitle size="xs"> | ||
<h2>{node.name}</h2> | ||
</EuiTitle> | ||
<EuiSpacer size="s" /> | ||
<EuiTabs style={{ marginBottom: '-25px' }} size="s"> | ||
{tabs.map((tab, i) => ( | ||
<EuiTab key={tab.id} isSelected={i === selectedTab} onClick={() => setSelectedTab(i)}> | ||
{tab.name} | ||
</EuiTab> | ||
))} | ||
</EuiTabs> | ||
</EuiFlyoutHeader> | ||
<EuiFlyoutBody>{tabs[selectedTab].content}</EuiFlyoutBody> | ||
</EuiFlyout> | ||
); | ||
}; |
110 changes: 110 additions & 0 deletions
110
...ins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/metadata.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
* 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, { useMemo } from 'react'; | ||
import { i18n } from '@kbn/i18n'; | ||
import { EuiLoadingChart } from '@elastic/eui'; | ||
import { EuiCallOut, EuiLink } from '@elastic/eui'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
import { useSourceContext } from '../../../../../../containers/metrics_source'; | ||
import { findInventoryModel } from '../../../../../../../common/inventory_models'; | ||
import type { InventoryItemType } from '../../../../../../../common/inventory_models/types'; | ||
import { useMetadata } from '../../../../metric_detail/hooks/use_metadata'; | ||
import { Table } from './table'; | ||
import { getAllFields } from './utils'; | ||
import type { HostNodeRow } from '../../../hooks/use_hosts_table'; | ||
import type { MetricsTimeInput } from '../../../../metric_detail/hooks/use_metrics_time'; | ||
|
||
const NODE_TYPE = 'host' as InventoryItemType; | ||
|
||
export interface TabProps { | ||
currentTimeRange: MetricsTimeInput; | ||
node: HostNodeRow; | ||
} | ||
|
||
const Metadata = ({ node, currentTimeRange }: TabProps) => { | ||
const nodeId = node.name; | ||
const inventoryModel = findInventoryModel(NODE_TYPE); | ||
const { sourceId } = useSourceContext(); | ||
const { | ||
loading: metadataLoading, | ||
error, | ||
metadata, | ||
} = useMetadata(nodeId, NODE_TYPE, inventoryModel.requiredMetrics, sourceId, currentTimeRange); | ||
|
||
const fields = useMemo(() => getAllFields(metadata), [metadata]); | ||
|
||
if (metadataLoading) { | ||
return <LoadingPlaceholder />; | ||
} | ||
|
||
if (error) { | ||
return ( | ||
<EuiCallOut | ||
title={i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.errorTitle', { | ||
defaultMessage: 'Sorry, there was an error', | ||
})} | ||
color="danger" | ||
iconType="error" | ||
> | ||
<FormattedMessage | ||
id="xpack.infra.hostsViewPage.hostDetail.metadata.errorMessage" | ||
defaultMessage="There was an error loading your data. Try to {reload} and open the host details again." | ||
values={{ | ||
reload: ( | ||
<EuiLink | ||
data-test-subj="infraMetadataThisLinkCanHelpLink" | ||
onClick={() => window.location.reload()} | ||
> | ||
{i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.errorAction', { | ||
defaultMessage: 'reload the page', | ||
})} | ||
</EuiLink> | ||
), | ||
}} | ||
/> | ||
</EuiCallOut> | ||
); | ||
} | ||
|
||
return fields.length > 0 ? ( | ||
<Table rows={fields} /> | ||
) : ( | ||
<EuiCallOut | ||
title={i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.noMetadataFound', { | ||
defaultMessage: 'Sorry, there is no metadata related to this host.', | ||
})} | ||
size="m" | ||
iconType="iInCircle" | ||
/> | ||
); | ||
}; | ||
|
||
const LoadingPlaceholder = () => { | ||
return ( | ||
<div | ||
style={{ | ||
width: '100%', | ||
height: '200px', | ||
padding: '16px', | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}} | ||
> | ||
<EuiLoadingChart size="xl" /> | ||
</div> | ||
); | ||
}; | ||
|
||
export const MetadataTab = { | ||
id: 'metadata', | ||
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', { | ||
defaultMessage: 'Metadata', | ||
}), | ||
content: Metadata, | ||
}; |
114 changes: 114 additions & 0 deletions
114
...lugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/table.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* | ||
* 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 { EuiText, EuiFlexGroup, EuiFlexItem, EuiLink, EuiBasicTable } from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
import React, { useCallback, useMemo, useState } from 'react'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
|
||
interface Row { | ||
name: string; | ||
value: string | string[] | undefined; | ||
} | ||
|
||
interface Props { | ||
rows: Row[]; | ||
} | ||
|
||
/** | ||
* Columns translations | ||
*/ | ||
const FIELD_LABEL = i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.field', { | ||
defaultMessage: 'Field', | ||
}); | ||
|
||
const VALUE_LABEL = i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.value', { | ||
defaultMessage: 'Value', | ||
}); | ||
|
||
export const Table = (props: Props) => { | ||
const { rows } = props; | ||
const columns = useMemo( | ||
() => [ | ||
{ | ||
field: 'name', | ||
name: FIELD_LABEL, | ||
width: '35%', | ||
sortable: false, | ||
render: (name: string) => <EuiText size="s">{name}</EuiText>, | ||
}, | ||
{ | ||
field: 'value', | ||
name: VALUE_LABEL, | ||
width: '65%', | ||
sortable: false, | ||
render: (_name: string, item: Row) => <ExpandableContent values={item.value} />, | ||
}, | ||
], | ||
[] | ||
); | ||
|
||
return <EuiBasicTable tableLayout={'fixed'} responsive={false} columns={columns} items={rows} />; | ||
}; | ||
|
||
interface ExpandableContentProps { | ||
values: string | string[] | undefined; | ||
} | ||
const ExpandableContent = (props: ExpandableContentProps) => { | ||
const { values } = props; | ||
const [isExpanded, setIsExpanded] = useState(false); | ||
const expand = useCallback(() => { | ||
setIsExpanded(true); | ||
}, []); | ||
|
||
const collapse = useCallback(() => { | ||
setIsExpanded(false); | ||
}, []); | ||
|
||
const list = Array.isArray(values) ? values : [values]; | ||
const [first, ...others] = list; | ||
const hasOthers = others.length > 0; | ||
const shouldShowMore = hasOthers && !isExpanded; | ||
|
||
return ( | ||
<EuiFlexGroup | ||
gutterSize={'xs'} | ||
responsive={false} | ||
alignItems={'baseline'} | ||
wrap={true} | ||
direction="column" | ||
> | ||
<div> | ||
{first} | ||
{shouldShowMore && ( | ||
<> | ||
{' ... '} | ||
<EuiLink data-test-subj="infraExpandableContentCountMoreLink" onClick={expand}> | ||
<FormattedMessage | ||
id="xpack.infra.nodeDetails.tabs.metadata.seeMore" | ||
defaultMessage="+{count} more" | ||
values={{ | ||
count: others.length, | ||
}} | ||
/> | ||
</EuiLink> | ||
</> | ||
)} | ||
</div> | ||
{isExpanded && others.map((item) => <EuiFlexItem key={item}>{item}</EuiFlexItem>)} | ||
{hasOthers && isExpanded && ( | ||
<EuiFlexItem> | ||
<EuiLink data-test-subj="infraExpandableContentShowLessLink" onClick={collapse}> | ||
{i18n.translate('xpack.infra.nodeDetails.tabs.metadata.seeLess', { | ||
defaultMessage: 'Show less', | ||
})} | ||
</EuiLink> | ||
</EuiFlexItem> | ||
)} | ||
</EuiFlexGroup> | ||
); | ||
}; |
109 changes: 109 additions & 0 deletions
109
...plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/metadata/utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* 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 type { InfraMetadata } from '../../../../../../../common/http_api'; | ||
|
||
export const getAllFields = (metadata: InfraMetadata | null) => { | ||
if (!metadata?.info) return []; | ||
return prune([ | ||
{ | ||
name: 'host.architecture', | ||
value: metadata.info.host?.architecture, | ||
}, | ||
{ | ||
name: 'host.hostname', | ||
value: metadata.info.host?.name, | ||
}, | ||
{ | ||
name: 'host.id', | ||
value: metadata.info.host?.id, | ||
}, | ||
{ | ||
name: 'host.ip', | ||
value: metadata.info.host?.ip, | ||
}, | ||
{ | ||
name: 'host.mac', | ||
value: metadata.info.host?.mac, | ||
}, | ||
{ | ||
name: 'host.name', | ||
value: metadata.info.host?.name, | ||
}, | ||
{ | ||
name: 'host.os.build', | ||
value: metadata.info.host?.os?.build, | ||
}, | ||
{ | ||
name: 'host.os.family', | ||
value: metadata.info.host?.os?.family, | ||
}, | ||
{ | ||
name: 'host.os.name', | ||
value: metadata.info.host?.os?.name, | ||
}, | ||
{ | ||
name: 'host.os.kernel', | ||
value: metadata.info.host?.os?.kernel, | ||
}, | ||
{ | ||
name: 'host.os.platform', | ||
value: metadata.info.host?.os?.platform, | ||
}, | ||
{ | ||
name: 'host.os.version', | ||
value: metadata.info.host?.os?.version, | ||
}, | ||
{ | ||
name: 'cloud.account.id', | ||
value: metadata.info.cloud?.account?.id, | ||
}, | ||
{ | ||
name: 'cloud.account.name', | ||
value: metadata.info.cloud?.account?.name, | ||
}, | ||
{ | ||
name: 'cloud.availability_zone', | ||
value: metadata.info.cloud?.availability_zone, | ||
}, | ||
{ | ||
name: 'cloud.instance.id', | ||
value: metadata.info.cloud?.instance?.id, | ||
}, | ||
{ | ||
name: 'cloud.instance.name', | ||
value: metadata.info.cloud?.instance?.name, | ||
}, | ||
{ | ||
name: 'cloud.machine.type', | ||
value: metadata.info.cloud?.machine?.type, | ||
}, | ||
{ | ||
name: 'cloud.provider', | ||
value: metadata.info.cloud?.provider, | ||
}, | ||
{ | ||
name: 'cloud.region', | ||
value: metadata.info.cloud?.region, | ||
}, | ||
{ | ||
name: 'agent.id', | ||
value: metadata.info.agent?.id, | ||
}, | ||
{ | ||
name: 'agent.version', | ||
value: metadata.info.agent?.version, | ||
}, | ||
{ | ||
name: 'agent.policy', | ||
value: metadata.info.agent?.policy, | ||
}, | ||
]); | ||
}; | ||
|
||
const prune = (fields: Array<{ name: string; value: string | string[] | undefined }>) => | ||
fields.filter((f) => !!f.value); |
Oops, something went wrong.