diff --git a/x-pack/plugins/monitoring/common/types/es.ts b/x-pack/plugins/monitoring/common/types/es.ts index 013d504fa8a73..862bf575c1c6d 100644 --- a/x-pack/plugins/monitoring/common/types/es.ts +++ b/x-pack/plugins/monitoring/common/types/es.ts @@ -417,6 +417,7 @@ export interface ElasticsearchMetricbeatNode { name?: string; stats?: ElasticsearchNodeStats; master: boolean; + roles?: string[]; } export interface ElasticsearchMetricbeatSource { diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx index b258361474a61..f64951817d310 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/nodes_page.tsx @@ -66,7 +66,10 @@ export const ElasticsearchNodesPage: React.FC = ({ clusters }) = const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/nodes`; if (services.http?.fetch && clusterUuid) { setIsLoading(true); - const response = await services.http?.fetch<{ totalNodeCount: number }>(url, { + const response = await services.http?.fetch<{ + totalNodeCount: number; + nodes: Array<{ roles: string[] }>; + }>(url, { method: 'POST', body: JSON.stringify({ ccs, @@ -79,7 +82,20 @@ export const ElasticsearchNodesPage: React.FC = ({ clusters }) = }); setIsLoading(false); - setData(response); + + const { nodes } = response; + const nodesWithSortedRoles = nodes.map((node) => { + const sortedRoles = sortNodeRoles(node.roles); + return { + ...node, + roles: sortedRoles, + }; + }); + + setData({ + ...response, + nodes: nodesWithSortedRoles, + }); updateTotalItemCount(response.totalNodeCount); const alertsResponse = await fetchAlerts({ fetch: services.http.fetch, @@ -140,3 +156,35 @@ export const ElasticsearchNodesPage: React.FC = ({ clusters }) = ); }; + +function sortNodeRoles(roles: string[]): string[] | undefined { + if (!roles) { + return undefined; + } + + if (roles.length === 0) { + return roles; + } + + const roleMap: { [key: string]: string } = {}; + roles.forEach((role) => { + roleMap[role] = role; + }); + + const sortedRoles = [ + roleMap.master, + roleMap.voting_only, + roleMap.data, + roleMap.data_content, + roleMap.data_hot, + roleMap.data_warm, + roleMap.data_cold, + roleMap.data_frozen, + roleMap.ingest, + roleMap.transform, + roleMap.ml, + roleMap.remote_cluster_client, + ]; + + return sortedRoles.filter((role) => role); +} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index e38b2162c8779..b8551a6bbc887 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -5,36 +5,38 @@ * 2.0. */ -import React, { Fragment } from 'react'; -import { extractIp } from '../../../lib/extract_ip'; // TODO this is only used for elasticsearch nodes summary / node detail, so it should be moved to components/elasticsearch/nodes/lib -import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; -import { ClusterStatus } from '../cluster_status'; -import { EuiMonitoringSSPTable } from '../../table'; -import { MetricCell, OfflineCell } from './cells'; -import { SetupModeBadge } from '../../setup_mode/badge'; import { + EuiBadge, + EuiBadgeGroup, + EuiButton, + EuiCallOut, + EuiHealth, EuiIcon, EuiLink, - EuiToolTip, - EuiSpacer, EuiPage, - EuiPageContent_Deprecated as EuiPageContent, EuiPageBody, + EuiPageContent_Deprecated as EuiPageContent, EuiPanel, - EuiCallOut, - EuiButton, - EuiText, EuiScreenReaderOnly, - EuiHealth, + EuiSpacer, + EuiText, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { get } from 'lodash'; +import React, { Fragment } from 'react'; import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { ListingCallOut } from '../../setup_mode/listing_callout'; +import { SetupModeFeature } from '../../../../common/enums'; import { AlertsStatus } from '../../../alerts/status'; +import { extractIp } from '../../../lib/extract_ip'; // TODO this is only used for elasticsearch nodes summary / node detail, so it should be moved to components/elasticsearch/nodes/lib +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; -import { SetupModeFeature } from '../../../../common/enums'; +import { SetupModeBadge } from '../../setup_mode/badge'; +import { ListingCallOut } from '../../setup_mode/listing_callout'; +import { EuiMonitoringSSPTable } from '../../table'; +import { ClusterStatus } from '../cluster_status'; +import { MetricCell, OfflineCell } from './cells'; const getNodeTooltip = (node) => { const { nodeTypeLabel, nodeTypeClass } = node; @@ -177,6 +179,60 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler }, }); + cols.push({ + name: i18n.translate('xpack.monitoring.elasticsearch.nodes.rolesColumnTitle', { + defaultMessage: 'Roles', + }), + field: 'roles', + render: (roles) => { + if (!roles) { + return i18n.translate('xpack.monitoring.formatNumbers.notAvailableLabel', { + defaultMessage: 'N/A', + }); + } + + if (roles.length === 0) { + return ( + + {i18n.translate('xpack.monitoring.elasticsearch.nodes.coordinatingNodeLabel', { + defaultMessage: 'coordinating only', + })} + + ); + } + + if (roles.length > 5) { + const head = roles.slice(0, 5); + const tail = roles.slice(5, roles.length); + + return ( + + {head.map((role) => ( + {role} + ))} + + +{tail.length} + + + ); + } + + return ( + + {roles.map((role) => ( + {role} + ))} + + ); + }, + }); + cols.push({ name: i18n.translate('xpack.monitoring.elasticsearch.nodes.shardsColumnTitle', { defaultMessage: 'Shards', diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts index aaa2092e08c4f..f3713fd03a77c 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.ts @@ -11,6 +11,7 @@ import { getNodeTypeClassLabel } from '../get_node_type_class_label'; import { ElasticsearchResponseHit, ElasticsearchModifiedSource, + ElasticsearchMetricbeatNode, } from '../../../../../common/types/es'; /** @@ -52,6 +53,7 @@ export function mapNodesInfo( nodeTypeLabel, nodeTypeClass, shardCount: nodesShardCount?.nodes[uuid]?.shardCount ?? 0, + roles: (sourceNode as ElasticsearchMetricbeatNode)?.roles, }, }; }, {});