diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js index d4bc3e3529610..e705802676900 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js @@ -128,7 +128,7 @@ const getColumns = ( name: i18n.translate('xpack.monitoring.cluster.listing.nodesColumnTitle', { defaultMessage: 'Nodes', }), - field: 'elasticsearch.cluster_stats.nodes.count.total', + field: 'elasticsearch.count', 'data-test-subj': 'nodesCount', sortable: true, render: (total, cluster) => ( @@ -138,44 +138,55 @@ const getColumns = ( ), }, { - name: i18n.translate('xpack.monitoring.cluster.listing.indicesColumnTitle', { - defaultMessage: 'Indices', + name: i18n.translate('xpack.monitoring.cluster.listing.logstashColumnTitle', { + defaultMessage: 'Logstash', }), - field: 'elasticsearch.cluster_stats.indices.count', - 'data-test-subj': 'indicesCount', + field: 'logstash.count', + 'data-test-subj': 'logstashCount', sortable: true, render: (count, cluster) => ( {numeral(count).format('0,0')} ), }, { - name: i18n.translate('xpack.monitoring.cluster.listing.dataColumnTitle', { - defaultMessage: 'Data', + name: i18n.translate('xpack.monitoring.cluster.listing.kibanaColumnTitle', { + defaultMessage: 'Kibana', }), - field: 'elasticsearch.cluster_stats.indices.store.size_in_bytes', - 'data-test-subj': 'dataSize', + field: 'kibana.count', + 'data-test-subj': 'kibanaCount', sortable: true, - render: (size, cluster) => ( - {numeral(size).format('0,0[.]0 b')} + render: (count, cluster) => ( + {numeral(count).format('0,0')} ), }, { - name: i18n.translate('xpack.monitoring.cluster.listing.logstashColumnTitle', { - defaultMessage: 'Logstash', + name: i18n.translate('xpack.monitoring.cluster.listing.beatsColumnTitle', { + defaultMessage: 'Apm', }), - field: 'logstash.node_count', - 'data-test-subj': 'logstashCount', + field: 'apm.count', + 'data-test-subj': 'apmCount', sortable: true, render: (count, cluster) => ( {numeral(count).format('0,0')} ), }, { - name: i18n.translate('xpack.monitoring.cluster.listing.kibanaColumnTitle', { - defaultMessage: 'Kibana', + name: i18n.translate('xpack.monitoring.cluster.listing.beatsColumnTitle', { + defaultMessage: 'Beats', }), - field: 'kibana.count', - 'data-test-subj': 'kibanaCount', + field: 'beats.count', + 'data-test-subj': 'beatsCount', + sortable: true, + render: (count, cluster) => ( + {numeral(count).format('0,0')} + ), + }, + { + name: i18n.translate('xpack.monitoring.cluster.listing.beatsColumnTitle', { + defaultMessage: 'Enterprise search', + }), + field: 'enterprisesearch.count', + 'data-test-subj': 'enterprisesearchCount', sortable: true, render: (count, cluster) => ( {numeral(count).format('0,0')} diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/index.js b/x-pack/plugins/monitoring/public/components/cluster/overview/index.js index 05d5cb22c4c1b..b4634d9b3c783 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/index.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/index.js @@ -32,15 +32,17 @@ export function Overview(props) { {!isFromStandaloneCluster ? ( - + {props.cluster.elasticsearch.cluster_stats.nodes.count.total > 0 ? ( + + ) : null} ((acc, ruleTypeName) => { + acc[ruleTypeName] = alertStatus[ruleTypeName].map((rule: any) => ({ + ...rule, + states: rule.states.filter( + (state: any) => state.state.cluster.clusterUuid === clusterUuid + ), + })); + return acc; + }, {}), + alertsMeta: { + enabled: true, + }, + }; + } catch (err) { + req.logger.warn( + `Unable to fetch alert status because '${err.message}'. Alerts may not properly show up in the UI.` + ); + return { + list: {}, + alertsMeta: { + enabled: true, + }, + }; + } +} + +/** + * returns lightweight informations of the clusters monitored + */ +export async function findMonitoredClusters(req: LegacyRequest) { + const indexPattern = [ + '.monitoring-*', + 'metrics-elasticsearch.stack_monitoring.*', + 'metrics-kibana.stack_monitoring.*', + 'metrics-logstash.stack_monitoring.*', + 'metrics-beats.stack_monitoring.*', + ].join(','); + + const params = { + index: indexPattern, + size: 0, + ignore_unavailable: true, + body: findMonitoredClustersQuery({ + start: req.payload.timeRange.min, + end: req.payload.timeRange.max, + }), + }; + + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + const result = await callWithRequest(req, 'search', params); + + if (!result.aggregations) { + return []; + } + + const { buckets } = result.aggregations.cluster_uuid; + const clusterUuids = buckets.map((bucket: any) => bucket.key); + + const rulesClient = req.getRulesClient(); + const alertStatus = await fetchStatus(rulesClient, undefined, clusterUuids); + + return result.aggregations.cluster_uuid.buckets.map((bucket: any) => { + const { + key: cluster_uuid, + elasticsearch, + kibana, + beats, + logstash, + apm, + enterprisesearch, + } = bucket; + + const alerts = getAlertState(req, alertStatus, cluster_uuid); + return { + isSupported: true, + cluster_uuid, + cluster_name: + elasticsearch.latest_doc.hits.hits[0]?._source.elasticsearch.cluster?.name ?? cluster_uuid, + license: + elasticsearch.latest_doc.hits.hits[0]?._source.elasticsearch.cluster?.stats?.license ?? {}, + alerts, + elasticsearch: { + count: + elasticsearch.latest_doc.hits.hits[0]?._source.elasticsearch.cluster?.stats?.nodes + .count ?? 0, + }, + kibana: { count: kibana.instance_count.value }, + apm: { count: apm.instance_count.value }, + beats: { count: beats.instance_count.value }, + logstash: { count: logstash.instance_count.value }, + enterprisesearch: { count: enterprisesearch.instance_count.value }, + }; + }); +} + /** * Get all clusters or the cluster associated with {@code clusterUuid} when it is defined. */ @@ -71,6 +179,9 @@ export async function getClustersFromRequest( } else { // get clusters with stats and cluster state clusters = await getClustersStats(req, clusterUuid, CCS_REMOTE_PATTERN); + if (!clusters.length) { + clusters.push({ cluster_uuid: clusterUuid!, license: {}, isSupported: true }); + } } if (!clusterUuid && !isStandaloneCluster) { @@ -81,19 +192,6 @@ export async function getClustersFromRequest( // TODO: this handling logic should be two different functions if (clusterUuid) { - // if is defined, get specific cluster (no need for license checking) - if (!clusters || clusters.length === 0) { - throw notFound( - i18n.translate('xpack.monitoring.requestedClusters.uuidNotFoundErrorMessage', { - defaultMessage: - 'Unable to find the cluster in the selected time range. UUID: {clusterUuid}', - values: { - clusterUuid, - }, - }) - ); - } - const cluster = clusters[0]; // add ml jobs and alerts data @@ -134,43 +232,11 @@ export async function getClustersFromRequest( ); for (const cluster of clusters) { - if (!rulesClient) { - cluster.alerts = { - list: {}, - alertsMeta: { - enabled: false, - }, - }; - } else { - try { - cluster.alerts = { - list: Object.keys(alertStatus).reduce((acc, ruleTypeName) => { - acc[ruleTypeName] = alertStatus[ruleTypeName].map((rule) => ({ - ...rule, - states: rule.states.filter( - (state) => - state.state.cluster.clusterUuid === - get(cluster, 'elasticsearch.cluster.id', cluster.cluster_uuid) - ), - })); - return acc; - }, {}), - alertsMeta: { - enabled: true, - }, - }; - } catch (err) { - req.logger.warn( - `Unable to fetch alert status because '${err.message}'. Alerts may not properly show up in the UI.` - ); - cluster.alerts = { - list: {}, - alertsMeta: { - enabled: true, - }, - }; - } - } + cluster.alerts = getAlertState( + req, + alertStatus, + get(cluster, 'elasticsearch.cluster.id', cluster.cluster_uuid) + ); } } } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.ts b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.ts index 1e5883360d09b..bc6d366d16046 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/cluster/clusters.ts @@ -5,11 +5,8 @@ * 2.0. */ -import { - postClustersRequestPayloadRT, - postClustersResponsePayloadRT, -} from '../../../../../common/http_api/cluster'; -import { getClustersFromRequest } from '../../../../lib/cluster/get_clusters_from_request'; +import { postClustersRequestPayloadRT } from '../../../../../common/http_api/cluster'; +import { findMonitoredClusters } from '../../../../lib/cluster/get_clusters_from_request'; import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { verifyMonitoringAuth } from '../../../../lib/elasticsearch/verify_monitoring_auth'; import { handleError } from '../../../../lib/errors'; @@ -37,10 +34,8 @@ export function clustersRoute(server: MonitoringCore) { try { await verifyMonitoringAuth(req); - const clusters = await getClustersFromRequest(req, { - codePaths: req.payload.codePaths, - }); - return postClustersResponsePayloadRT.encode(clusters); + const clusters = await findMonitoredClusters(req); + return clusters; } catch (err) { throw handleError(err, req); }