diff --git a/x-pack/legacy/plugins/monitoring/common/constants.js b/x-pack/legacy/plugins/monitoring/common/constants.js index 56450cf614db2..3623d010a0cdb 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.js +++ b/x-pack/legacy/plugins/monitoring/common/constants.js @@ -167,6 +167,7 @@ export const METRICBEAT_INDEX_NAME_UNIQUE_TOKEN = '-mb-'; // We use this for metricbeat migration to identify specific products that we do not have constants for export const ELASTICSEARCH_CUSTOM_ID = 'elasticsearch'; +export const APM_CUSTOM_ID = 'apm'; /** * The id of the infra source owned by the monitoring plugin. */ diff --git a/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js index b4a1d5ea85c16..04b6652c6ce0a 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import moment from 'moment'; -import { uniq } from 'lodash'; +import { uniq, get } from 'lodash'; import { EuiMonitoringTable } from '../../table'; -import { EuiLink, EuiPage, EuiPageBody, EuiPageContent, EuiSpacer } from '@elastic/eui'; +import { EuiLink, EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { Status } from './status'; import { formatMetric } from '../../../lib/format_number'; import { formatTimestampToDuration } from '../../../../common'; @@ -83,14 +83,37 @@ const columns = [ }, ]; -export function ApmServerInstances({ apms }) { +export function ApmServerInstances({ apms, setupMode }) { const { pagination, sorting, onTableChange, - data + data, } = apms; + let detectedInstanceMessage = null; + if (setupMode.enabled && setupMode.data && get(setupMode.data, 'detected.mightExist')) { + detectedInstanceMessage = ( + + +

+ {i18n.translate('xpack.monitoring.apm.instances.metricbeatMigration.detectedInstanceDescription', { + defaultMessage: `Based on your indices, we think you might have an APM server. Click the 'Setup monitoring' + button below to start monitoring this APM server.` + })} +

+
+ +
+ ); + } + const versions = uniq(data.apms.map(item => item.version)).map(version => { return { value: version }; }); @@ -101,12 +124,19 @@ export function ApmServerInstances({ apms }) { + {detectedInstanceMessage} + +

+ {i18n.translate('xpack.monitoring.beats.instances.metricbeatMigration.detectedInstanceDescription', { + defaultMessage: `Based on your indices, we think you might have a beats instance. Click the 'Setup monitoring' + button below to start monitoring this instance.` + })} +

+
+ + + ); + } const types = uniq(data.map(item => item.type)).map(type => { return { value: type }; @@ -92,9 +115,16 @@ export class Listing extends PureComponent { + {detectedInstanceMessage} 0) { + const { setupMode } = props; + const apmsTotal = get(props, 'apms.total') || 0; + // Do not show if we are not in setup mode + if (apmsTotal === 0 && !setupMode.enabled) { return null; } const goToApm = () => props.changeUrl('apm'); const goToInstances = () => props.changeUrl('apm/instances'); + const setupModeApmData = get(setupMode.data, 'apm'); + let setupModeInstancesData = null; + if (setupMode.enabled && setupMode.data) { + const migratedNodesCount = Object.values(setupModeApmData.byUuid).filter(node => node.isFullyMigrated).length; + let totalNodesCount = Object.values(setupModeApmData.byUuid).length; + if (totalNodesCount === 0 && get(setupMode.data, 'apm.detected.mightExist', false)) { + totalNodesCount = 1; + } + + const badgeColor = migratedNodesCount === totalNodesCount + ? 'secondary' + : 'danger'; + + setupModeInstancesData = ( + + + + {migratedNodesCount}/{totalNodesCount} + + + + ); + } + return (

- - +

@@ -90,27 +128,32 @@ export function ApmPanel(props) { - -

- - {props.apms.total}) }} - /> - -

-
+ + + +

+ + {apmsTotal}) }} + /> + +

+
+
+ {setupModeInstancesData} +
diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/beats_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/beats_panel.js index f4da1a0e8737b..1f569fc5043e5 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/beats_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/beats_panel.js @@ -17,19 +17,56 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, EuiHorizontalRule, + EuiFlexGroup, + EuiToolTip, + EuiBadge } from '@elastic/eui'; -import { ClusterItemContainer } from './helpers'; +import { ClusterItemContainer, DisabledIfNoDataAndInSetupModeLink } from './helpers'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; export function BeatsPanel(props) { - if (!get(props, 'beats.total', 0) > 0) { + const { setupMode } = props; + const beatsTotal = get(props, 'beats.total') || 0; + // Do not show if we are not in setup mode + if (beatsTotal === 0 && !setupMode.enabled) { return null; } const goToBeats = () => props.changeUrl('beats'); const goToInstances = () => props.changeUrl('beats/beats'); + const setupModeBeatsData = get(setupMode.data, 'beats'); + let setupModeInstancesData = null; + if (setupMode.enabled && setupMode.data) { + const migratedNodesCount = Object.values(setupModeBeatsData.byUuid).filter(node => node.isFullyMigrated).length; + let totalNodesCount = Object.values(setupModeBeatsData.byUuid).length; + if (totalNodesCount === 0 && get(setupMode.data, 'beats.detected.mightExist', false)) { + totalNodesCount = 1; + } + + const badgeColor = migratedNodesCount === totalNodesCount + ? 'secondary' + : 'danger'; + + setupModeInstancesData = ( + + + + {migratedNodesCount}/{totalNodesCount} + + + + ); + } + const beatTypes = props.beats.types.map((beat, index) => { return [

- - +

@@ -99,27 +138,32 @@ export function BeatsPanel(props) {
- -

- - {props.beats.total}) }} - /> - -

-
+ + + +

+ + {beatsTotal}) }} + /> + +

+
+
+ {setupModeInstancesData} +
{beatTypes} diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index c891bc5373db3..4213c81dd97b7 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -7,7 +7,13 @@ import React, { Fragment } from 'react'; import { get, capitalize } from 'lodash'; import { formatNumber } from 'plugins/monitoring/lib/format_number'; -import { ClusterItemContainer, HealthStatusIndicator, BytesUsage, BytesPercentageUsage } from './helpers'; +import { + ClusterItemContainer, + HealthStatusIndicator, + BytesUsage, + BytesPercentageUsage, + DisabledIfNoDataAndInSetupModeLink +} from './helpers'; import { EuiFlexGrid, EuiFlexItem, @@ -151,36 +157,13 @@ export function ElasticsearchPanel(props) { ); - const showMlJobs = () => { - // if license doesn't support ML, then `ml === null` - if (props.ml) { - const gotoURL = '#/elasticsearch/ml_jobs'; - return ( - <> - - - - - - - {props.ml.jobs} - - - ); - } - return null; - }; - const licenseText = ; + const setupModeElasticsearchData = get(setupMode.data, 'elasticsearch'); let setupModeNodesData = null; if (setupMode.enabled && setupMode.data) { - const elasticsearchData = get(setupMode.data, 'elasticsearch.byUuid'); - const migratedNodesCount = Object.values(elasticsearchData).filter(node => node.isFullyMigrated).length; - const totalNodesCount = Object.values(elasticsearchData).length; + const migratedNodesCount = Object.values(setupModeElasticsearchData.byUuid).filter(node => node.isFullyMigrated).length; + const totalNodesCount = Object.values(setupModeElasticsearchData.byUuid).length; const badgeColor = migratedNodesCount === totalNodesCount ? 'secondary' @@ -204,6 +187,39 @@ export function ElasticsearchPanel(props) { ); } + const showMlJobs = () => { + // if license doesn't support ML, then `ml === null` + if (props.ml) { + const gotoURL = '#/elasticsearch/ml_jobs'; + return ( + <> + + + + + + + + {props.ml.jobs} + + + + ); + } + return null; + }; + return (

- - +

@@ -318,7 +336,9 @@ export function ElasticsearchPanel(props) {

- - +

@@ -383,7 +403,9 @@ export function ElasticsearchPanel(props) {

- - +

diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js index af495b97270a2..182f64bd2e41e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js @@ -5,8 +5,8 @@ */ import React from 'react'; +import { get } from 'lodash'; import { formatBytesUsage, formatPercentageUsage } from 'plugins/monitoring/lib/format_number'; - import { EuiSpacer, EuiFlexItem, @@ -15,6 +15,7 @@ import { EuiIcon, EuiHealth, EuiText, + EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -123,5 +124,15 @@ export function BytesPercentageUsage({ usedBytes, maxBytes }) { ); } - return null; + return 0; +} + +export function DisabledIfNoDataAndInSetupModeLink({ setupModeEnabled, setupModeData, children, ...props }) { + if (setupModeEnabled && get(setupModeData, 'totalUniqueInstanceCount', 0) === 0) { + return children; + } + + return ( + {children} + ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js index 56814b6fae128..895c61f19785a 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js @@ -45,11 +45,11 @@ export function Overview(props) { : null } - + - + - + ); diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/kibana_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/kibana_panel.js index a6f75446d94f5..3be52c89a7815 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/kibana_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/kibana_panel.js @@ -6,7 +6,7 @@ import React from 'react'; import { formatNumber } from 'plugins/monitoring/lib/format_number'; -import { ClusterItemContainer, HealthStatusIndicator, BytesPercentageUsage } from './helpers'; +import { ClusterItemContainer, HealthStatusIndicator, BytesPercentageUsage, DisabledIfNoDataAndInSetupModeLink } from './helpers'; import { get } from 'lodash'; import { EuiFlexGrid, @@ -39,11 +39,11 @@ export function KibanaPanel(props) { const goToKibana = () => props.changeUrl('kibana'); const goToInstances = () => props.changeUrl('kibana/instances'); + const setupModeKibanaData = get(setupMode.data, 'kibana'); let setupModeInstancesData = null; if (setupMode.enabled && setupMode.data) { - const kibanaData = get(setupMode.data, 'kibana.byUuid'); - const migratedNodesCount = Object.values(kibanaData).filter(node => node.isFullyMigrated).length; - const totalNodesCount = Object.values(kibanaData).length; + const migratedNodesCount = Object.values(setupModeKibanaData.byUuid).filter(node => node.isFullyMigrated).length; + const totalNodesCount = Object.values(setupModeKibanaData.byUuid).length; const badgeColor = migratedNodesCount === totalNodesCount ? 'secondary' @@ -81,7 +81,9 @@ export function KibanaPanel(props) {

- - +

diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/logstash_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/logstash_panel.js index 073dfbcbd6f7f..a3dfa7f2ef72b 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/logstash_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/logstash_panel.js @@ -6,7 +6,7 @@ import React from 'react'; import { formatNumber } from 'plugins/monitoring/lib/format_number'; -import { ClusterItemContainer, BytesPercentageUsage } from './helpers'; +import { ClusterItemContainer, BytesPercentageUsage, DisabledIfNoDataAndInSetupModeLink } from './helpers'; import { LOGSTASH } from '../../../../common/constants'; import { @@ -21,12 +21,20 @@ import { EuiDescriptionListDescription, EuiHorizontalRule, EuiIconTip, + EuiToolTip, + EuiBadge } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; export function LogstashPanel(props) { - if (!props.node_count) { + const { setupMode } = props; + const nodesCount = props.node_count || 0; + const queueTypes = props.queue_types || {}; + + // Do not show if we are not in setup mode + if (!nodesCount && !setupMode.enabled) { return null; } @@ -34,6 +42,37 @@ export function LogstashPanel(props) { const goToNodes = () => props.changeUrl('logstash/nodes'); const goToPipelines = () => props.changeUrl('logstash/pipelines'); + const setupModeLogstashData = get(setupMode.data, 'logstash'); + let setupModeInstancesData = null; + if (setupMode.enabled && setupMode.data) { + const migratedNodesCount = Object.values(setupModeLogstashData.byUuid).filter(node => node.isFullyMigrated).length; + let totalNodesCount = Object.values(setupModeLogstashData.byUuid).length; + if (totalNodesCount === 0 && get(setupMode.data, 'logstash.detected.mightExist', false)) { + totalNodesCount = 1; + } + + const badgeColor = migratedNodesCount === totalNodesCount + ? 'secondary' + : 'danger'; + + setupModeInstancesData = ( + + + + {formatNumber(migratedNodesCount, 'int_commas')}/{formatNumber(totalNodesCount, 'int_commas')} + + + + ); + } + return (

- - +

@@ -86,27 +127,32 @@ export function LogstashPanel(props) { - -

- - { props.node_count }) }} - /> - -

-
+ + + +

+ + { nodesCount }) }} + /> + +

+
+
+ {setupModeInstancesData} +
@@ -116,7 +162,7 @@ export function LogstashPanel(props) { /> - { formatNumber(props.max_uptime, 'time_since') } + { props.max_uptime ? formatNumber(props.max_uptime, 'time_since') : 0 }

- { props.pipeline_count }) }} /> - +

@@ -177,14 +225,14 @@ export function LogstashPanel(props) { defaultMessage="With Memory Queues" /> - { props.queue_types[LOGSTASH.QUEUE_TYPES.MEMORY] } + { queueTypes[LOGSTASH.QUEUE_TYPES.MEMORY] || 0 } - { props.queue_types[LOGSTASH.QUEUE_TYPES.PERSISTED] } + { queueTypes[LOGSTASH.QUEUE_TYPES.PERSISTED] || 0 }
diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index f976438f2f35d..f8073937a7e4d 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -330,6 +330,9 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear setupMode={setupMode} uuidField="resolver" nameField="name" + setupNewButtonLabel={i18n.translate('xpack.monitoring.elasticsearch.metricbeatMigration.setupNewButtonLabel', { + defaultMessage: 'Setup monitoring for new Elasticsearch node' + })} search={{ box: { incremental: true, diff --git a/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js b/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js index a1f645e817219..3fbc9287e7ba3 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js +++ b/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js @@ -227,6 +227,9 @@ export class KibanaInstances extends PureComponent { setupMode={setupMode} uuidField="kibana.uuid" nameField="name" + setupNewButtonLabel={i18n.translate('xpack.monitoring.kibana.metricbeatMigration.setupNewButtonLabel', { + defaultMessage: 'Setup monitoring for new Kibana instance' + })} search={{ box: { incremental: true, diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/__snapshots__/listing.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/__snapshots__/listing.test.js.snap index 7fa2bd05c2be9..987505dda09bb 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/__snapshots__/listing.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/__snapshots__/listing.test.js.snap @@ -55,6 +55,7 @@ exports[`Listing should render with certain data pieces missing 1`] = ` ], } } + nameField="name" rows={ Array [ Object { @@ -80,6 +81,8 @@ exports[`Listing should render with certain data pieces missing 1`] = ` }, } } + setupMode={Object {}} + setupNewButtonLabel="Setup monitoring for new Logstash node" sorting={ Object { "sort": Object { @@ -90,6 +93,7 @@ exports[`Listing should render with certain data pieces missing 1`] = ` }, } } + uuidField="logstash.uuid" /> `; @@ -148,6 +152,7 @@ exports[`Listing should render with expected props 1`] = ` ], } } + nameField="name" rows={ Array [ Object { @@ -205,6 +210,8 @@ exports[`Listing should render with expected props 1`] = ` }, } } + setupMode={Object {}} + setupNewButtonLabel="Setup monitoring for new Logstash node" sorting={ Object { "sort": Object { @@ -215,5 +222,6 @@ exports[`Listing should render with expected props 1`] = ` }, } } + uuidField="logstash.uuid" /> `; diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js b/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js index c1f682c67119d..bc51d6278c142 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent } from 'react'; +import React, { PureComponent, Fragment } from 'react'; import { get } from 'lodash'; -import { EuiPage, EuiLink, EuiPageBody, EuiPageContent, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { EuiPage, EuiLink, EuiPageBody, EuiPageContent, EuiPanel, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { formatPercentageUsage, formatNumber } from '../../../lib/format_number'; import { ClusterStatus } from '..//cluster_status'; import { EuiMonitoringTable } from '../../table'; @@ -110,8 +110,9 @@ export class Listing extends PureComponent { } ]; } + render() { - const { data, stats, sorting, pagination, onTableChange } = this.props; + const { stats, sorting, pagination, onTableChange, data, setupMode } = this.props; const columns = this.getColumns(); const flattenedData = data.map(item => ({ ...item, @@ -123,6 +124,29 @@ export class Listing extends PureComponent { version: get(item, 'logstash.version', 'N/A'), })); + let netNewUserMessage = null; + if (setupMode.enabled && setupMode.data && get(setupMode.data, 'detected.mightExist')) { + netNewUserMessage = ( + + +

+ {i18n.translate('xpack.monitoring.logstash.nodes.metribeatMigration.netNewUserDescription', { + defaultMessage: `Based on your indices, we think you might have a Logstash node. Click the 'Setup monitoring' + button below to start monitoring this node.` + })} +

+
+ +
+ ); + } + return ( @@ -130,10 +154,17 @@ export class Listing extends PureComponent { + {netNewUserMessage} { }, sorting: { sort: 'asc' - } + }, + setupMode: {} }; const component = shallow(); @@ -79,7 +80,8 @@ describe('Listing', () => { }, sorting: { sort: 'asc' - } + }, + setupMode: {} }; const component = shallow(); diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js index d3ac352dc5c50..4f5e9135894b1 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js @@ -20,19 +20,23 @@ import { EuiButtonEmpty, EuiLink, EuiText, + EuiCallOut, + EuiSpacer, + EuiCheckbox, } from '@elastic/eui'; import { getInstructionSteps } from '../instruction_steps'; import { Storage } from 'ui/storage'; import { STORAGE_KEY, ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants'; import { ensureMinimumTime } from '../../../lib/ensure_minimum_time'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { get } from 'lodash'; import { INSTRUCTION_STEP_SET_MONITORING_URL, INSTRUCTION_STEP_ENABLE_METRICBEAT, INSTRUCTION_STEP_DISABLE_INTERNAL } from '../constants'; -import { KIBANA_SYSTEM_ID } from '../../../../../telemetry/common/constants'; +import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID } from '../../../../../telemetry/common/constants'; import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import { setNewlyDiscoveredClusterUuid } from '../../../lib/setup_mode'; @@ -65,6 +69,7 @@ export class Flyout extends Component { [INSTRUCTION_STEP_DISABLE_INTERNAL]: false, }, checkingMigrationStatus: false, + userAcknowledgedNoClusterUuidPrompt: false }; } @@ -101,7 +106,9 @@ export class Flyout extends Component { checkForMigrationStatus = async () => { this.setState({ checkingMigrationStatus: true }); - await ensureMinimumTime(this.props.updateProduct(), 1000); + await ensureMinimumTime( + this.props.updateProduct(this.props.instance.uuid, true), 1000 + ); this.setState(state => ({ ...state, checkingMigrationStatus: false, @@ -177,7 +184,7 @@ export class Flyout extends Component { renderActiveStepNextButton() { const { product, productName } = this.props; - const { activeStep, esMonitoringUrl } = this.state; + const { activeStep, esMonitoringUrl, userAcknowledgedNoClusterUuidPrompt } = this.state; // It is possible that, during the migration steps, products are not reporting // monitoring data for a period of time outside the window of our server-side check @@ -205,6 +212,19 @@ export class Flyout extends Component { } } + // This is a possible scenario that come up during testing where logstash/beats + // is not outputing to ES, but has monitorining enabled. In these scenarios, + // the monitoring documents will not have a `cluster_uuid` so once migrated, + // the instance/node will actually live in the standalone cluster listing + // instead of the one it currently lives in. We need the user to understand + // this so we're going to force them to acknowledge a prompt saying this + if (product.isFullyMigrated && product.clusterUuid === null) { + // Did they acknowledge the prompt? + if (!userAcknowledgedNoClusterUuidPrompt) { + willDisableDoneButton = true; + } + } + if (willShowNextButton) { let isDisabled = false; let nextStep = null; @@ -237,7 +257,6 @@ export class Flyout extends Component { ); } - return ( + +

+ + Click here to view the Standalone cluster. + + ) + }} + /> +

+ + this.setState({ userAcknowledgedNoClusterUuidPrompt: e.target.checked })} + /> +
+ + + ); + } + return ( {this.renderActiveStep()} + {noClusterUuidPrompt} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/common_apm_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/common_apm_instructions.js new file mode 100644 index 0000000000000..643f299bca344 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/common_apm_instructions.js @@ -0,0 +1,10 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const statusTitle = i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.statusTitle', { + defaultMessage: `Migration status` +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/disable_internal_collection_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/disable_internal_collection_instructions.js new file mode 100644 index 0000000000000..827e535a57262 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/disable_internal_collection_instructions.js @@ -0,0 +1,197 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { formatTimestampToDuration } from '../../../../../common'; +import { CALCULATE_DURATION_SINCE } from '../../../../../common/constants'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_apm_instructions'; + +export function getApmInstructionsForDisablingInternalCollection(product, meta, { + checkForMigrationStatus, + checkingMigrationStatus, + hasCheckedStatus, + autoCheckIntervalInMs, +}) { + const disableInternalCollectionStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.title', { + defaultMessage: 'Disable internal collection of the APM server\'s monitoring metrics' + }), + children: ( + + +

+ apm-server.yml + ) + }} + /> +

+
+ + + monitoring.enabled: false + + + +

+ +

+
+
+ ) + }; + + let migrationStatusStep = null; + if (!product || !product.isFullyMigrated) { + let status = null; + if (hasCheckedStatus) { + let lastInternallyCollectedMessage = ''; + // It is possible that, during the migration steps, products are not reporting + // monitoring data for a period of time outside the window of our server-side check + // and this is most likely temporary so we want to be defensive and not error out + // and hopefully wait for the next check and this state will be self-corrected. + if (product) { + const lastInternallyCollectedTimestamp = product.lastInternallyCollectedTimestamp || product.lastTimestamp; + const secondsSinceLastInternalCollectionLabel = + formatTimestampToDuration(lastInternallyCollectedTimestamp, CALCULATE_DURATION_SINCE); + lastInternallyCollectedMessage = (); + } + + status = ( + + + +

+ +

+

+ {lastInternallyCollectedMessage} +

+
+
+ ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.checkingStatusButtonLabel', + { + defaultMessage: 'Checking...' + } + ); + } else { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.checkStatusButtonLabel', + { + defaultMessage: 'Check' + } + ); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate( + 'xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.statusDescription', + { + defaultMessage: 'Check that no documents are coming from internal collection.' + } + )} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + disableInternalCollectionStep, + migrationStatusStep + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/enable_metricbeat_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/enable_metricbeat_instructions.js new file mode 100644 index 0000000000000..cfc478761b62c --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/enable_metricbeat_instructions.js @@ -0,0 +1,267 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_apm_instructions'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +export function getApmInstructionsForEnablingMetricbeat(product, _meta, { + esMonitoringUrl, + hasCheckedStatus, + checkingMigrationStatus, + checkForMigrationStatus, + autoCheckIntervalInMs +}) { + const securitySetup = ( + + + + + {` `} + + + + + ) + }} + /> + + )} + /> + + ); + const installMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.installMetricbeatTitle', { + defaultMessage: 'Install Metricbeat on the same server as the APM server' + }), + children: ( + +

+ + + +

+
+ ) + }; + + const enableMetricbeatModuleStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.enableMetricbeatModuleTitle', { + defaultMessage: 'Enable and configure the Beat x-pack module in Metricbeat' + }), + children: ( + + + metricbeat modules enable beat-xpack + + + +

+ hosts + ), + file: ( + modules.d/beat-xpack.yml + ) + }} + /> +

+
+ {securitySetup} +
+ ) + }; + + const configureMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.configureMetricbeatTitle', { + defaultMessage: 'Configure Metricbeat to send to the monitoring cluster' + }), + children: ( + + + metricbeat.yml + ) + }} + /> + + + + {`output.elasticsearch: + hosts: ["${esMonitoringUrl}"] ## Monitoring cluster + + # Optional protocol and basic auth credentials. + #protocol: "https" + #username: "elastic" + #password: "changeme" +`} + + {securitySetup} + + + ) + }; + + const startMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.startMetricbeatTitle', { + defaultMessage: 'Start Metricbeat' + }), + children: ( + +

+ + + +

+
+ ) + }; + + let migrationStatusStep = null; + if (product.isInternalCollector || product.isNetNewUser) { + let status = null; + if (hasCheckedStatus) { + status = ( + + + + + ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.checkingStatusButtonLabel', { + defaultMessage: 'Checking for data...' + }); + } else { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.checkStatusButtonLabel', { + defaultMessage: 'Check for data' + }); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.statusDescription', { + defaultMessage: 'Check that data is received from the Metricbeat' + })} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else if (product.isPartiallyMigrated || product.isFullyMigrated) { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + installMetricbeatStep, + enableMetricbeatModuleStep, + configureMetricbeatStep, + startMetricbeatStep, + migrationStatusStep + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/index.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/index.js new file mode 100644 index 0000000000000..bef6f9f36197e --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/apm/index.js @@ -0,0 +1,8 @@ +/* + * 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 { getApmInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions'; +export { getApmInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/common_beats_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/common_beats_instructions.js new file mode 100644 index 0000000000000..0ada632f9779e --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/common_beats_instructions.js @@ -0,0 +1,13 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const statusTitle = i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.statusTitle', { + defaultMessage: `Migration status` +}); + +export const UNDETECTED_BEAT_TYPE = 'beat'; +export const DEFAULT_BEAT_FOR_URLS = 'metricbeat'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/disable_internal_collection_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/disable_internal_collection_instructions.js new file mode 100644 index 0000000000000..4a843ff286598 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/disable_internal_collection_instructions.js @@ -0,0 +1,205 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { formatTimestampToDuration } from '../../../../../common'; +import { CALCULATE_DURATION_SINCE } from '../../../../../common/constants'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle, UNDETECTED_BEAT_TYPE } from './common_beats_instructions'; + +export function getBeatsInstructionsForDisablingInternalCollection(product, meta, { + checkForMigrationStatus, + checkingMigrationStatus, + hasCheckedStatus, + autoCheckIntervalInMs, +}) { + const beatType = product.beatType; + const disableInternalCollectionStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.title', { + defaultMessage: 'Disable internal collection of {beatType}\'s monitoring metrics', + values: { + beatType: beatType || UNDETECTED_BEAT_TYPE + } + }), + children: ( + + +

+ {beatType}.yml + ) + }} + /> +

+
+ + + monitoring.enabled: false + + + +

+ +

+
+
+ ) + }; + + let migrationStatusStep = null; + if (!product || !product.isFullyMigrated) { + let status = null; + if (hasCheckedStatus) { + let lastInternallyCollectedMessage = ''; + // It is possible that, during the migration steps, products are not reporting + // monitoring data for a period of time outside the window of our server-side check + // and this is most likely temporary so we want to be defensive and not error out + // and hopefully wait for the next check and this state will be self-corrected. + if (product) { + const lastInternallyCollectedTimestamp = product.lastInternallyCollectedTimestamp || product.lastTimestamp; + const secondsSinceLastInternalCollectionLabel = + formatTimestampToDuration(lastInternallyCollectedTimestamp, CALCULATE_DURATION_SINCE); + lastInternallyCollectedMessage = (); + } + + status = ( + + + +

+ +

+

+ {lastInternallyCollectedMessage} +

+
+
+ ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.checkingStatusButtonLabel', + { + defaultMessage: 'Checking...' + } + ); + } else { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.checkStatusButtonLabel', + { + defaultMessage: 'Check' + } + ); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate( + 'xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.statusDescription', + { + defaultMessage: 'Check that no documents are coming from internal collection.' + } + )} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + disableInternalCollectionStep, + migrationStatusStep + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/enable_metricbeat_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/enable_metricbeat_instructions.js new file mode 100644 index 0000000000000..810304cf2a7ce --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/enable_metricbeat_instructions.js @@ -0,0 +1,307 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle, UNDETECTED_BEAT_TYPE, DEFAULT_BEAT_FOR_URLS } from './common_beats_instructions'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +export function getBeatsInstructionsForEnablingMetricbeat(product, _meta, { + esMonitoringUrl, + hasCheckedStatus, + checkingMigrationStatus, + checkForMigrationStatus, + autoCheckIntervalInMs +}) { + const beatType = product.beatType; + const securitySetup = ( + + + + + {` `} + + + + + ) + }} + /> + + )} + /> + + ); + const installMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.installMetricbeatTitle', { + defaultMessage: 'Install Metricbeat on the same server as this {beatType}', + values: { + beatType: beatType || UNDETECTED_BEAT_TYPE + } + }), + children: ( + +

+ + + +

+
+ ) + }; + + const httpEndpointUrl = `${ELASTIC_WEBSITE_URL}guide/en/beats/${beatType || DEFAULT_BEAT_FOR_URLS}` + + `/${DOC_LINK_VERSION}/http-endpoint.html`; + + const enableMetricbeatModuleStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.enableMetricbeatModuleTitle', { + defaultMessage: 'Enable and configure the Beat x-pack module in Metricbeat' + }), + children: ( + + + metricbeat modules enable beat-xpack + + + +

+ hosts + ), + file: ( + modules.d/beat-xpack.yml + ), + beatType: beatType || UNDETECTED_BEAT_TYPE + }} + /> +

+
+ + +

+ + + + ), + beatType: beatType || UNDETECTED_BEAT_TYPE + }} + /> +

+ + )} + /> + {securitySetup} +
+ ) + }; + + const configureMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.configureMetricbeatTitle', { + defaultMessage: 'Configure Metricbeat to send to the monitoring cluster' + }), + children: ( + + + metricbeat.yml + ) + }} + /> + + + + {`output.elasticsearch: + hosts: ["${esMonitoringUrl}"] ## Monitoring cluster + + # Optional protocol and basic auth credentials. + #protocol: "https" + #username: "elastic" + #password: "changeme" +`} + + {securitySetup} + + + ) + }; + + const startMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.startMetricbeatTitle', { + defaultMessage: 'Start Metricbeat' + }), + children: ( + +

+ + + +

+
+ ) + }; + + let migrationStatusStep = null; + if (product.isInternalCollector || product.isNetNewUser) { + let status = null; + if (hasCheckedStatus) { + status = ( + + + + + ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.checkingStatusButtonLabel', { + defaultMessage: 'Checking for data...' + }); + } else { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.checkStatusButtonLabel', { + defaultMessage: 'Check for data' + }); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.statusDescription', { + defaultMessage: 'Check that data is received from the Metricbeat' + })} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else if (product.isPartiallyMigrated || product.isFullyMigrated) { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + installMetricbeatStep, + enableMetricbeatModuleStep, + configureMetricbeatStep, + startMetricbeatStep, + migrationStatusStep + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/index.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/index.js new file mode 100644 index 0000000000000..e6f002c56865a --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/beats/index.js @@ -0,0 +1,8 @@ +/* + * 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 { getBeatsInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions'; +export { getBeatsInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js index c12df364092a8..7075be3def9bb 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js @@ -12,27 +12,62 @@ import { getElasticsearchInstructionsForEnablingMetricbeat, getElasticsearchInstructionsForDisablingInternalCollection } from './elasticsearch'; +import { + getLogstashInstructionsForEnablingMetricbeat, + getLogstashInstructionsForDisablingInternalCollection, +} from './logstash'; +import { + getBeatsInstructionsForEnablingMetricbeat, + getBeatsInstructionsForDisablingInternalCollection, +} from './beats'; +import { + getApmInstructionsForEnablingMetricbeat, + getApmInstructionsForDisablingInternalCollection, +} from './apm'; import { INSTRUCTION_STEP_ENABLE_METRICBEAT, INSTRUCTION_STEP_DISABLE_INTERNAL } from '../constants'; +import { ELASTICSEARCH_CUSTOM_ID, APM_CUSTOM_ID } from '../../../../common/constants'; +import { KIBANA_SYSTEM_ID, LOGSTASH_SYSTEM_ID, BEATS_SYSTEM_ID } from '../../../../../telemetry/common/constants'; export function getInstructionSteps(productName, product, step, meta, opts) { switch (productName) { - case 'kibana': + case KIBANA_SYSTEM_ID: if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { return getKibanaInstructionsForEnablingMetricbeat(product, meta, opts); } if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { return getKibanaInstructionsForDisablingInternalCollection(product, meta, opts); } - case 'elasticsearch': + case ELASTICSEARCH_CUSTOM_ID: if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { return getElasticsearchInstructionsForEnablingMetricbeat(product, meta, opts); } if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { return getElasticsearchInstructionsForDisablingInternalCollection(product, meta, opts); } + case LOGSTASH_SYSTEM_ID: + if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { + return getLogstashInstructionsForEnablingMetricbeat(product, meta, opts); + } + if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { + return getLogstashInstructionsForDisablingInternalCollection(product, meta, opts); + } + case BEATS_SYSTEM_ID: + if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { + return getBeatsInstructionsForEnablingMetricbeat(product, meta, opts); + } + if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { + return getBeatsInstructionsForDisablingInternalCollection(product, meta, opts); + } + case APM_CUSTOM_ID: + if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { + return getApmInstructionsForEnablingMetricbeat(product, meta, opts); + } + if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { + return getApmInstructionsForDisablingInternalCollection(product, meta, opts); + } } return []; } diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/common_logstash_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/common_logstash_instructions.js new file mode 100644 index 0000000000000..642add4d43fc4 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/common_logstash_instructions.js @@ -0,0 +1,10 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const statusTitle = i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.statusTitle', { + defaultMessage: `Migration status` +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/disable_internal_collection_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/disable_internal_collection_instructions.js new file mode 100644 index 0000000000000..9efc5a26ef822 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/disable_internal_collection_instructions.js @@ -0,0 +1,197 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { formatTimestampToDuration } from '../../../../../common'; +import { CALCULATE_DURATION_SINCE } from '../../../../../common/constants'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_logstash_instructions'; + +export function getLogstashInstructionsForDisablingInternalCollection(product, meta, { + checkForMigrationStatus, + checkingMigrationStatus, + hasCheckedStatus, + autoCheckIntervalInMs, +}) { + const disableInternalCollectionStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.title', { + defaultMessage: 'Disable internal collection of Logstash monitoring metrics' + }), + children: ( + + +

+ logstash.yml + ) + }} + /> +

+
+ + + xpack.monitoring.enabled: false + + + +

+ +

+
+
+ ) + }; + + let migrationStatusStep = null; + if (!product || !product.isFullyMigrated) { + let status = null; + if (hasCheckedStatus) { + let lastInternallyCollectedMessage = ''; + // It is possible that, during the migration steps, products are not reporting + // monitoring data for a period of time outside the window of our server-side check + // and this is most likely temporary so we want to be defensive and not error out + // and hopefully wait for the next check and this state will be self-corrected. + if (product) { + const lastInternallyCollectedTimestamp = product.lastInternallyCollectedTimestamp || product.lastTimestamp; + const secondsSinceLastInternalCollectionLabel = + formatTimestampToDuration(lastInternallyCollectedTimestamp, CALCULATE_DURATION_SINCE); + lastInternallyCollectedMessage = (); + } + + status = ( + + + +

+ +

+

+ {lastInternallyCollectedMessage} +

+
+
+ ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.checkingStatusButtonLabel', + { + defaultMessage: 'Checking...' + } + ); + } else { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.checkStatusButtonLabel', + { + defaultMessage: 'Check' + } + ); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate( + 'xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.statusDescription', + { + defaultMessage: 'Check that no documents are coming from internal collection.' + } + )} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + disableInternalCollectionStep, + migrationStatusStep + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/enable_metricbeat_instructions.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/enable_metricbeat_instructions.js new file mode 100644 index 0000000000000..bbdd208ad3628 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/enable_metricbeat_instructions.js @@ -0,0 +1,267 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_logstash_instructions'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +export function getLogstashInstructionsForEnablingMetricbeat(product, _meta, { + esMonitoringUrl, + hasCheckedStatus, + checkingMigrationStatus, + checkForMigrationStatus, + autoCheckIntervalInMs +}) { + const securitySetup = ( + + + + + {` `} + + + + + ) + }} + /> + + )} + /> + + ); + const installMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.installMetricbeatTitle', { + defaultMessage: 'Install Metricbeat on the same server as Logstash' + }), + children: ( + +

+ + + +

+
+ ) + }; + + const enableMetricbeatModuleStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.enableMetricbeatModuleTitle', { + defaultMessage: 'Enable and configure the Logstash x-pack module in Metricbeat' + }), + children: ( + + + metricbeat modules enable logstash-xpack + + + +

+ hosts + ), + file: ( + modules.d/logstash-xpack.yml + ) + }} + /> +

+
+ {securitySetup} +
+ ) + }; + + const configureMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.configureMetricbeatTitle', { + defaultMessage: 'Configure Metricbeat to send to the monitoring cluster' + }), + children: ( + + + metricbeat.yml + ) + }} + /> + + + + {`output.elasticsearch: + hosts: ["${esMonitoringUrl}"] ## Monitoring cluster + + # Optional protocol and basic auth credentials. + #protocol: "https" + #username: "elastic" + #password: "changeme" +`} + + {securitySetup} + + + ) + }; + + const startMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.startMetricbeatTitle', { + defaultMessage: 'Start Metricbeat' + }), + children: ( + +

+ + + +

+
+ ) + }; + + let migrationStatusStep = null; + if (product.isInternalCollector || product.isNetNewUser) { + let status = null; + if (hasCheckedStatus) { + status = ( + + + + + ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.checkingStatusButtonLabel', { + defaultMessage: 'Checking for data...' + }); + } else { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.checkStatusButtonLabel', { + defaultMessage: 'Check for data' + }); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.statusDescription', { + defaultMessage: 'Check that data is received from the Metricbeat' + })} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else if (product.isPartiallyMigrated || product.isFullyMigrated) { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + installMetricbeatStep, + enableMetricbeatModuleStep, + configureMetricbeatStep, + startMetricbeatStep, + migrationStatusStep + ]; +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/index.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/index.js new file mode 100644 index 0000000000000..c140c69db6bcc --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/logstash/index.js @@ -0,0 +1,8 @@ +/* + * 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 { getLogstashInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions'; +export { getLogstashInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/lib/find_new_uuid.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/lib/find_new_uuid.js new file mode 100644 index 0000000000000..d7fcb6beb0d79 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/lib/find_new_uuid.js @@ -0,0 +1,13 @@ +/* + * 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 function findNewUuid(oldUuids, newUuids) { + for (const newUuid of newUuids) { + if (oldUuids.indexOf(newUuid) === -1) { + return newUuid; + } + } +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js index 097b5e2428cfe..9e664aada7efb 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js +++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js @@ -6,42 +6,93 @@ import React from 'react'; import { getSetupModeState, initSetupModeState, updateSetupModeData } from '../../lib/setup_mode'; import { Flyout } from '../metricbeat_migration/flyout'; -import { ELASTICSEARCH_CUSTOM_ID } from '../../../common/constants'; +import { findNewUuid } from './lib/find_new_uuid'; export class SetupModeRenderer extends React.Component { state = { renderState: false, isFlyoutOpen: false, instance: null, + newProduct: null, + isSettingUpNew: false, } componentWillMount() { const { scope, injector } = this.props; - initSetupModeState(scope, injector, () => this.setState({ renderState: true })); + initSetupModeState(scope, injector, (_oldData) => { + const newState = { renderState: true }; + const { productName } = this.props; + if (!productName) { + this.setState(newState); + return; + } + + const setupModeState = getSetupModeState(); + if (!setupModeState.enabled || !setupModeState.data) { + this.setState(newState); + return; + } + + const data = setupModeState.data[productName]; + const oldData = _oldData ? _oldData[productName] : null; + if (data && oldData) { + const newUuid = findNewUuid(Object.keys(oldData.byUuid), Object.keys(data.byUuid)); + if (newUuid) { + newState.newProduct = data.byUuid[newUuid]; + } + } + + this.setState(newState); + }); + } + + reset() { + this.setState({ + renderState: false, + isFlyoutOpen: false, + instance: null, + newProduct: null, + isSettingUpNew: false, + }); } getFlyout(data, meta) { const { productName } = this.props; - const { isFlyoutOpen, instance } = this.state; + const { isFlyoutOpen, instance, isSettingUpNew, newProduct } = this.state; if (!data || !isFlyoutOpen) { return null; } - let product = instance ? data.byUuid[instance.uuid] : null; - const isFullyOrPartiallyMigrated = data.totalUniquePartiallyMigratedCount === data.totalUniqueInstanceCount - || data.totalUniqueFullyMigratedCount === data.totalUniqueInstanceCount; - if (!product && productName === ELASTICSEARCH_CUSTOM_ID && isFullyOrPartiallyMigrated) { - product = Object.values(data.byUuid)[0]; + let product = null; + if (newProduct) { + product = newProduct; + } + // For new instance discovery flow, we pass in empty instance object + else if (instance && Object.keys(instance).length) { + product = data.byUuid[instance.uuid]; + } + + if (!product) { + const uuids = Object.values(data.byUuid); + if (uuids.length && !isSettingUpNew) { + product = uuids[0]; + } + else { + product = { + isNetNewUser: true + }; + } } return ( this.setState({ isFlyoutOpen: false })} + onClose={() => this.reset()} productName={productName} product={product} meta={meta} instance={instance} updateProduct={updateSetupModeData} + isSettingUpNew={isSettingUpNew} /> ); } @@ -59,6 +110,7 @@ export class SetupModeRenderer extends React.Component { data = setupModeState.data; } } + const meta = setupModeState.data ? setupModeState.data._meta : null; return render({ @@ -67,7 +119,7 @@ export class SetupModeRenderer extends React.Component { enabled: setupModeState.enabled, productName, updateSetupModeData, - openFlyout: (instance) => this.setState({ isFlyoutOpen: true, instance }), + openFlyout: (instance, isSettingUpNew) => this.setState({ isFlyoutOpen: true, instance, isSettingUpNew }), closeFlyout: () => this.setState({ isFlyoutOpen: false }), }, flyoutComponent: this.getFlyout(data, meta), diff --git a/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js b/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js index 09e60bdd6b8c2..a52bafd1858cd 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js +++ b/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { get } from 'lodash'; import { EuiInMemoryTable, EuiBadge, EuiButtonEmpty, - EuiHealth + EuiHealth, + EuiButton, + EuiSpacer } from '@elastic/eui'; import { ELASTICSEARCH_CUSTOM_ID } from '../../../common/constants'; import { i18n } from '@kbn/i18n'; @@ -46,6 +48,7 @@ export class EuiMonitoringTable extends React.PureComponent { return column; }); + let footerContent = null; if (setupMode && setupMode.enabled) { columns.push({ name: i18n.translate('xpack.monitoring.euiTable.setupStatusTitle', { @@ -185,6 +188,15 @@ export class EuiMonitoringTable extends React.PureComponent { return null; } }); + + footerContent = ( + + + setupMode.openFlyout({}, true)}> + {props.setupNewButtonLabel} + + + ); } return ( @@ -195,6 +207,7 @@ export class EuiMonitoringTable extends React.PureComponent { columns={columns} {...props} /> + {footerContent} ); } diff --git a/x-pack/legacy/plugins/monitoring/public/directives/main/index.html b/x-pack/legacy/plugins/monitoring/public/directives/main/index.html index 823b3f763521c..7608c7743651b 100644 --- a/x-pack/legacy/plugins/monitoring/public/directives/main/index.html +++ b/x-pack/legacy/plugins/monitoring/public/directives/main/index.html @@ -6,13 +6,18 @@