mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Support for logstash * Beats support * Fix cherry-pick api issue * Support for logstash * Updates for beats and logstash * APM migration working * Tweaks for beats migration * Update copy for setup new button * If on cloud, disable setup mode * Handle new beat flow better * Better phrasing for APM * Add beat type to disable step * Fix i18n issue * Fix jest tests * Fix api tests * PR feedback * Update copy * Remove unnecessary code * Undo changes that are now in a separate PR * Disable more links * Fix overview link for logstash * PR feedback * Fix tests * PR feedback * PR feedback * Capitalize Beat per PR feedback
This commit is contained in:
parent
ba516a41f5
commit
1597b30132
53 changed files with 2426 additions and 221 deletions
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 = (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.monitoring.apm.instances.metricbeatMigration.detectedInstanceTitle', {
|
||||
defaultMessage: 'APM server detected',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<p>
|
||||
{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.`
|
||||
})}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m"/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const versions = uniq(data.apms.map(item => item.version)).map(version => {
|
||||
return { value: version };
|
||||
});
|
||||
|
@ -101,12 +124,19 @@ export function ApmServerInstances({ apms }) {
|
|||
<EuiPageContent>
|
||||
<Status stats={data.stats} />
|
||||
<EuiSpacer size="m"/>
|
||||
{detectedInstanceMessage}
|
||||
<EuiMonitoringTable
|
||||
className="apmInstancesTable"
|
||||
rows={data.apms}
|
||||
columns={columns}
|
||||
sorting={sorting}
|
||||
pagination={pagination}
|
||||
setupMode={setupMode}
|
||||
uuidField="uuid"
|
||||
nameField="name"
|
||||
setupNewButtonLabel={i18n.translate('xpack.monitoring.apm.metricbeatMigration.setupNewButtonLabel', {
|
||||
defaultMessage: 'Setup monitoring for new APM server'
|
||||
})}
|
||||
search={{
|
||||
box: {
|
||||
incremental: true,
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { uniq } from 'lodash';
|
||||
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import { uniq, get } from 'lodash';
|
||||
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink, EuiCallOut } from '@elastic/eui';
|
||||
import { Stats } from 'plugins/monitoring/components/beats';
|
||||
import { formatMetric } from 'plugins/monitoring/lib/format_number';
|
||||
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
|
||||
|
@ -74,9 +74,32 @@ export class Listing extends PureComponent {
|
|||
data,
|
||||
sorting,
|
||||
pagination,
|
||||
onTableChange
|
||||
onTableChange,
|
||||
setupMode
|
||||
} = this.props;
|
||||
|
||||
let detectedInstanceMessage = null;
|
||||
if (setupMode.enabled && setupMode.data && get(setupMode.data, 'detected.mightExist')) {
|
||||
detectedInstanceMessage = (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.monitoring.beats.instances.metricbeatMigration.detectedInstanceTitle', {
|
||||
defaultMessage: 'Beats instance detected',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<p>
|
||||
{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.`
|
||||
})}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m"/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const types = uniq(data.map(item => item.type)).map(type => {
|
||||
return { value: type };
|
||||
|
@ -92,9 +115,16 @@ export class Listing extends PureComponent {
|
|||
<EuiPageContent>
|
||||
<Stats stats={stats} />
|
||||
<EuiSpacer size="m"/>
|
||||
{detectedInstanceMessage}
|
||||
<EuiMonitoringTable
|
||||
className="beatsTable"
|
||||
rows={data}
|
||||
setupMode={setupMode}
|
||||
uuidField="uuid"
|
||||
nameField="name"
|
||||
setupNewButtonLabel={i18n.translate('xpack.monitoring.beats.metricbeatMigration.setupNewButtonLabel', {
|
||||
defaultMessage: 'Setup monitoring for new Beats instance'
|
||||
})}
|
||||
columns={this.getColumns()}
|
||||
sorting={sorting}
|
||||
pagination={pagination}
|
||||
|
|
|
@ -8,10 +8,9 @@ import React from 'react';
|
|||
import moment from 'moment';
|
||||
import { get } from 'lodash';
|
||||
import { formatMetric } from 'plugins/monitoring/lib/format_number';
|
||||
import { ClusterItemContainer, BytesPercentageUsage } from './helpers';
|
||||
import { ClusterItemContainer, BytesPercentageUsage, DisabledIfNoDataAndInSetupModeLink } from './helpers';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
|
@ -22,18 +21,55 @@ import {
|
|||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiHorizontalRule,
|
||||
EuiFlexGroup,
|
||||
EuiToolTip,
|
||||
EuiBadge
|
||||
} from '@elastic/eui';
|
||||
import { formatTimestampToDuration } from '../../../../common';
|
||||
import { CALCULATE_DURATION_SINCE } from '../../../../common/constants';
|
||||
|
||||
export function ApmPanel(props) {
|
||||
if (!get(props, 'apms.total', 0) > 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 = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate('xpack.monitoring.cluster.overview.apmPanel.setupModeNodesTooltip', {
|
||||
defaultMessage: `These numbers indicate how many detected monitored APM servers versus how many ` +
|
||||
`detected total APM servers. If there are more detected APM servers than monitored APM servers, click the Nodes ` +
|
||||
`link and you will be guided in how to setup monitoring for the missing node.`
|
||||
})}
|
||||
>
|
||||
<EuiBadge color={badgeColor}>
|
||||
{migratedNodesCount}/{totalNodesCount}
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClusterItemContainer
|
||||
{...props}
|
||||
|
@ -47,7 +83,9 @@ export function ApmPanel(props) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeApmData}
|
||||
onClick={goToApm}
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.apmPanel.overviewLinkAriaLabel', {
|
||||
defaultMessage: 'APM Overview'
|
||||
|
@ -58,7 +96,7 @@ export function ApmPanel(props) {
|
|||
id="xpack.monitoring.cluster.overview.apmPanel.overviewLinkLabel"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
@ -90,27 +128,32 @@ export function ApmPanel(props) {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
onClick={goToInstances}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.monitoring.cluster.overview.apmPanel.instancesTotalLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Apm Instances: {apmsTotal}',
|
||||
values: { apmsTotal: props.apms.total }
|
||||
}
|
||||
)}
|
||||
data-test-subj="apmListing"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.apmPanel.serversTotalLinkLabel"
|
||||
defaultMessage="APM Servers: {apmsTotal}"
|
||||
values={{ apmsTotal: (<span data-test-subj="apmsTotal">{props.apms.total}</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
onClick={goToInstances}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.monitoring.cluster.overview.apmPanel.instancesTotalLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'APM Instances: {apmsTotal}',
|
||||
values: { apmsTotal }
|
||||
}
|
||||
)}
|
||||
data-test-subj="apmListing"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.apmPanel.serversTotalLinkLabel"
|
||||
defaultMessage="APM Servers: {apmsTotal}"
|
||||
values={{ apmsTotal: (<span data-test-subj="apmsTotal">{apmsTotal}</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{setupModeInstancesData}
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiDescriptionList type="column">
|
||||
<EuiDescriptionListTitle>
|
||||
|
|
|
@ -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 = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate('xpack.monitoring.cluster.overview.beatsPanel.setupModeNodesTooltip', {
|
||||
defaultMessage: `These numbers indicate how many detected monitored Beats versus how many ` +
|
||||
`detected total Beats. If there are more detected Beats than monitored Beats, click the Nodes ` +
|
||||
`link and you will be guided in how to setup monitoring for the missing node.`
|
||||
})}
|
||||
>
|
||||
<EuiBadge color={badgeColor}>
|
||||
{migratedNodesCount}/{totalNodesCount}
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
const beatTypes = props.beats.types.map((beat, index) => {
|
||||
return [
|
||||
<EuiDescriptionListTitle
|
||||
|
@ -60,7 +97,9 @@ export function BeatsPanel(props) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeBeatsData}
|
||||
onClick={goToBeats}
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.beatsPanel.overviewLinkAriaLabel', {
|
||||
defaultMessage: 'Beats Overview'
|
||||
|
@ -71,7 +110,7 @@ export function BeatsPanel(props) {
|
|||
id="xpack.monitoring.cluster.overview.beatsPanel.overviewLinkLabel"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
@ -99,27 +138,32 @@ export function BeatsPanel(props) {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
onClick={goToInstances}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.monitoring.cluster.overview.beatsPanel.instancesTotalLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Beats Instances: {beatsTotal}',
|
||||
values: { beatsTotal: props.beats.total }
|
||||
}
|
||||
)}
|
||||
data-test-subj="beatsListing"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.beatsPanel.beatsTotalLinkLabel"
|
||||
defaultMessage="Beats: {beatsTotal}"
|
||||
values={{ beatsTotal: (<span data-test-subj="beatsTotal">{props.beats.total}</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
onClick={goToInstances}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.monitoring.cluster.overview.beatsPanel.instancesTotalLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Beats Instances: {beatsTotal}',
|
||||
values: { beatsTotal }
|
||||
}
|
||||
)}
|
||||
data-test-subj="beatsListing"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.beatsPanel.beatsTotalLinkLabel"
|
||||
defaultMessage="Beats: {beatsTotal}"
|
||||
values={{ beatsTotal: (<span data-test-subj="beatsTotal">{beatsTotal}</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{setupModeInstancesData}
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiDescriptionList type="column">
|
||||
{beatTypes}
|
||||
|
|
|
@ -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) {
|
|||
<HealthStatusIndicator status={clusterStats.status} />
|
||||
);
|
||||
|
||||
const showMlJobs = () => {
|
||||
// if license doesn't support ML, then `ml === null`
|
||||
if (props.ml) {
|
||||
const gotoURL = '#/elasticsearch/ml_jobs';
|
||||
return (
|
||||
<>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiLink href={gotoURL}>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.esPanel.jobsLabel"
|
||||
defaultMessage="Jobs"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription data-test-subj="esMlJobs">
|
||||
<EuiLink href={gotoURL}>{props.ml.jobs}</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const licenseText = <LicenseText license={props.license} showLicenseExpiration={props.showLicenseExpiration} />;
|
||||
|
||||
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 (
|
||||
<>
|
||||
<EuiDescriptionListTitle>
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeElasticsearchData}
|
||||
href={gotoURL}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.esPanel.jobsLabel"
|
||||
defaultMessage="Jobs"
|
||||
/>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription data-test-subj="esMlJobs">
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeElasticsearchData}
|
||||
href={gotoURL}
|
||||
>
|
||||
{props.ml.jobs}
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<ClusterItemContainer
|
||||
{...props}
|
||||
|
@ -218,7 +234,9 @@ export function ElasticsearchPanel(props) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeElasticsearchData}
|
||||
onClick={goToElasticsearch}
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.esPanel.overviewLinkAriaLabel', {
|
||||
defaultMessage: 'Elasticsearch Overview'
|
||||
|
@ -229,7 +247,7 @@ export function ElasticsearchPanel(props) {
|
|||
id="xpack.monitoring.cluster.overview.esPanel.overviewLinkLabel"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
@ -318,7 +336,9 @@ export function ElasticsearchPanel(props) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeElasticsearchData}
|
||||
onClick={goToIndices}
|
||||
data-test-subj="esNumberOfIndices"
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.esPanel.indicesCountLinkAriaLabel', {
|
||||
|
@ -331,7 +351,7 @@ export function ElasticsearchPanel(props) {
|
|||
defaultMessage="Indices: {indicesCount}"
|
||||
values={{ indicesCount: formatNumber(get(indices, 'count'), 'int_commas') }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
@ -383,7 +403,9 @@ export function ElasticsearchPanel(props) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeElasticsearchData}
|
||||
onClick={goToElasticsearch}
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.esPanel.logsLinkAriaLabel', {
|
||||
defaultMessage: 'Elasticsearch Logs'
|
||||
|
@ -394,7 +416,7 @@ export function ElasticsearchPanel(props) {
|
|||
id="xpack.monitoring.cluster.overview.esPanel.logsLinkLabel"
|
||||
defaultMessage="Logs"
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
|
|
@ -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 <EuiText>0</EuiText>;
|
||||
}
|
||||
|
||||
export function DisabledIfNoDataAndInSetupModeLink({ setupModeEnabled, setupModeData, children, ...props }) {
|
||||
if (setupModeEnabled && get(setupModeData, 'totalUniqueInstanceCount', 0) === 0) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiLink {...props}>{children}</EuiLink>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -45,11 +45,11 @@ export function Overview(props) {
|
|||
: null
|
||||
}
|
||||
|
||||
<LogstashPanel {...props.cluster.logstash} changeUrl={props.changeUrl} />
|
||||
<LogstashPanel {...props.cluster.logstash} setupMode={props.setupMode} changeUrl={props.changeUrl} />
|
||||
|
||||
<BeatsPanel {...props.cluster.beats} changeUrl={props.changeUrl} />
|
||||
<BeatsPanel {...props.cluster.beats} setupMode={props.setupMode} changeUrl={props.changeUrl} />
|
||||
|
||||
<ApmPanel {...props.cluster.apm} changeUrl={props.changeUrl} />
|
||||
<ApmPanel {...props.cluster.apm} setupMode={props.setupMode} changeUrl={props.changeUrl} />
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
|
|
|
@ -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) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeKibanaData}
|
||||
onClick={goToKibana}
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.kibanaPanel.overviewLinkAriaLabel', {
|
||||
defaultMessage: 'Kibana Overview'
|
||||
|
@ -92,7 +94,7 @@ export function KibanaPanel(props) {
|
|||
id="xpack.monitoring.cluster.overview.kibanaPanel.overviewLinkLabel"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
|
|
@ -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 = (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={i18n.translate('xpack.monitoring.cluster.overview.logstashPanel.setupModeNodesTooltip', {
|
||||
defaultMessage: `These numbers indicate how many detected monitored nodes versus how many ` +
|
||||
`detected total nodes. If there are more detected nodes than monitored nodes, click the Nodes ` +
|
||||
`link and you will be guided in how to setup monitoring for the missing node.`
|
||||
})}
|
||||
>
|
||||
<EuiBadge color={badgeColor}>
|
||||
{formatNumber(migratedNodesCount, 'int_commas')}/{formatNumber(totalNodesCount, 'int_commas')}
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClusterItemContainer
|
||||
{...props}
|
||||
|
@ -47,7 +86,9 @@ export function LogstashPanel(props) {
|
|||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeLogstashData}
|
||||
onClick={goToLogstash}
|
||||
aria-label={i18n.translate('xpack.monitoring.cluster.overview.logstashPanel.overviewLinkAriaLabel', {
|
||||
defaultMessage: 'Logstash Overview'
|
||||
|
@ -57,7 +98,7 @@ export function LogstashPanel(props) {
|
|||
id="xpack.monitoring.cluster.overview.logstashPanel.overviewLinkLabel"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
@ -86,27 +127,32 @@ export function LogstashPanel(props) {
|
|||
|
||||
<EuiFlexItem>
|
||||
<EuiPanel paddingSize="m">
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
onClick={goToNodes}
|
||||
data-test-subj="lsNodes"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.monitoring.cluster.overview.logstashPanel.nodesCountLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Logstash Nodes: {nodesCount}',
|
||||
values: { nodesCount: props.node_count }
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.logstashPanel.nodesCountLinkLabel"
|
||||
defaultMessage="Nodes: {nodesCount}"
|
||||
values={{ nodesCount: (<span data-test-subj="number_of_logstash_instances">{ props.node_count }</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
onClick={goToNodes}
|
||||
data-test-subj="lsNodes"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.monitoring.cluster.overview.logstashPanel.nodesCountLinkAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Logstash Nodes: {nodesCount}',
|
||||
values: { nodesCount }
|
||||
}
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.logstashPanel.nodesCountLinkLabel"
|
||||
defaultMessage="Nodes: {nodesCount}"
|
||||
values={{ nodesCount: (<span data-test-subj="number_of_logstash_instances">{ nodesCount }</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{setupModeInstancesData}
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiDescriptionList type="column">
|
||||
<EuiDescriptionListTitle>
|
||||
|
@ -116,7 +162,7 @@ export function LogstashPanel(props) {
|
|||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription data-test-subj="lsUptime">
|
||||
{ formatNumber(props.max_uptime, 'time_since') }
|
||||
{ props.max_uptime ? formatNumber(props.max_uptime, 'time_since') : 0 }
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
|
@ -138,7 +184,9 @@ export function LogstashPanel(props) {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<EuiLink
|
||||
<DisabledIfNoDataAndInSetupModeLink
|
||||
setupModeEnabled={setupMode.enabled}
|
||||
setupModeData={setupModeLogstashData}
|
||||
onClick={goToPipelines}
|
||||
data-test-subj="lsPipelines"
|
||||
aria-label={i18n.translate(
|
||||
|
@ -154,7 +202,7 @@ export function LogstashPanel(props) {
|
|||
defaultMessage="Pipelines: {pipelineCount}"
|
||||
values={{ pipelineCount: (<span data-test-subj="number_of_logstash_pipelines">{ props.pipeline_count }</span>) }}
|
||||
/>
|
||||
</EuiLink>
|
||||
</DisabledIfNoDataAndInSetupModeLink>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
@ -177,14 +225,14 @@ export function LogstashPanel(props) {
|
|||
defaultMessage="With Memory Queues"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>{ props.queue_types[LOGSTASH.QUEUE_TYPES.MEMORY] }</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListDescription>{ queueTypes[LOGSTASH.QUEUE_TYPES.MEMORY] || 0 }</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.cluster.overview.logstashPanel.withPersistentQueuesLabel"
|
||||
defaultMessage="With Persistent Queues"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>{ props.queue_types[LOGSTASH.QUEUE_TYPES.PERSISTED] }</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListDescription>{ queueTypes[LOGSTASH.QUEUE_TYPES.PERSISTED] || 0 }</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -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 = (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.monitoring.logstash.nodes.metribeatMigration.netNewUserTitle', {
|
||||
defaultMessage: 'No monitoring data detected',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<p>
|
||||
{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.`
|
||||
})}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m"/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
|
@ -130,10 +154,17 @@ export class Listing extends PureComponent {
|
|||
<ClusterStatus stats={stats} />
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
{netNewUserMessage}
|
||||
<EuiPageContent>
|
||||
<EuiMonitoringTable
|
||||
className="logstashNodesTable"
|
||||
rows={flattenedData}
|
||||
setupMode={setupMode}
|
||||
uuidField="logstash.uuid"
|
||||
nameField="name"
|
||||
setupNewButtonLabel={i18n.translate('xpack.monitoring.logstash.metricbeatMigration.setupNewButtonLabel', {
|
||||
defaultMessage: 'Setup monitoring for new Logstash node'
|
||||
})}
|
||||
columns={columns}
|
||||
sorting={{
|
||||
...sorting,
|
||||
|
|
|
@ -60,7 +60,8 @@ describe('Listing', () => {
|
|||
},
|
||||
sorting: {
|
||||
sort: 'asc'
|
||||
}
|
||||
},
|
||||
setupMode: {}
|
||||
};
|
||||
|
||||
const component = shallow(<Listing {...props} />);
|
||||
|
@ -79,7 +80,8 @@ describe('Listing', () => {
|
|||
},
|
||||
sorting: {
|
||||
sort: 'asc'
|
||||
}
|
||||
},
|
||||
setupMode: {}
|
||||
};
|
||||
|
||||
const component = shallow(<Listing {...props} />);
|
||||
|
|
|
@ -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 {
|
|||
</EuiButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiButton
|
||||
type="submit"
|
||||
|
@ -318,6 +337,71 @@ export class Flyout extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
let noClusterUuidPrompt = null;
|
||||
if (product.isFullyMigrated && product.clusterUuid === null) {
|
||||
const nodeText = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.node', {
|
||||
defaultMessage: 'node'
|
||||
});
|
||||
const instanceText = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.instance', {
|
||||
defaultMessage: 'instance'
|
||||
});
|
||||
|
||||
let typeText = nodeText;
|
||||
if (productName === BEATS_SYSTEM_ID) {
|
||||
typeText = instanceText;
|
||||
}
|
||||
|
||||
noClusterUuidPrompt = (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="help"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.flyout.noClusterUuidTitle',
|
||||
{
|
||||
defaultMessage: 'No cluster detected'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.flyout.noClusterUuidDescription"
|
||||
defaultMessage="This {productName} {typeText} is not connected to an Elasticsearch cluster so once fully migrated,
|
||||
this {productName} {typeText} will appear in the Standalone cluster instead of this one. {link}"
|
||||
values={{
|
||||
productName,
|
||||
typeText,
|
||||
link: (
|
||||
<EuiLink href={`#/overview?_g=(cluster_uuid:__standalone_cluster__)`} target="_blank">
|
||||
Click here to view the Standalone cluster.
|
||||
</EuiLink>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCheckbox
|
||||
id="monitoringFlyoutNoClusterUuidCheckbox"
|
||||
label={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.flyout.noClusterUuidCheckboxLabel',
|
||||
{
|
||||
defaultMessage: `Yes, I understand that I will need to look in the Standalone cluster for
|
||||
this {productName} {typeText}.`,
|
||||
values: {
|
||||
productName,
|
||||
typeText
|
||||
}
|
||||
}
|
||||
)}
|
||||
checked={this.state.userAcknowledgedNoClusterUuidPrompt}
|
||||
onChange={e => this.setState({ userAcknowledgedNoClusterUuidPrompt: e.target.checked })}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s"/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
onClose={onClose}
|
||||
|
@ -333,6 +417,7 @@ export class Flyout extends Component {
|
|||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
{this.renderActiveStep()}
|
||||
{noClusterUuidPrompt}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
|
|
|
@ -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`
|
||||
});
|
|
@ -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: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.description"
|
||||
defaultMessage="Add the following setting in the APM server's configuration file ({file}):"
|
||||
values={{
|
||||
file: (
|
||||
<Monospace>apm-server.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
monitoring.enabled: false
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.note"
|
||||
defaultMessage="You'll need to restart the APM server after making this change."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
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 = (<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.partiallyMigratedStatusDescription"
|
||||
defaultMessage="Last internal collection occurred {secondsSinceLastInternalCollectionLabel} ago."
|
||||
values={{
|
||||
secondsSinceLastInternalCollectionLabel,
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.partiallyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: `We still see data coming from internal collection of this APM server.`
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.partiallyMigratedStatusDescription"
|
||||
defaultMessage="Note that it can take up to {secondsAgo} seconds to detect, but
|
||||
we will continuously check every {timePeriod} seconds in the background."
|
||||
values={{
|
||||
secondsAgo: meta.secondsAgo,
|
||||
timePeriod: autoCheckIntervalInMs / 1000,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{lastInternallyCollectedMessage}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.statusDescription',
|
||||
{
|
||||
defaultMessage: 'Check that no documents are coming from internal collection.'
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={checkForMigrationStatus} isDisabled={checkingMigrationStatus}>
|
||||
{buttonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
else {
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'complete',
|
||||
children: (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.disableInternalCollection.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are not seeing any documents from internal collection. Migration complete!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
disableInternalCollectionStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -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 = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="help"
|
||||
title={(
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.metricbeatSecuritySetup"
|
||||
defaultMessage="If security features are enabled, there may be more setup required.{link}"
|
||||
values={{
|
||||
link: (
|
||||
<Fragment>
|
||||
{` `}
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/apm/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.metricbeatSecuritySetupLinkText"
|
||||
defaultMessage="View more information."
|
||||
/>
|
||||
</EuiLink>
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
const installMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.installMetricbeatTitle', {
|
||||
defaultMessage: 'Install Metricbeat on the same server as the APM server'
|
||||
}),
|
||||
children: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/apm/metricbeat/${DOC_LINK_VERSION}/metricbeat-installation.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.installMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
const enableMetricbeatModuleStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.enableMetricbeatModuleTitle', {
|
||||
defaultMessage: 'Enable and configure the Beat x-pack module in Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
metricbeat modules enable beat-xpack
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.enableMetricbeatModuleDescription"
|
||||
defaultMessage="By default the module will collect APM server monitoring metrics from http://localhost:5066. If the local APM server has a different address, you must specify it via the {hosts} setting in the {file} file."
|
||||
values={{
|
||||
hosts: (
|
||||
<Monospace>hosts</Monospace>
|
||||
),
|
||||
file: (
|
||||
<Monospace>modules.d/beat-xpack.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
const configureMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.configureMetricbeatTitle', {
|
||||
defaultMessage: 'Configure Metricbeat to send to the monitoring cluster'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.configureMetricbeatDescription"
|
||||
defaultMessage="Make these changes in your {file}."
|
||||
values={{
|
||||
file: (
|
||||
<Monospace>metricbeat.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
>
|
||||
{`output.elasticsearch:
|
||||
hosts: ["${esMonitoringUrl}"] ## Monitoring cluster
|
||||
|
||||
# Optional protocol and basic auth credentials.
|
||||
#protocol: "https"
|
||||
#username: "elastic"
|
||||
#password: "changeme"
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
|
||||
)
|
||||
};
|
||||
|
||||
const startMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.startMetricbeatTitle', {
|
||||
defaultMessage: 'Start Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/apm/metricbeat/${DOC_LINK_VERSION}/metricbeat-starting.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.startMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
let migrationStatusStep = null;
|
||||
if (product.isInternalCollector || product.isNetNewUser) {
|
||||
let status = null;
|
||||
if (hasCheckedStatus) {
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.isInternalCollectorStatusTitle', {
|
||||
defaultMessage: `We have not detected any monitoring data coming from Metricbeat for this APM server.
|
||||
We will continuously check every {timePeriod} seconds in the background.`,
|
||||
values: {
|
||||
timePeriod: autoCheckIntervalInMs / 1000,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.apmInstructions.statusDescription', {
|
||||
defaultMessage: 'Check that data is received from the Metricbeat'
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={checkForMigrationStatus} isDisabled={checkingMigrationStatus}>
|
||||
{buttonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
else if (product.isPartiallyMigrated || product.isFullyMigrated) {
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'complete',
|
||||
children: (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.apmInstructions.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.apmInstructions.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are now seeing monitoring data shipping from Metricbeat!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
installMetricbeatStep,
|
||||
enableMetricbeatModuleStep,
|
||||
configureMetricbeatStep,
|
||||
startMetricbeatStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -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';
|
|
@ -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';
|
|
@ -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: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.description"
|
||||
defaultMessage="Add the following setting in {beatType}'s configuration file ({file}):"
|
||||
values={{
|
||||
beatType: beatType || UNDETECTED_BEAT_TYPE,
|
||||
file: (
|
||||
<Monospace>{beatType}.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
monitoring.enabled: false
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.note"
|
||||
defaultMessage="You'll need to restart {beatType} after making this change."
|
||||
values={{
|
||||
beatType: beatType || UNDETECTED_BEAT_TYPE
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
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 = (<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.partiallyMigratedStatusDescription"
|
||||
defaultMessage="Last internal collection occurred {secondsSinceLastInternalCollectionLabel} ago."
|
||||
values={{
|
||||
secondsSinceLastInternalCollectionLabel,
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.partiallyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: `We still see data coming from internal collection of this beat.`
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.partiallyMigratedStatusDescription"
|
||||
defaultMessage="Note that it can take up to {secondsAgo} seconds to detect, but
|
||||
we will continuously check every {timePeriod} seconds in the background."
|
||||
values={{
|
||||
secondsAgo: meta.secondsAgo,
|
||||
timePeriod: autoCheckIntervalInMs / 1000,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{lastInternallyCollectedMessage}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.statusDescription',
|
||||
{
|
||||
defaultMessage: 'Check that no documents are coming from internal collection.'
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={checkForMigrationStatus} isDisabled={checkingMigrationStatus}>
|
||||
{buttonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
else {
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'complete',
|
||||
children: (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.disableInternalCollection.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are not seeing any documents from internal collection. Migration complete!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
disableInternalCollectionStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -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 = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="help"
|
||||
title={(
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.metricbeatSecuritySetup"
|
||||
defaultMessage="If security features are enabled, there may be more setup required.{link}"
|
||||
values={{
|
||||
link: (
|
||||
<Fragment>
|
||||
{` `}
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/beats/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.metricbeatSecuritySetupLinkText"
|
||||
defaultMessage="View more information."
|
||||
/>
|
||||
</EuiLink>
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
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: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}/metricbeat-installation.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.installMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
metricbeat modules enable beat-xpack
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.enableMetricbeatModuleDescription"
|
||||
defaultMessage="By default the module will collect {beatType} monitoring metrics from http://localhost:5066. If the {beatType} instance being monitored has a different address, you must specify it via the {hosts} setting in the {file} file."
|
||||
values={{
|
||||
hosts: (
|
||||
<Monospace>hosts</Monospace>
|
||||
),
|
||||
file: (
|
||||
<Monospace>modules.d/beat-xpack.yml</Monospace>
|
||||
),
|
||||
beatType: beatType || UNDETECTED_BEAT_TYPE
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="help"
|
||||
title={(
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.enableMetricbeatModuleHttpEnabledDirections"
|
||||
defaultMessage="In order for Metricbeat to collect metrics from the running {beatType}, you need to {link}."
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink
|
||||
href={httpEndpointUrl}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.enableMetricbeatModuleHttpEnabledDirectionsLinkText"
|
||||
defaultMessage="enable an HTTP endpoint for the {beatType} instance being monitored"
|
||||
values={{
|
||||
beatType
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
beatType: beatType || UNDETECTED_BEAT_TYPE
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
)}
|
||||
/>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
const configureMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.configureMetricbeatTitle', {
|
||||
defaultMessage: 'Configure Metricbeat to send to the monitoring cluster'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.configureMetricbeatDescription"
|
||||
defaultMessage="Make these changes in your {file}."
|
||||
values={{
|
||||
file: (
|
||||
<Monospace>metricbeat.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
>
|
||||
{`output.elasticsearch:
|
||||
hosts: ["${esMonitoringUrl}"] ## Monitoring cluster
|
||||
|
||||
# Optional protocol and basic auth credentials.
|
||||
#protocol: "https"
|
||||
#username: "elastic"
|
||||
#password: "changeme"
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
|
||||
)
|
||||
};
|
||||
|
||||
const startMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.startMetricbeatTitle', {
|
||||
defaultMessage: 'Start Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}/metricbeat-starting.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.startMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
let migrationStatusStep = null;
|
||||
if (product.isInternalCollector || product.isNetNewUser) {
|
||||
let status = null;
|
||||
if (hasCheckedStatus) {
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.isInternalCollectorStatusTitle', {
|
||||
defaultMessage: `We have not detected any monitoring data coming from Metricbeat for this Beat.
|
||||
We will continuously check every {timePeriod} seconds in the background.`,
|
||||
values: {
|
||||
timePeriod: autoCheckIntervalInMs / 1000,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.beatsInstructions.statusDescription', {
|
||||
defaultMessage: 'Check that data is received from the Metricbeat'
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={checkForMigrationStatus} isDisabled={checkingMigrationStatus}>
|
||||
{buttonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
else if (product.isPartiallyMigrated || product.isFullyMigrated) {
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'complete',
|
||||
children: (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.beatsInstructions.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.beatsInstructions.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are now seeing monitoring data shipping from Metricbeat!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
installMetricbeatStep,
|
||||
enableMetricbeatModuleStep,
|
||||
configureMetricbeatStep,
|
||||
startMetricbeatStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -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';
|
|
@ -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 [];
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
});
|
|
@ -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: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.description"
|
||||
defaultMessage="Add the following setting in the Logstash configuration file ({file}):"
|
||||
values={{
|
||||
file: (
|
||||
<Monospace>logstash.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
xpack.monitoring.enabled: false
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.note"
|
||||
defaultMessage="You'll need to restart Logstash after making this change."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
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 = (<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.partiallyMigratedStatusDescription"
|
||||
defaultMessage="Last internal collection occurred {secondsSinceLastInternalCollectionLabel} ago."
|
||||
values={{
|
||||
secondsSinceLastInternalCollectionLabel,
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.partiallyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: `We still see data coming from internal collection of Logstash.`
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.partiallyMigratedStatusDescription"
|
||||
defaultMessage="Note that it can take up to {secondsAgo} seconds to detect, but
|
||||
we will continuously check every {timePeriod} seconds in the background."
|
||||
values={{
|
||||
secondsAgo: meta.secondsAgo,
|
||||
timePeriod: autoCheckIntervalInMs / 1000,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{lastInternallyCollectedMessage}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.statusDescription',
|
||||
{
|
||||
defaultMessage: 'Check that no documents are coming from internal collection.'
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={checkForMigrationStatus} isDisabled={checkingMigrationStatus}>
|
||||
{buttonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
else {
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'complete',
|
||||
children: (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.disableInternalCollection.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are not seeing any documents from internal collection. Migration complete!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
disableInternalCollectionStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -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 = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="help"
|
||||
title={(
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.metricbeatSecuritySetup"
|
||||
defaultMessage="If security features are enabled, there may be more setup required.{link}"
|
||||
values={{
|
||||
link: (
|
||||
<Fragment>
|
||||
{` `}
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/logstash/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.metricbeatSecuritySetupLinkText"
|
||||
defaultMessage="View more information."
|
||||
/>
|
||||
</EuiLink>
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
const installMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.installMetricbeatTitle', {
|
||||
defaultMessage: 'Install Metricbeat on the same server as Logstash'
|
||||
}),
|
||||
children: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}/metricbeat-installation.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.installMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
const enableMetricbeatModuleStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.enableMetricbeatModuleTitle', {
|
||||
defaultMessage: 'Enable and configure the Logstash x-pack module in Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
metricbeat modules enable logstash-xpack
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.enableMetricbeatModuleDescription"
|
||||
defaultMessage="By default the module will collect Logstash monitoring metrics from http://localhost:9600. If the local Logstash instance has a different address, you must specify it via the {hosts} setting in the {file} file."
|
||||
values={{
|
||||
hosts: (
|
||||
<Monospace>hosts</Monospace>
|
||||
),
|
||||
file: (
|
||||
<Monospace>modules.d/logstash-xpack.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
const configureMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.configureMetricbeatTitle', {
|
||||
defaultMessage: 'Configure Metricbeat to send to the monitoring cluster'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.configureMetricbeatDescription"
|
||||
defaultMessage="Make these changes in your {file}."
|
||||
values={{
|
||||
file: (
|
||||
<Monospace>metricbeat.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
>
|
||||
{`output.elasticsearch:
|
||||
hosts: ["${esMonitoringUrl}"] ## Monitoring cluster
|
||||
|
||||
# Optional protocol and basic auth credentials.
|
||||
#protocol: "https"
|
||||
#username: "elastic"
|
||||
#password: "changeme"
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
|
||||
)
|
||||
};
|
||||
|
||||
const startMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.startMetricbeatTitle', {
|
||||
defaultMessage: 'Start Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<EuiText>
|
||||
<p>
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}/metricbeat-starting.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.startMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
let migrationStatusStep = null;
|
||||
if (product.isInternalCollector || product.isNetNewUser) {
|
||||
let status = null;
|
||||
if (hasCheckedStatus) {
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.isInternalCollectorStatusTitle', {
|
||||
defaultMessage: `We have not detected any monitoring data coming from Metricbeat for this Logstash.
|
||||
We will continuously check every {timePeriod} seconds in the background.`,
|
||||
values: {
|
||||
timePeriod: autoCheckIntervalInMs / 1000,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
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: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.logstashInstructions.statusDescription', {
|
||||
defaultMessage: 'Check that data is received from the Metricbeat'
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={checkForMigrationStatus} isDisabled={checkingMigrationStatus}>
|
||||
{buttonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
}
|
||||
else if (product.isPartiallyMigrated || product.isFullyMigrated) {
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'complete',
|
||||
children: (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.logstashInstructions.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.logstashInstructions.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are now seeing monitoring data shipping from Metricbeat!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
installMetricbeatStep,
|
||||
enableMetricbeatModuleStep,
|
||||
configureMetricbeatStep,
|
||||
startMetricbeatStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<Flyout
|
||||
onClose={() => 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),
|
||||
|
|
|
@ -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 = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiButton onClick={() => setupMode.openFlyout({}, true)}>
|
||||
{props.setupNewButtonLabel}
|
||||
</EuiButton>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -195,6 +207,7 @@ export class EuiMonitoringTable extends React.PureComponent {
|
|||
columns={columns}
|
||||
{...props}
|
||||
/>
|
||||
{footerContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,13 +6,18 @@
|
|||
<div data-transclude-slot="bottomRow">
|
||||
<div ng-if="monitoringMain.inElasticsearch" class="euiTabs" role="navigation">
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.isDisabledTab('elasticsearch')"
|
||||
kbn-href="#/elasticsearch"
|
||||
class="euiTab"
|
||||
ng-class="{
|
||||
'euiTab-isSelected': monitoringMain.isActiveTab('overview'),
|
||||
'euiTab-isDisabled': monitoringMain.isDisabledTab('elasticsearch', 'overview')
|
||||
}"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.esNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && monitoringMain.isDisabledTab('elasticsearch')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.esNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
></a>
|
||||
|
@ -25,14 +30,18 @@
|
|||
i18n-default-message="Nodes"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.isDisabledTab('elasticsearch')"
|
||||
kbn-href="#/elasticsearch/indices"
|
||||
class="euiTab"
|
||||
ng-disabled="true"
|
||||
ng-class="{
|
||||
'euiTab-isSelected': monitoringMain.isActiveTab('indices'),
|
||||
'euiTab-isDisabled': monitoringMain.isDisabledTab('elasticsearch', 'indices')
|
||||
}"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('indices')}"
|
||||
i18n-id="xpack.monitoring.esNavigation.indicesLinkText"
|
||||
i18n-default-message="Indices"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && monitoringMain.isDisabledTab('elasticsearch')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('indices')}"
|
||||
i18n-id="xpack.monitoring.esNavigation.indicesLinkText"
|
||||
i18n-default-message="Indices"
|
||||
></a>
|
||||
|
@ -81,13 +90,23 @@
|
|||
|
||||
<div ng-if="monitoringMain.inKibana" class="euiTabs" role="navigation">
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.isDisabledTab('kibana')"
|
||||
kbn-href="#/kibana"
|
||||
class="euiTab"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.kibanaNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && monitoringMain.isDisabledTab('kibana')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{
|
||||
'euiTab-isSelected': monitoringMain.isActiveTab('overview'),
|
||||
}"
|
||||
i18n-id="xpack.monitoring.kibanaNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
kbn-href="#/kibana/instances"
|
||||
|
@ -101,13 +120,21 @@
|
|||
|
||||
<div ng-if="monitoringMain.inApm" class="euiTabs" role="navigation">
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.isDisabledTab('apm')"
|
||||
kbn-href="#/apm"
|
||||
class="euiTab"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.apmNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && monitoringMain.isDisabledTab('apm')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.apmNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
></a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
kbn-href="#/apm/instances"
|
||||
|
@ -121,11 +148,19 @@
|
|||
|
||||
<div ng-if="monitoringMain.inBeats" class="euiTabs" role="navigation">
|
||||
<a
|
||||
ng-if="!monitoringMain.instance"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.isDisabledTab('beats')"
|
||||
kbn-href="#/beats"
|
||||
class="euiTab"
|
||||
ng-class="{'euiTab-isSelected':
|
||||
monitoringMain.isActiveTab('overview')}"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.beatsNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && monitoringMain.isDisabledTab('beats')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.beatsNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
>
|
||||
|
@ -152,7 +187,7 @@
|
|||
|
||||
<div ng-if="monitoringMain.inLogstash" class="euiTabs" role="navigation">
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId && !monitoringMain.isDisabledTab('logstash')"
|
||||
kbn-href="#/logstash"
|
||||
class="euiTab"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
|
@ -160,6 +195,15 @@
|
|||
i18n-default-message="Overview"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId && monitoringMain.isDisabledTab('logstash')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('overview')}"
|
||||
i18n-id="xpack.monitoring.logstashNavigation.overviewLinkText"
|
||||
i18n-default-message="Overview"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId"
|
||||
kbn-href="#/logstash/nodes"
|
||||
|
@ -170,7 +214,7 @@
|
|||
>
|
||||
</a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId"
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId && !monitoringMain.isDisabledTab('logstash')"
|
||||
kbn-href="#/logstash/pipelines"
|
||||
class="euiTab"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('pipelines')}"
|
||||
|
@ -181,6 +225,18 @@
|
|||
></span>
|
||||
<span class="kuiIcon fa-flask monTabs--icon" tooltip="Beta feature" />
|
||||
</a>
|
||||
<a
|
||||
ng-if="!monitoringMain.instance && !monitoringMain.pipelineId && monitoringMain.isDisabledTab('logstash')"
|
||||
kbn-href=""
|
||||
class="euiTab euiTab-isDisabled"
|
||||
ng-class="{'euiTab-isSelected': monitoringMain.isActiveTab('pipelines')}"
|
||||
>
|
||||
<span
|
||||
i18n-id="xpack.monitoring.logstashNavigation.pipelinesLinkText"
|
||||
i18n-default-message="Pipelines"
|
||||
></span>
|
||||
<span class="kuiIcon fa-flask monTabs--icon" tooltip="Beta feature" />
|
||||
</a>
|
||||
<a
|
||||
ng-if="monitoringMain.instance"
|
||||
kbn-href="#/logstash/node/{{ monitoringMain.resolver }}"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { ajaxErrorHandlersProvider } from './ajax_error_handler';
|
||||
import { get } from 'lodash';
|
||||
|
||||
const angularState = {
|
||||
injector: null,
|
||||
|
@ -36,7 +37,7 @@ export const setNewlyDiscoveredClusterUuid = clusterUuid => {
|
|||
executor.run();
|
||||
};
|
||||
|
||||
export const fetchCollectionData = async () => {
|
||||
export const fetchCollectionData = async (uuid, fetchWithoutClusterUuid = false) => {
|
||||
checkAngularState();
|
||||
|
||||
const http = angularState.injector.get('$http');
|
||||
|
@ -45,8 +46,11 @@ export const fetchCollectionData = async () => {
|
|||
const ccs = globalState.ccs;
|
||||
|
||||
let url = '../api/monitoring/v1/setup/collection';
|
||||
if (clusterUuid) {
|
||||
url += `/${clusterUuid}`;
|
||||
if (uuid) {
|
||||
url += `/node/${uuid}`;
|
||||
}
|
||||
else if (!fetchWithoutClusterUuid && clusterUuid) {
|
||||
url += `/cluster/${clusterUuid}`;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -60,13 +64,17 @@ export const fetchCollectionData = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const notifySetupModeDataChange = () => {
|
||||
setupModeState.callbacks.forEach(cb => cb());
|
||||
const notifySetupModeDataChange = (oldData) => {
|
||||
setupModeState.callbacks.forEach(cb => cb(oldData));
|
||||
};
|
||||
|
||||
export const updateSetupModeData = async () => {
|
||||
setupModeState.data = await fetchCollectionData();
|
||||
notifySetupModeDataChange();
|
||||
export const updateSetupModeData = async (uuid, fetchWithoutClusterUuid = false) => {
|
||||
const oldData = setupModeState.data;
|
||||
setupModeState.data = await fetchCollectionData(uuid, fetchWithoutClusterUuid);
|
||||
if (get(setupModeState.data, '_meta.isOnCloud', false)) {
|
||||
return toggleSetupMode(false); // eslint-disable-line no-use-before-define
|
||||
}
|
||||
notifySetupModeDataChange(oldData);
|
||||
};
|
||||
|
||||
export const toggleSetupMode = inSetupMode => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { find } from 'lodash';
|
||||
import uiRoutes from'ui/routes';
|
||||
|
@ -13,6 +13,8 @@ import template from './index.html';
|
|||
import { ApmServerInstances } from '../../../components/apm/instances';
|
||||
import { MonitoringViewBaseEuiTableController } from '../..';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { APM_CUSTOM_ID } from '../../../../common/constants';
|
||||
|
||||
uiRoutes.when('/apm/instances', {
|
||||
template,
|
||||
|
@ -45,6 +47,9 @@ uiRoutes.when('/apm/instances', {
|
|||
$injector
|
||||
});
|
||||
|
||||
this.scope = $scope;
|
||||
this.injector = $injector;
|
||||
|
||||
$scope.$watch(() => this.data, data => {
|
||||
this.renderReact(data);
|
||||
});
|
||||
|
@ -59,13 +64,24 @@ uiRoutes.when('/apm/instances', {
|
|||
|
||||
const component = (
|
||||
<I18nContext>
|
||||
<ApmServerInstances
|
||||
apms={{
|
||||
pagination,
|
||||
sorting,
|
||||
onTableChange,
|
||||
data,
|
||||
}}
|
||||
<SetupModeRenderer
|
||||
scope={this.scope}
|
||||
injector={this.injector}
|
||||
productName={APM_CUSTOM_ID}
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
<ApmServerInstances
|
||||
setupMode={setupMode}
|
||||
apms={{
|
||||
pagination,
|
||||
sorting,
|
||||
onTableChange,
|
||||
data,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
</I18nContext>
|
||||
);
|
||||
|
|
|
@ -11,9 +11,11 @@ import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
|
|||
import { MonitoringViewBaseEuiTableController } from '../../';
|
||||
import { getPageData } from './get_page_data';
|
||||
import template from './index.html';
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { Listing } from '../../../components/beats/listing/listing';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { BEATS_SYSTEM_ID } from '../../../../../telemetry/common/constants';
|
||||
|
||||
uiRoutes.when('/beats/beats', {
|
||||
template,
|
||||
|
@ -43,6 +45,7 @@ uiRoutes.when('/beats/beats', {
|
|||
|
||||
this.data = $route.current.locals.pageData;
|
||||
this.scope = $scope;
|
||||
this.injector = $injector;
|
||||
this.kbnUrl = $injector.get('kbnUrl');
|
||||
|
||||
//Bypassing super.updateData, since this controller loads its own data
|
||||
|
@ -55,16 +58,27 @@ uiRoutes.when('/beats/beats', {
|
|||
const { sorting, pagination, onTableChange } = this.scope.beats;
|
||||
this.renderReact(
|
||||
<I18nContext>
|
||||
<Listing
|
||||
stats={this.data.stats}
|
||||
data={this.data.listing}
|
||||
sorting={this.sorting || sorting}
|
||||
pagination={this.pagination || pagination}
|
||||
onTableChange={this.onTableChange || onTableChange}
|
||||
angular={{
|
||||
kbnUrl: this.kbnUrl,
|
||||
scope: this.scope,
|
||||
}}
|
||||
<SetupModeRenderer
|
||||
scope={this.scope}
|
||||
injector={this.injector}
|
||||
productName={BEATS_SYSTEM_ID}
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
<Listing
|
||||
stats={this.data.stats}
|
||||
data={this.data.listing}
|
||||
setupMode={setupMode}
|
||||
sorting={this.sorting || sorting}
|
||||
pagination={this.pagination || pagination}
|
||||
onTableChange={this.onTableChange || onTableChange}
|
||||
angular={{
|
||||
kbnUrl: this.kbnUrl,
|
||||
scope: this.scope,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
</I18nContext>
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import { ElasticsearchNodes } from '../../../components';
|
|||
import { I18nContext } from 'ui/i18n';
|
||||
import { ajaxErrorHandlersProvider } from '../../../lib/ajax_error_handler';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants';
|
||||
|
||||
uiRoutes.when('/elasticsearch/nodes', {
|
||||
template,
|
||||
|
@ -82,7 +83,7 @@ uiRoutes.when('/elasticsearch/nodes', {
|
|||
<SetupModeRenderer
|
||||
scope={$scope}
|
||||
injector={$injector}
|
||||
productName="elasticsearch"
|
||||
productName={ELASTICSEARCH_CUSTOM_ID}
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
|
|
|
@ -13,6 +13,7 @@ import template from './index.html';
|
|||
import { KibanaInstances } from 'plugins/monitoring/components/kibana/instances';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { KIBANA_SYSTEM_ID } from '../../../../../telemetry/common/constants';
|
||||
|
||||
uiRoutes.when('/kibana/instances', {
|
||||
template,
|
||||
|
@ -44,7 +45,7 @@ uiRoutes.when('/kibana/instances', {
|
|||
<SetupModeRenderer
|
||||
scope={$scope}
|
||||
injector={$injector}
|
||||
productName="kibana"
|
||||
productName={KIBANA_SYSTEM_ID}
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* 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 from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import uiRoutes from'ui/routes';
|
||||
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
|
||||
import { MonitoringViewBaseEuiTableController } from '../../';
|
||||
|
@ -11,6 +11,8 @@ import { getPageData } from './get_page_data';
|
|||
import template from './index.html';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { Listing } from '../../../components/logstash/listing';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { LOGSTASH_SYSTEM_ID } from '../../../../../telemetry/common/constants';
|
||||
|
||||
uiRoutes.when('/logstash/nodes', {
|
||||
template,
|
||||
|
@ -39,14 +41,26 @@ uiRoutes.when('/logstash/nodes', {
|
|||
$scope.$watch(() => this.data, data => {
|
||||
this.renderReact(
|
||||
<I18nContext>
|
||||
<Listing
|
||||
data={data.nodes}
|
||||
stats={data.clusterStatus}
|
||||
sorting={this.sorting}
|
||||
pagination={this.pagination}
|
||||
onTableChange={this.onTableChange}
|
||||
angular={{ kbnUrl, scope: $scope }}
|
||||
<SetupModeRenderer
|
||||
scope={$scope}
|
||||
injector={$injector}
|
||||
productName={LOGSTASH_SYSTEM_ID}
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
<Listing
|
||||
data={data.nodes}
|
||||
setupMode={setupMode}
|
||||
stats={data.clusterStatus}
|
||||
sorting={this.sorting}
|
||||
pagination={this.pagination}
|
||||
onTableChange={this.onTableChange}
|
||||
angular={{ kbnUrl, scope: $scope }}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
|
||||
</I18nContext>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -5,15 +5,14 @@
|
|||
*/
|
||||
|
||||
import { get, uniq } from 'lodash';
|
||||
import { METRICBEAT_INDEX_NAME_UNIQUE_TOKEN, ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants';
|
||||
import { METRICBEAT_INDEX_NAME_UNIQUE_TOKEN, ELASTICSEARCH_CUSTOM_ID, APM_CUSTOM_ID } from '../../../../common/constants';
|
||||
import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID, LOGSTASH_SYSTEM_ID } from '../../../../../telemetry/common/constants';
|
||||
import { getLivesNodes } from '../../elasticsearch/nodes/get_nodes/get_live_nodes';
|
||||
import { KIBANA_STATS_TYPE } from '../../../../../../../../src/legacy/server/status/constants';
|
||||
|
||||
const NUMBER_OF_SECONDS_AGO_TO_LOOK = 30;
|
||||
const APM_CUSTOM_ID = 'apm';
|
||||
|
||||
const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) => {
|
||||
const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nodeUuid) => {
|
||||
const start = get(req.payload, 'timeRange.min', `now-${NUMBER_OF_SECONDS_AGO_TO_LOOK}s`);
|
||||
const end = get(req.payload, 'timeRange.max', 'now');
|
||||
|
||||
|
@ -32,6 +31,20 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) =>
|
|||
filters.push({ term: { 'cluster_uuid': clusterUuid } });
|
||||
}
|
||||
|
||||
const nodesClause = [];
|
||||
if (nodeUuid) {
|
||||
nodesClause.push({
|
||||
bool: {
|
||||
should: [
|
||||
{ term: { 'node_stats.node_id': nodeUuid } },
|
||||
{ term: { 'kibana_stats.kibana.uuid': nodeUuid } },
|
||||
{ term: { 'beats_stats.beat.uuid': nodeUuid } },
|
||||
{ term: { 'logstash_stats.logstash.uuid': nodeUuid } }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const params = {
|
||||
index: Object.values(indexPatterns),
|
||||
size: 0,
|
||||
|
@ -43,6 +56,7 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) =>
|
|||
query: {
|
||||
bool: {
|
||||
filter: filters,
|
||||
must: nodesClause,
|
||||
}
|
||||
},
|
||||
aggs: {
|
||||
|
@ -90,6 +104,11 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) =>
|
|||
terms: {
|
||||
field: 'beats_stats.beat.type'
|
||||
}
|
||||
},
|
||||
cluster_uuid: {
|
||||
terms: {
|
||||
field: 'cluster_uuid'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -102,6 +121,11 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) =>
|
|||
max: {
|
||||
field: 'timestamp'
|
||||
}
|
||||
},
|
||||
cluster_uuid: {
|
||||
terms: {
|
||||
field: 'cluster_uuid'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,8 +292,10 @@ async function getLiveElasticsearchClusterUuid(req) {
|
|||
* @param {*} req Standard request object. Can contain a timeRange to use for the query
|
||||
* @param {*} indexPatterns Map of index patterns to search against (will be all .monitoring-* indices)
|
||||
* @param {*} clusterUuid Optional and will be used to filter down the query if used
|
||||
* @param {*} nodeUuid Optional and will be used to filter down the query if used
|
||||
* @param {*} skipLiveData Optional and will not make any live api calls if set to true
|
||||
*/
|
||||
export const getCollectionStatus = async (req, indexPatterns, clusterUuid, skipLiveData) => {
|
||||
export const getCollectionStatus = async (req, indexPatterns, clusterUuid, nodeUuid, skipLiveData) => {
|
||||
const config = req.server.config();
|
||||
const kibanaUuid = config.get('server.uuid');
|
||||
|
||||
|
@ -285,7 +311,7 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, skipL
|
|||
recentDocuments,
|
||||
detectedProducts
|
||||
] = await Promise.all([
|
||||
await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid),
|
||||
await getRecentMonitoringDocuments(req, indexPatterns, clusterUuid, nodeUuid),
|
||||
await detectProducts(req)
|
||||
]);
|
||||
|
||||
|
@ -355,6 +381,12 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, skipL
|
|||
if (product.name === KIBANA_SYSTEM_ID && key === kibanaUuid) {
|
||||
map[key].isPrimary = true;
|
||||
}
|
||||
if (product.name === BEATS_SYSTEM_ID) {
|
||||
map[key].beatType = get(bucket.beat_type, 'buckets[0].key');
|
||||
}
|
||||
if (bucket.cluster_uuid) {
|
||||
map[key].clusterUuid = get(bucket.cluster_uuid, 'buckets[0].key', '') || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
productStatus.totalUniqueInstanceCount = Object.keys(map).length;
|
||||
|
@ -416,6 +448,12 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, skipL
|
|||
if (product.name === KIBANA_SYSTEM_ID && key === kibanaUuid) {
|
||||
map[key].isPrimary = true;
|
||||
}
|
||||
if (product.name === BEATS_SYSTEM_ID) {
|
||||
map[key].beatType = get(bucket.beat_type, 'buckets[0].key');
|
||||
}
|
||||
if (bucket.cluster_uuid) {
|
||||
map[key].clusterUuid = get(bucket.cluster_uuid, 'buckets[0].key', '') || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isFullyMigrated) {
|
||||
|
@ -473,6 +511,7 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, skipL
|
|||
status._meta = {
|
||||
secondsAgo: NUMBER_OF_SECONDS_AGO_TO_LOOK,
|
||||
clusterUuid: liveClusterUuid,
|
||||
isOnCloud: get(req.server.plugins, 'cloud.config.isCloudEnabled', false)
|
||||
};
|
||||
|
||||
return status;
|
||||
|
|
|
@ -16,11 +16,11 @@ export function clusterSetupStatusRoute(server) {
|
|||
*/
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/monitoring/v1/setup/collection/{clusterUuid}',
|
||||
path: '/api/monitoring/v1/setup/collection/cluster/{clusterUuid}',
|
||||
config: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
clusterUuid: Joi.string().required()
|
||||
clusterUuid: Joi.string().required(),
|
||||
}),
|
||||
query: Joi.object({
|
||||
// This flag is not intended to be used in production. It was introduced
|
||||
|
@ -50,7 +50,7 @@ export function clusterSetupStatusRoute(server) {
|
|||
try {
|
||||
await verifyMonitoringAuth(req);
|
||||
const indexPatterns = getIndexPatterns(server);
|
||||
status = await getCollectionStatus(req, indexPatterns, req.params.clusterUuid, req.query.skipLiveData);
|
||||
status = await getCollectionStatus(req, indexPatterns, req.params.clusterUuid, null, req.query.skipLiveData);
|
||||
} catch (err) {
|
||||
throw handleError(err, req);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export function clustersSetupStatusRoute(server) {
|
|||
try {
|
||||
await verifyMonitoringAuth(req);
|
||||
const indexPatterns = getIndexPatterns(server);
|
||||
status = await getCollectionStatus(req, indexPatterns, null, req.query.skipLiveData);
|
||||
status = await getCollectionStatus(req, indexPatterns, null, null, req.query.skipLiveData);
|
||||
} catch (err) {
|
||||
throw handleError(err, req);
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
export { clustersSetupStatusRoute } from './clusters_setup_status';
|
||||
export { clusterSetupStatusRoute } from './cluster_setup_status';
|
||||
export { nodeSetupStatusRoute } from './node_setup_status';
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 Joi from 'joi';
|
||||
import { verifyMonitoringAuth } from '../../../../lib/elasticsearch/verify_monitoring_auth';
|
||||
import { handleError } from '../../../../lib/errors';
|
||||
import { getCollectionStatus } from '../../../../lib/setup/collection';
|
||||
import { getIndexPatterns } from '../../../../lib/cluster/get_index_patterns';
|
||||
|
||||
export function nodeSetupStatusRoute(server) {
|
||||
/*
|
||||
* Monitoring Home
|
||||
* Route Init (for checking license and compatibility for multi-cluster monitoring
|
||||
*/
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/monitoring/v1/setup/collection/node/{nodeUuid}',
|
||||
config: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
nodeUuid: Joi.string().required(),
|
||||
}),
|
||||
query: Joi.object({
|
||||
// This flag is not intended to be used in production. It was introduced
|
||||
// as a way to ensure consistent API testing - the typical data source
|
||||
// for API tests are archived data, where the cluster configuration and data
|
||||
// are consistent from environment to environment. However, this endpoint
|
||||
// also attempts to retrieve data from the running stack products (ES and Kibana)
|
||||
// which will vary from environment to environment making it difficult
|
||||
// to write tests against. Therefore, this flag exists and should only be used
|
||||
// in our testing environment.
|
||||
skipLiveData: Joi.boolean().default(false)
|
||||
}),
|
||||
payload: Joi.object({
|
||||
timeRange: Joi.object({
|
||||
min: Joi.date().required(),
|
||||
max: Joi.date().required()
|
||||
}).optional()
|
||||
}).allow(null)
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
let status = null;
|
||||
|
||||
// NOTE using try/catch because checkMonitoringAuth is expected to throw
|
||||
// an error when current logged-in user doesn't have permission to read
|
||||
// the monitoring data. `try/catch` makes it a little more explicit.
|
||||
try {
|
||||
await verifyMonitoringAuth(req);
|
||||
const indexPatterns = getIndexPatterns(server);
|
||||
status = await getCollectionStatus(req, indexPatterns, null, req.params.nodeUuid, req.query.skipLiveData);
|
||||
} catch (err) {
|
||||
throw handleError(err, req);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -46,6 +46,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
"beatType": "metricbeat",
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"lastTimestamp": 1554821586714,
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
|
@ -34,6 +36,7 @@
|
|||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
"lastTimestamp": 1554821579833,
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
}
|
||||
|
@ -61,6 +64,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
"beatType": "metricbeat",
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"lastTimestamp": 1554821406717,
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
|
@ -34,6 +36,7 @@
|
|||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
"lastTimestamp": 1554821409656,
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
}
|
||||
|
@ -61,6 +64,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
"beatType": "metricbeat",
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"lastTimestamp": 1554821536716,
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
|
@ -34,6 +36,7 @@
|
|||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
"lastTimestamp": 1554821539784,
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
}
|
||||
|
@ -61,6 +64,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
"beatType": "metricbeat",
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"lastTimestamp": 1554821354041,
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
|
@ -34,6 +36,7 @@
|
|||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
"lastTimestamp": 1554821359616,
|
||||
"clusterUuid": "jJ9oySUPR8G0wuV82vG5-g",
|
||||
"isInternalCollector": true,
|
||||
"isNetNewUser": false
|
||||
}
|
||||
|
@ -61,6 +64,7 @@
|
|||
},
|
||||
"_meta": {
|
||||
"secondsAgo": 30,
|
||||
"isOnCloud": false,
|
||||
"clusterUuid": null
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue