diff --git a/x-pack/plugins/monitoring/common/types/es.ts b/x-pack/plugins/monitoring/common/types/es.ts new file mode 100644 index 0000000000000..853e140ec66c7 --- /dev/null +++ b/x-pack/plugins/monitoring/common/types/es.ts @@ -0,0 +1,80 @@ +/* + * 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. + */ + +export interface ElasticsearchSourceKibanaStats { + timestamp?: string; + kibana?: { + name?: string; + status?: string; + uuid?: string; + response_times?: { + max?: number; + }; + }; + os?: { + memory?: { + free_in_bytes?: number; + }; + }; + process?: { + uptime_in_millis?: number; + }; +} + +export interface ElasticsearchSource { + timestamp: string; + kibana_stats?: ElasticsearchSourceKibanaStats; + beats_stats?: { + timestamp?: string; + beat?: { + uuid?: string; + name?: string; + type?: string; + version?: string; + host?: string; + }; + metrics?: { + beat?: { + memstats?: { + memory_alloc?: number; + }; + info?: { + uptime?: { + ms?: number; + }; + }; + handles?: { + limit?: { + hard?: number; + soft?: number; + }; + }; + }; + libbeat?: { + config?: { + reloads?: number; + }; + output?: { + type?: string; + write?: { + bytes?: number; + errors?: number; + }; + read?: { + errors?: number; + }; + }; + pipeline?: { + events?: { + total?: number; + published?: number; + dropped?: number; + }; + }; + }; + }; + }; +} diff --git a/x-pack/plugins/monitoring/public/alerts/status.tsx b/x-pack/plugins/monitoring/public/alerts/status.tsx index 4d51069efb972..f67f1df11cfe1 100644 --- a/x-pack/plugins/monitoring/public/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/alerts/status.tsx @@ -16,8 +16,8 @@ import { SetupModeContext } from '../components/setup_mode/setup_mode_context'; interface Props { alerts: { [alertTypeId: string]: CommonAlertStatus }; showBadge: boolean; - showOnlyCount: boolean; - stateFilter: (state: AlertState) => boolean; + showOnlyCount?: boolean; + stateFilter?: (state: AlertState) => boolean; } export const AlertsStatus: React.FC = (props: Props) => { const { alerts, showBadge = false, showOnlyCount = false, stateFilter = () => true } = props; diff --git a/x-pack/plugins/monitoring/public/components/kibana/instances/instances.js b/x-pack/plugins/monitoring/public/components/kibana/instances/instances.js deleted file mode 100644 index cde7952aa1839..0000000000000 --- a/x-pack/plugins/monitoring/public/components/kibana/instances/instances.js +++ /dev/null @@ -1,294 +0,0 @@ -/* - * 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, { PureComponent, Fragment } from 'react'; -import { - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPanel, - EuiSpacer, - EuiLink, - EuiCallOut, - EuiScreenReaderOnly, - EuiToolTip, - EuiHealth, -} from '@elastic/eui'; -import { capitalize, get } from 'lodash'; -import { ClusterStatus } from '../cluster_status'; -import { EuiMonitoringTable } from '../../table'; -import { StatusIcon } from '../../status_icon'; -import { formatMetric, formatNumber } from '../../../lib/format_number'; -import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { SetupModeBadge } from '../../setup_mode/badge'; -import { KIBANA_SYSTEM_ID } from '../../../../common/constants'; -import { ListingCallOut } from '../../setup_mode/listing_callout'; -import { AlertsStatus } from '../../../alerts/status'; -import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; -import { SetupModeFeature } from '../../../../common/enums'; - -const getColumns = (setupMode, alerts) => { - const columns = [ - { - name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', { - defaultMessage: 'Name', - }), - field: 'name', - render: (name, kibana) => { - let setupModeStatus = null; - if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { - const list = get(setupMode, 'data.byUuid', {}); - const uuid = get(kibana, 'kibana.uuid'); - const status = list[uuid] || {}; - const instance = { - uuid, - name: kibana.name, - }; - - setupModeStatus = ( -
- -
- ); - if (status.isNetNewUser) { - return ( -
- {name} - {setupModeStatus} -
- ); - } - } - - return ( -
- - {name} - - {setupModeStatus} -
- ); - }, - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.alertsColumnTitle', { - defaultMessage: 'Alerts', - }), - field: 'isOnline', - width: '175px', - sortable: true, - render: () => , - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', { - defaultMessage: 'Status', - }), - field: 'status', - render: (status, kibana) => { - return ( - - - {capitalize(status)} - - - ); - }, - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.loadAverageColumnTitle', { - defaultMessage: 'Load Average', - }), - field: 'os.load.1m', - render: (value) => {formatMetric(value, '0.00')}, - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.memorySizeColumnTitle', { - defaultMessage: 'Memory Size', - }), - field: 'process.memory.resident_set_size_in_bytes', - render: (value) => {formatNumber(value, 'byte')}, - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.requestsColumnTitle', { - defaultMessage: 'Requests', - }), - field: 'requests.total', - render: (value) => {formatNumber(value, 'int_commas')}, - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.responseTimeColumnTitle', { - defaultMessage: 'Response Times', - }), - // It is possible this does not exist through MB collection - field: 'response_times.average', - render: (value, kibana) => { - if (!value) { - return null; - } - - return ( -
-
- {formatNumber(value, 'int_commas') + ' ms avg'} -
-
- {formatNumber(kibana.response_times.max, 'int_commas')} ms max -
-
- ); - }, - }, - ]; - - return columns; -}; - -export class KibanaInstances extends PureComponent { - render() { - const { clusterStatus, alerts, setupMode, sorting, pagination, onTableChange } = this.props; - - let setupModeCallOut = null; - // Merge the instances data with the setup data if enabled - const instances = this.props.instances || []; - if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { - // We want to create a seamless experience for the user by merging in the setup data - // and the node data from monitoring indices in the likely scenario where some instances - // are using MB collection and some are using no collection - const instancesByUuid = instances.reduce( - (byUuid, instance) => ({ - ...byUuid, - [get(instance, 'kibana.uuid')]: instance, - }), - {} - ); - - instances.push( - ...Object.entries(setupMode.data.byUuid).reduce((instances, [nodeUuid, instance]) => { - if (!instancesByUuid[nodeUuid]) { - instances.push({ - kibana: { - ...instance.instance.kibana, - status: StatusIcon.TYPES.GRAY, - }, - }); - } - return instances; - }, []) - ); - - setupModeCallOut = ( - { - const customRenderResponse = { - shouldRender: false, - componentToRender: null, - }; - - const hasInstances = setupMode.data.totalUniqueInstanceCount > 0; - if (!hasInstances) { - customRenderResponse.shouldRender = true; - customRenderResponse.componentToRender = ( - - -

- {i18n.translate( - 'xpack.monitoring.kibana.instances.metricbeatMigration.detectedNodeDescription', - { - defaultMessage: `The following instances are not monitored. - Click 'Monitor with Metricbeat' below to start monitoring.`, - } - )} -

-
- -
- ); - } - - return customRenderResponse; - }} - /> - ); - } - - const dataFlattened = instances.map((item) => ({ - ...item, - name: item.kibana.name, - status: item.kibana.status, - })); - - return ( - - - -

- -

-
- - - - - {setupModeCallOut} - - - -
-
- ); - } -} diff --git a/x-pack/plugins/monitoring/public/components/kibana/instances/instances.tsx b/x-pack/plugins/monitoring/public/components/kibana/instances/instances.tsx new file mode 100644 index 0000000000000..102f80b4722d4 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/kibana/instances/instances.tsx @@ -0,0 +1,314 @@ +/* + * 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, { Fragment } from 'react'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPanel, + EuiSpacer, + EuiLink, + EuiCallOut, + EuiScreenReaderOnly, + EuiToolTip, + EuiHealth, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { capitalize, get } from 'lodash'; +// @ts-ignore +import { ClusterStatus } from '../cluster_status'; +// @ts-ignore +import { EuiMonitoringTable } from '../../table'; +// @ts-ignore +import { StatusIcon } from '../../status_icon'; +// @ts-ignore +import { formatMetric, formatNumber } from '../../../lib/format_number'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; +// @ts-ignore +import { SetupModeBadge } from '../../setup_mode/badge'; +import { KIBANA_SYSTEM_ID } from '../../../../common/constants'; +import { CommonAlertStatus } from '../../../../common/types/alerts'; +import { ElasticsearchSourceKibanaStats } from '../../../../common/types/es'; +// @ts-ignore +import { ListingCallOut } from '../../setup_mode/listing_callout'; +import { AlertsStatus } from '../../../alerts/status'; +import { isSetupModeFeatureEnabled } from '../../../lib/setup_mode'; +import { SetupModeFeature } from '../../../../common/enums'; + +const getColumns = (setupMode: any, alerts: { [alertTypeId: string]: CommonAlertStatus }) => { + const columns = [ + { + name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', { + defaultMessage: 'Name', + }), + field: 'name', + render: (name: string, kibana: any) => { + let setupModeStatus = null; + if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { + const list = get(setupMode, 'data.byUuid', {}); + const uuid = get(kibana, 'kibana.uuid'); + const status = list[uuid] || {}; + const instance = { + uuid, + name: kibana.name, + }; + + setupModeStatus = ( +
+ +
+ ); + if (status.isNetNewUser) { + return ( +
+ {name} + {setupModeStatus} +
+ ); + } + } + + return ( +
+ + {name} + + {setupModeStatus} +
+ ); + }, + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.alertsColumnTitle', { + defaultMessage: 'Alerts', + }), + field: 'isOnline', + width: '175px', + sortable: true, + render: () => , + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', { + defaultMessage: 'Status', + }), + field: 'status', + render: ( + status: string, + kibana: Pick & { availability: boolean } + ) => { + return ( + + + {capitalize(status)} + + + ); + }, + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.loadAverageColumnTitle', { + defaultMessage: 'Load Average', + }), + field: 'os.load.1m', + render: (value: string) => {formatMetric(value, '0.00')}, + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.memorySizeColumnTitle', { + defaultMessage: 'Memory Size', + }), + field: 'process.memory.resident_set_size_in_bytes', + render: (value: string) => {formatNumber(value, 'byte')}, + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.requestsColumnTitle', { + defaultMessage: 'Requests', + }), + field: 'requests.total', + render: (value: string) => {formatNumber(value, 'int_commas')}, + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.responseTimeColumnTitle', { + defaultMessage: 'Response Times', + }), + // It is possible this does not exist through MB collection + field: 'response_times.average', + render: (value: string, kibana: ElasticsearchSourceKibanaStats['kibana']) => { + if (!value) { + return null; + } + + return ( +
+
+ {formatNumber(value, 'int_commas') + ' ms avg'} +
+
+ {formatNumber(kibana?.response_times?.max, 'int_commas')} ms max +
+
+ ); + }, + }, + ]; + + return columns; +}; + +interface Props { + clusterStatus: any; + alerts: { [alertTypeId: string]: CommonAlertStatus }; + setupMode: any; + sorting: any; + pagination: any; + onTableChange: any; + instances: ElasticsearchSourceKibanaStats[]; +} + +export const KibanaInstances: React.FC = (props: Props) => { + const { clusterStatus, alerts, setupMode, sorting, pagination, onTableChange } = props; + + let setupModeCallOut = null; + // Merge the instances data with the setup data if enabled + const instances = props.instances || []; + if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { + // We want to create a seamless experience for the user by merging in the setup data + // and the node data from monitoring indices in the likely scenario where some instances + // are using MB collection and some are using no collection + const instancesByUuid = instances.reduce( + (byUuid: { [uuid: string]: ElasticsearchSourceKibanaStats }, instance) => ({ + ...byUuid, + [instance.kibana?.uuid ?? '']: instance, + }), + {} + ); + + instances.push( + ...Object.entries(setupMode.data.byUuid).reduce((_instances: any, [nodeUuid, instance]) => { + if (!instancesByUuid[nodeUuid]) { + _instances.push({ + kibana: { + ...(instance as any).instance.kibana, + status: StatusIcon.TYPES.GRAY, + }, + }); + } + return _instances; + }, []) + ); + + setupModeCallOut = ( + { + const customRenderResponse = { + shouldRender: false, + componentToRender: null, + }; + + const hasInstances = setupMode.data.totalUniqueInstanceCount > 0; + if (!hasInstances) { + customRenderResponse.shouldRender = true; + // @ts-ignore + customRenderResponse.componentToRender = ( + + +

+ {i18n.translate( + 'xpack.monitoring.kibana.instances.metricbeatMigration.detectedNodeDescription', + { + defaultMessage: `The following instances are not monitored. + Click 'Monitor with Metricbeat' below to start monitoring.`, + } + )} +

+
+ +
+ ); + } + + return customRenderResponse; + }} + /> + ); + } + + const dataFlattened = instances.map((item) => ({ + ...item, + name: item.kibana?.name, + status: item.kibana?.status, + })); + + return ( + + + +

+ +

+
+ + + + + {setupModeCallOut} + + + +
+
+ ); +}; diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.js b/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts similarity index 68% rename from x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.js rename to x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts index 5a3e2dea930e0..0e8903908a55e 100644 --- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.js +++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts @@ -4,21 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, merge } from 'lodash'; +import { merge } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { calculateAvailability } from '../calculate_availability'; +import { LegacyRequest, ElasticsearchResponse } from '../../types'; -export function handleResponse(resp) { - const source = get(resp, 'hits.hits[0]._source.kibana_stats'); - const kibana = get(source, 'kibana'); +export function handleResponse(resp: ElasticsearchResponse) { + const source = resp.hits?.hits[0]._source.kibana_stats; + const kibana = source?.kibana; return merge(kibana, { - availability: calculateAvailability(get(source, 'timestamp')), - os_memory_free: get(source, 'os.memory.free_in_bytes'), - uptime: get(source, 'process.uptime_in_millis'), + availability: calculateAvailability(source?.timestamp), + os_memory_free: source?.os?.memory?.free_in_bytes, + uptime: source?.process?.uptime_in_millis, }); } -export function getKibanaInfo(req, kbnIndexPattern, { clusterUuid, kibanaUuid }) { +export function getKibanaInfo( + req: LegacyRequest, + kbnIndexPattern: string, + { clusterUuid, kibanaUuid }: { clusterUuid: string; kibanaUuid: string } +) { checkParam(kbnIndexPattern, 'kbnIndexPattern in getKibanaInfo'); const params = { diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 22ea6c31dbe69..9af95019dafd8 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -335,6 +335,7 @@ export class Plugin { } }, server: { + route: () => {}, config: legacyConfigWrapper, newPlatform: { setup: { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts similarity index 89% rename from x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js rename to x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts index 02229de372862..845908e6df341 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts @@ -6,11 +6,16 @@ import { schema } from '@kbn/config-schema'; import { getKibanaInfo } from '../../../../lib/kibana/get_kibana_info'; +// @ts-ignore import { handleError } from '../../../../lib/errors'; +// @ts-ignore import { getMetrics } from '../../../../lib/details/get_metrics'; +// @ts-ignore import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +// @ts-ignore import { metricSet } from './metric_set_instance'; import { INDEX_PATTERN_KIBANA } from '../../../../../common/constants'; +import { LegacyRequest, LegacyServer } from '../../../../types'; /** * Kibana instance: This will fetch all data required to display a Kibana @@ -18,7 +23,7 @@ import { INDEX_PATTERN_KIBANA } from '../../../../../common/constants'; * - Kibana Instance Summary (Status) * - Metrics */ -export function kibanaInstanceRoute(server) { +export function kibanaInstanceRoute(server: LegacyServer) { server.route({ method: 'POST', path: '/api/monitoring/v1/clusters/{clusterUuid}/kibana/{kibanaUuid}', @@ -37,7 +42,7 @@ export function kibanaInstanceRoute(server) { }), }, }, - async handler(req) { + async handler(req: LegacyRequest) { const config = server.config(); const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 84b331df8ba42..4fbc1c494f14c 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -17,6 +17,7 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/server'; import { CloudSetup } from '../../cloud/server'; +import { ElasticsearchSource } from '../common/types/es'; export interface MonitoringLicenseService { refresh: () => Promise; @@ -81,30 +82,36 @@ export interface LegacyRequest { payload: { [key: string]: any; }; + params: { + [key: string]: string; + }; getKibanaStatsCollector: () => any; getUiSettingsService: () => any; getActionTypeRegistry: () => any; getAlertsClient: () => any; getActionsClient: () => any; - server: { - config: () => { - get: (key: string) => string | undefined; + server: LegacyServer; +} + +export interface LegacyServer { + route: (params: any) => void; + config: () => { + get: (key: string) => string | undefined; + }; + newPlatform: { + setup: { + plugins: PluginsSetup; }; - newPlatform: { - setup: { - plugins: PluginsSetup; - }; + }; + plugins: { + monitoring: { + info: MonitoringLicenseService; }; - plugins: { - monitoring: { - info: MonitoringLicenseService; - }; - elasticsearch: { - getCluster: ( - name: string - ) => { - callWithRequest: (req: any, endpoint: string, params: any) => Promise; - }; + elasticsearch: { + getCluster: ( + name: string + ) => { + callWithRequest: (req: any, endpoint: string, params: any) => Promise; }; }; }; @@ -132,57 +139,3 @@ export interface ElasticsearchResponseHit { }; }; } - -export interface ElasticsearchSource { - timestamp: string; - beats_stats?: { - timestamp?: string; - beat?: { - uuid?: string; - name?: string; - type?: string; - version?: string; - host?: string; - }; - metrics?: { - beat?: { - memstats?: { - memory_alloc?: number; - }; - info?: { - uptime?: { - ms?: number; - }; - }; - handles?: { - limit?: { - hard?: number; - soft?: number; - }; - }; - }; - libbeat?: { - config?: { - reloads?: number; - }; - output?: { - type?: string; - write?: { - bytes?: number; - errors?: number; - }; - read?: { - errors?: number; - }; - }; - pipeline?: { - events?: { - total?: number; - published?: number; - dropped?: number; - }; - }; - }; - }; - }; -}