[Monitoring] Display node roles in Nodes table (#152127)

Fixes #151818 

<img width="2863" alt="Screenshot 2023-02-24 at 17 29 44"
src="https://user-images.githubusercontent.com/2564140/221235055-5742b3d3-522d-4677-9c2d-18bcd860d0b8.png">

Related PR: https://github.com/elastic/beats/pull/34668

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Milton Hultgren 2023-03-20 15:43:40 +01:00 committed by GitHub
parent d762a2a6bb
commit 118609b18d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 146 additions and 26 deletions

View file

@ -417,6 +417,7 @@ export interface ElasticsearchMetricbeatNode {
name?: string;
stats?: ElasticsearchNodeStats;
master: boolean;
roles?: string[];
}
export interface ElasticsearchMetricbeatSource {

View file

@ -29,6 +29,35 @@ import {
RULE_MISSING_MONITORING_DATA,
} from '../../../../common/constants';
type ElasticsearchNodeRole =
| 'master'
| 'voting_only'
| 'data'
| 'data_content'
| 'data_hot'
| 'data_warm'
| 'data_cold'
| 'data_frozen'
| 'ingest'
| 'transform'
| 'ml'
| 'remote_cluster_client';
const rolesByImportance: ElasticsearchNodeRole[] = [
'master',
'voting_only',
'data',
'data_content',
'data_hot',
'data_warm',
'data_cold',
'data_frozen',
'ingest',
'transform',
'ml',
'remote_cluster_client',
];
export const ElasticsearchNodesPage: React.FC<ComponentProps> = ({ clusters }) => {
const globalState = useContext(GlobalStateContext);
const { showCgroupMetricsElasticsearch } = useContext(ExternalConfigContext);
@ -66,7 +95,10 @@ export const ElasticsearchNodesPage: React.FC<ComponentProps> = ({ 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 +111,20 @@ export const ElasticsearchNodesPage: React.FC<ComponentProps> = ({ 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 +185,16 @@ export const ElasticsearchNodesPage: React.FC<ComponentProps> = ({ clusters }) =
</ElasticsearchTemplate>
);
};
function sortNodeRoles(roles: string[] | undefined): string[] | undefined {
if (!roles) {
return undefined;
}
if (roles.length === 0) {
return [];
}
const rolesAsSet = new Set(roles);
return rolesByImportance.filter((role) => rolesAsSet.has(role));
}

View file

@ -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 { get } from 'lodash';
import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants';
import { FormattedMessage } from '@kbn/i18n-react';
import { ListingCallOut } from '../../setup_mode/listing_callout';
import { AlertsStatus } from '../../../alerts/status';
import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode';
import { get } from 'lodash';
import React, { Fragment } from 'react';
import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants';
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 { 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,53 @@ 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 (
<EuiBadge>
{i18n.translate('xpack.monitoring.elasticsearch.nodes.coordinatingNodeLabel', {
defaultMessage: 'coordinating only',
})}
</EuiBadge>
);
}
const head = roles.slice(0, 5);
const tail = roles.slice(5);
const hasMoreRoles = tail.length > 0;
return (
<EuiBadgeGroup gutterSize="xs">
{head.map((role) => (
<EuiBadge color={role === 'master' ? 'hollow' : 'default'}>{role}</EuiBadge>
))}
{hasMoreRoles && (
<EuiToolTip
anchorProps={{
style: { lineHeight: '1' },
}}
position="bottom"
content={tail.join(', ')}
>
<EuiBadge>+{tail.length}</EuiBadge>
</EuiToolTip>
)}
</EuiBadgeGroup>
);
},
});
cols.push({
name: i18n.translate('xpack.monitoring.elasticsearch.nodes.shardsColumnTitle', {
defaultMessage: 'Shards',

View file

@ -8,6 +8,7 @@ Array [
"nodeTypeClass": "storage",
"nodeTypeLabel": "Node",
"resolver": "_x_V2YzPQU-a9KRRBxUxZQ",
"roles": undefined,
"shardCount": 6,
"transport_address": "127.0.0.1:9300",
"type": "node",
@ -19,6 +20,7 @@ Array [
"nodeTypeClass": "storage",
"nodeTypeLabel": "Node",
"resolver": "DAiX7fFjS3Wii7g2HYKrOg",
"roles": undefined,
"shardCount": 6,
"transport_address": "127.0.0.1:9301",
"type": "node",
@ -161,6 +163,7 @@ Array [
},
},
"resolver": "_x_V2YzPQU-a9KRRBxUxZQ",
"roles": undefined,
"shardCount": 0,
"transport_address": "127.0.0.1:9300",
"type": "master",
@ -276,6 +279,7 @@ Array [
},
},
"resolver": "DAiX7fFjS3Wii7g2HYKrOg",
"roles": undefined,
"shardCount": 0,
"transport_address": "127.0.0.1:9301",
"type": "node",
@ -298,6 +302,7 @@ Array [
"node_jvm_mem_percent": null,
"node_load_average": null,
"resolver": "_x_V2YzPQU-a9KRRBxUxZQ",
"roles": undefined,
"shardCount": 6,
"transport_address": "127.0.0.1:9300",
"type": "master",
@ -315,6 +320,7 @@ Array [
"node_jvm_mem_percent": null,
"node_load_average": null,
"resolver": "DAiX7fFjS3Wii7g2HYKrOg",
"roles": undefined,
"shardCount": 6,
"transport_address": "127.0.0.1:9301",
"type": "node",
@ -455,6 +461,7 @@ Array [
},
},
"resolver": "_x_V2YzPQU-a9KRRBxUxZQ",
"roles": undefined,
"shardCount": 6,
"transport_address": "127.0.0.1:9300",
"type": "master",
@ -570,6 +577,7 @@ Array [
},
},
"resolver": "DAiX7fFjS3Wii7g2HYKrOg",
"roles": undefined,
"shardCount": 6,
"transport_address": "127.0.0.1:9301",
"type": "node",

View file

@ -7,6 +7,7 @@ Object {
"name": "node01",
"nodeTypeClass": "starFilled",
"nodeTypeLabel": "Master Node",
"roles": undefined,
"shardCount": 57,
"transport_address": "127.0.0.1:9300",
"type": "master",
@ -16,6 +17,7 @@ Object {
"name": "node02",
"nodeTypeClass": "storage",
"nodeTypeLabel": "Node",
"roles": undefined,
"shardCount": 0,
"transport_address": "127.0.0.1:9301",
"type": "node",

View file

@ -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,
},
};
}, {});

View file

@ -25,11 +25,11 @@ export function MonitoringElasticsearchNodesProvider({ getService, getPageObject
const SUBJ_TABLE_SORT_NAME_COL = `tableHeaderCell_name_0`;
const SUBJ_TABLE_SORT_STATUS_COL = `tableHeaderCell_isOnline_2`;
const SUBJ_TABLE_SORT_SHARDS_COL = `tableHeaderCell_shardCount_3`;
const SUBJ_TABLE_SORT_CPU_COL = `tableHeaderCell_node_cpu_utilization_4`;
const SUBJ_TABLE_SORT_LOAD_COL = `tableHeaderCell_node_load_average_5`;
const SUBJ_TABLE_SORT_MEM_COL = `tableHeaderCell_node_jvm_mem_percent_6`;
const SUBJ_TABLE_SORT_DISK_COL = `tableHeaderCell_node_free_space_7`;
const SUBJ_TABLE_SORT_SHARDS_COL = `tableHeaderCell_shardCount_4`;
const SUBJ_TABLE_SORT_CPU_COL = `tableHeaderCell_node_cpu_utilization_5`;
const SUBJ_TABLE_SORT_LOAD_COL = `tableHeaderCell_node_load_average_6`;
const SUBJ_TABLE_SORT_MEM_COL = `tableHeaderCell_node_jvm_mem_percent_7`;
const SUBJ_TABLE_SORT_DISK_COL = `tableHeaderCell_node_free_space_8`;
const SUBJ_TABLE_BODY = 'elasticsearchNodesTableContainer';
const SUBJ_NODES_NAMES = `${SUBJ_TABLE_BODY} > name`;