[Monitoring] Metricbeat migration flyout and instructions (#35228)
* Initial attempt at a reactor of how this works * Enter and exiting setup mode with migration buttons working * Adding monitoring url step back in and some small cleanup * Elasticsearch steps * Add missing file * Better organization here * Remove this debug logic * Clean up * PR feedback * Add in monospacing * Persist monitoring url in local storage * Rework the steps * Change node to server, and add missing files * Fix linting issues * Fix api integration tests * PR feedback * Pass down if the product is the "primary" or not, then use that to show certain warnings in the UI (just supported for Kibana right now) * Elasticsearch migration will work slightly differently in that all nodes must be partially migrated before we can disable internal collection * More PR feedback * PR feedback * Better links * Fix tests * This should open in a new tab * PR feedback * Design and PR feedback * Fix these tests * PR feedback * Remove debug * PR feedback * Update the import path * Update this import path too * PR feedback * Fix i18n
This commit is contained in:
parent
cd76109a2c
commit
b09e3a622f
|
@ -165,6 +165,8 @@ export const INDEX_PATTERN_FILEBEAT = 'filebeat-*';
|
|||
// This is the unique token that exists in monitoring indices collected by metricbeat
|
||||
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';
|
||||
/**
|
||||
* The id of the infra source owned by the monitoring plugin.
|
||||
*/
|
||||
|
|
|
@ -75,6 +75,7 @@ const getProps = (field) => {
|
|||
upTime: 28056934,
|
||||
version: ['8.0.0']
|
||||
},
|
||||
setupMode: {},
|
||||
nodes,
|
||||
sorting: {
|
||||
sort: { field: 'name', direction: 'asc' }
|
||||
|
|
|
@ -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 { NodeStatusIcon } from '../node';
|
||||
import { extractIp } from '../../../lib/extract_ip'; // TODO this is only used for elasticsearch nodes summary / node detail, so it should be moved to components/elasticsearch/nodes/lib
|
||||
import { ClusterStatus } from '../cluster_status';
|
||||
|
@ -18,6 +18,8 @@ import {
|
|||
EuiPageContent,
|
||||
EuiPageBody,
|
||||
EuiPanel,
|
||||
EuiCallOut,
|
||||
EuiButton,
|
||||
EuiText
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -207,7 +209,37 @@ const getColumns = showCgroupMetricsElasticsearch => {
|
|||
|
||||
export function ElasticsearchNodes({ clusterStatus, nodes, showCgroupMetricsElasticsearch, ...props }) {
|
||||
const columns = getColumns(showCgroupMetricsElasticsearch);
|
||||
const { sorting, pagination, onTableChange } = props;
|
||||
const { sorting, pagination, onTableChange, setupMode } = props;
|
||||
|
||||
let disableInternalCollectionForMigrationMessage = null;
|
||||
if (setupMode.data) {
|
||||
if (setupMode.data.totalUniquePartiallyMigratedCount === setupMode.data.totalUniqueInstanceCount) {
|
||||
disableInternalCollectionForMigrationMessage = (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.monitoring.elasticsearch.nodes.metribeatMigration.disableInternalCollectionTitle', {
|
||||
defaultMessage: 'Disable internal collection to finish the migration',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.monitoring.elasticsearch.nodes.metribeatMigration.disableInternalCollectionDescription', {
|
||||
defaultMessage: `All of your Elasticsearch servers are monitored using Metricbeat,
|
||||
but you need to disable internal collection to finish the migration.`
|
||||
})}
|
||||
</p>
|
||||
<EuiButton onClick={() => setupMode.openFlyout()} size="s" color="warning" fill>
|
||||
{i18n.translate('xpack.monitoring.elasticsearch.nodes.metribeatMigration.disableInternalCollectionMigrationButtonLabel', {
|
||||
defaultMessage: 'Disable and finish migration'
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m"/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
|
@ -216,6 +248,7 @@ export function ElasticsearchNodes({ clusterStatus, nodes, showCgroupMetricsElas
|
|||
<ClusterStatus stats={clusterStatus} />
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
{disableInternalCollectionForMigrationMessage}
|
||||
<EuiPageContent>
|
||||
<EuiMonitoringTable
|
||||
className="elasticsearchNodesTable"
|
||||
|
@ -223,6 +256,9 @@ export function ElasticsearchNodes({ clusterStatus, nodes, showCgroupMetricsElas
|
|||
columns={columns}
|
||||
sorting={sorting}
|
||||
pagination={pagination}
|
||||
setupMode={setupMode}
|
||||
uuidField="resolver"
|
||||
nameField="name"
|
||||
search={{
|
||||
box: {
|
||||
incremental: true,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { KibanaInstances } from './instances';
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { EuiPage, EuiPageBody, EuiPageContent, EuiPanel, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import { capitalize } from 'lodash';
|
||||
import { ClusterStatus } from '../cluster_status';
|
||||
import { EuiMonitoringTable } from '../../table';
|
||||
import { KibanaStatusIcon } from '../status_icon';
|
||||
import { formatMetric, formatNumber } from '../../../lib/format_number';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
const getColumns = (kbnUrl, scope) => {
|
||||
const columns = [
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', {
|
||||
defaultMessage: 'Name'
|
||||
}),
|
||||
field: 'name',
|
||||
render: (name, kibana) => (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
scope.$evalAsync(() => {
|
||||
kbnUrl.changePath(`/kibana/instances/${kibana.kibana.uuid}`);
|
||||
});
|
||||
}}
|
||||
data-test-subj={`kibanaLink-${name}`}
|
||||
>
|
||||
{ name }
|
||||
</EuiLink>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', {
|
||||
defaultMessage: 'Status'
|
||||
}),
|
||||
field: 'status',
|
||||
render: (status, kibana) => (
|
||||
<div
|
||||
title={`Instance status: ${status}`}
|
||||
className="monTableCell__status"
|
||||
>
|
||||
<KibanaStatusIcon status={status} availability={kibana.availability} />
|
||||
{ !kibana.availability ? (
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.kibana.listing.instanceStatus.offlineLabel"
|
||||
defaultMessage="Offline"
|
||||
/>
|
||||
) : capitalize(status) }
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.loadAverageColumnTitle', {
|
||||
defaultMessage: 'Load Average'
|
||||
}),
|
||||
field: 'os.load.1m',
|
||||
render: value => (
|
||||
<span>
|
||||
{formatMetric(value, '0.00')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.memorySizeColumnTitle', {
|
||||
defaultMessage: 'Memory Size'
|
||||
}),
|
||||
field: 'process.memory.resident_set_size_in_bytes',
|
||||
render: value => (
|
||||
<span>
|
||||
{formatNumber(value, 'byte')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.requestsColumnTitle', {
|
||||
defaultMessage: 'Requests'
|
||||
}),
|
||||
field: 'requests.total',
|
||||
render: value => (
|
||||
<span>
|
||||
{formatNumber(value, 'int_commas')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.responseTimeColumnTitle', {
|
||||
defaultMessage: 'Response Times'
|
||||
}),
|
||||
// It is possible this does not exist through MB collection
|
||||
field: 'response_times.average',
|
||||
render: (value, kibana) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="monTableCell__splitNumber">
|
||||
{ (formatNumber(value, 'int_commas') + ' ms avg') }
|
||||
</div>
|
||||
<div className="monTableCell__splitNumber">
|
||||
{ formatNumber(kibana.response_times.max, 'int_commas') } ms max
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return columns;
|
||||
};
|
||||
|
||||
export class KibanaInstances extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
instances,
|
||||
clusterStatus,
|
||||
angular,
|
||||
setupMode,
|
||||
sorting,
|
||||
pagination,
|
||||
onTableChange
|
||||
} = this.props;
|
||||
|
||||
const dataFlattened = instances.map(item => ({
|
||||
...item,
|
||||
name: item.kibana.name,
|
||||
status: item.kibana.status,
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPanel>
|
||||
<ClusterStatus stats={clusterStatus} />
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPageContent>
|
||||
<EuiMonitoringTable
|
||||
className="kibanaInstancesTable"
|
||||
rows={dataFlattened}
|
||||
columns={getColumns(angular.kbnUrl, angular.$scope)}
|
||||
sorting={sorting}
|
||||
pagination={pagination}
|
||||
setupMode={setupMode}
|
||||
uuidField="kibana.uuid"
|
||||
nameField="name"
|
||||
search={{
|
||||
box: {
|
||||
incremental: true,
|
||||
placeholder: i18n.translate('xpack.monitoring.kibana.listing.filterInstancesPlaceholder', {
|
||||
defaultMessage: 'Filter Instances…'
|
||||
})
|
||||
},
|
||||
}}
|
||||
onTableChange={onTableChange}
|
||||
executeQueryOptions={{
|
||||
defaultFields: ['name']
|
||||
}}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 const INSTRUCTION_STEP_SET_MONITORING_URL = 'setMonitoringUrl';
|
||||
export const INSTRUCTION_STEP_ENABLE_METRICBEAT = 'enableMetricbeat';
|
||||
export const INSTRUCTION_STEP_DISABLE_INTERNAL = 'disableInternal';
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlyoutBody,
|
||||
EuiTitle,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiButton,
|
||||
EuiSteps,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { getInstructionSteps } from '../instruction_steps';
|
||||
import { Storage } from '../../../../../../../src/legacy/ui/public/storage/storage';
|
||||
import { STORAGE_KEY, ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants';
|
||||
import { ensureMinimumTime } from '../../../lib/ensure_minimum_time';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
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 { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
|
||||
|
||||
const storage = new Storage(window.localStorage);
|
||||
const ES_MONITORING_URL_KEY = `${STORAGE_KEY}.mb_migration.esMonitoringUrl`;
|
||||
const AUTO_CHECK_INTERVAL_IN_MS = 5000;
|
||||
const DEFAULT_ES_MONITORING_URL = 'http://localhost:9200';
|
||||
|
||||
export class Flyout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let esMonitoringUrl = storage.get(ES_MONITORING_URL_KEY);
|
||||
if (!esMonitoringUrl) {
|
||||
esMonitoringUrl = props.monitoringHosts ? props.monitoringHosts[0] : DEFAULT_ES_MONITORING_URL;
|
||||
}
|
||||
|
||||
this.checkInterval = null;
|
||||
|
||||
let activeStep = INSTRUCTION_STEP_SET_MONITORING_URL;
|
||||
if (props.product && props.product.isPartiallyMigrated) {
|
||||
activeStep = INSTRUCTION_STEP_DISABLE_INTERNAL;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
activeStep,
|
||||
esMonitoringUrl,
|
||||
checkedStatusByStep: {
|
||||
[INSTRUCTION_STEP_ENABLE_METRICBEAT]: false,
|
||||
[INSTRUCTION_STEP_DISABLE_INTERNAL]: false,
|
||||
},
|
||||
checkingMigrationStatus: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUpdate(_nextProps, nextState) {
|
||||
// We attempt to provide a better UX for the user by automatically rechecking
|
||||
// the status of their current step, once they have initiated a check manually.
|
||||
// The logic here aims to remove the recheck one they have moved on from the
|
||||
// step
|
||||
|
||||
const thisActiveStep = this.state.activeStep;
|
||||
const nextActiveStep = nextState.activeStep;
|
||||
const nextEnableMbStatus = nextState.checkedStatusByStep[INSTRUCTION_STEP_ENABLE_METRICBEAT];
|
||||
const nowEnableMbStatus = this.state.checkedStatusByStep[INSTRUCTION_STEP_ENABLE_METRICBEAT];
|
||||
const nextDisableInternalStatus = nextState.checkedStatusByStep[INSTRUCTION_STEP_DISABLE_INTERNAL];
|
||||
const nowDisableInternalStatus = this.state.checkedStatusByStep[INSTRUCTION_STEP_DISABLE_INTERNAL];
|
||||
|
||||
const setupInterval = (nextEnableMbStatus && !nowEnableMbStatus) || (nextDisableInternalStatus && !nowDisableInternalStatus);
|
||||
const removeInterval = thisActiveStep !== nextActiveStep;
|
||||
if (removeInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.clearInterval = null;
|
||||
}
|
||||
|
||||
if (setupInterval) {
|
||||
this.checkInterval = setInterval(async () => {
|
||||
await this.checkForMigrationStatus();
|
||||
}, AUTO_CHECK_INTERVAL_IN_MS);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.checkInterval);
|
||||
}
|
||||
|
||||
checkForMigrationStatus = async () => {
|
||||
this.setState({ checkingMigrationStatus: true });
|
||||
await ensureMinimumTime(this.props.updateProduct(), 1000);
|
||||
this.setState(state => ({
|
||||
...state,
|
||||
checkingMigrationStatus: false,
|
||||
checkedStatusByStep: {
|
||||
...state.checkedStatusByStep,
|
||||
[this.state.activeStep]: true,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
setEsMonitoringUrl = esMonitoringUrl => {
|
||||
storage.set(ES_MONITORING_URL_KEY, esMonitoringUrl);
|
||||
this.setState({ esMonitoringUrl });
|
||||
}
|
||||
|
||||
renderActiveStep() {
|
||||
const { product, productName, onClose, meta } = this.props;
|
||||
const {
|
||||
activeStep,
|
||||
esMonitoringUrl,
|
||||
checkedStatusByStep,
|
||||
checkingMigrationStatus,
|
||||
} = this.state;
|
||||
|
||||
switch (activeStep) {
|
||||
case INSTRUCTION_STEP_SET_MONITORING_URL:
|
||||
return (
|
||||
<EuiForm>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.monitoring.metricbeatMigration.flyout.step1.monitoringUrlLabel', {
|
||||
defaultMessage: 'Monitoring cluster URL'
|
||||
})}
|
||||
helpText={i18n.translate('xpack.monitoring.metricbeatMigration.flyout.step1.monitoringUrlHelpText', {
|
||||
defaultMessage: `This is typically a single instance, but if you have multiple, enter all of instance urls comma-separated.
|
||||
Keep in mind that the running metricbeat instance will need to be able to communicate with these Elasticsearch servers.`
|
||||
})}
|
||||
>
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
value={esMonitoringUrl}
|
||||
onChange={e => this.setEsMonitoringUrl(e.target.value)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
case INSTRUCTION_STEP_ENABLE_METRICBEAT:
|
||||
case INSTRUCTION_STEP_DISABLE_INTERNAL:
|
||||
const instructionSteps = getInstructionSteps(productName, product, activeStep, meta, {
|
||||
doneWithMigration: onClose,
|
||||
esMonitoringUrl,
|
||||
checkForMigrationStatus: this.checkForMigrationStatus,
|
||||
checkingMigrationStatus,
|
||||
hasCheckedStatus: checkedStatusByStep[activeStep],
|
||||
autoCheckIntervalInMs: AUTO_CHECK_INTERVAL_IN_MS,
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSteps steps={instructionSteps}/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderActiveStepNextButton() {
|
||||
const { product, productName } = this.props;
|
||||
const { activeStep, esMonitoringUrl } = 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
|
||||
// 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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let willDisableDoneButton = !product.isFullyMigrated;
|
||||
let willShowNextButton = activeStep !== INSTRUCTION_STEP_DISABLE_INTERNAL;
|
||||
|
||||
if (activeStep === INSTRUCTION_STEP_ENABLE_METRICBEAT && productName === ELASTICSEARCH_CUSTOM_ID) {
|
||||
willShowNextButton = false;
|
||||
willDisableDoneButton = !product.isPartiallyMigrated;
|
||||
}
|
||||
|
||||
if (willShowNextButton) {
|
||||
let isDisabled = false;
|
||||
let nextStep = null;
|
||||
if (activeStep === INSTRUCTION_STEP_SET_MONITORING_URL) {
|
||||
isDisabled = !esMonitoringUrl || esMonitoringUrl.length === 0;
|
||||
if (product.isPartiallyMigrated || product.isFullyMigrated) {
|
||||
nextStep = INSTRUCTION_STEP_DISABLE_INTERNAL;
|
||||
}
|
||||
else {
|
||||
nextStep = INSTRUCTION_STEP_ENABLE_METRICBEAT;
|
||||
}
|
||||
}
|
||||
else if (activeStep === INSTRUCTION_STEP_ENABLE_METRICBEAT) {
|
||||
isDisabled = !product.isPartiallyMigrated && !product.isFullyMigrated;
|
||||
nextStep = INSTRUCTION_STEP_DISABLE_INTERNAL;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiButton
|
||||
type="submit"
|
||||
fill
|
||||
iconType="sortRight"
|
||||
iconSide="right"
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => this.setState({ activeStep: nextStep })}
|
||||
>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.flyout.nextButtonLabel', {
|
||||
defaultMessage: 'Next'
|
||||
})}
|
||||
</EuiButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiButton
|
||||
type="submit"
|
||||
fill
|
||||
isDisabled={willDisableDoneButton}
|
||||
onClick={this.props.onClose}
|
||||
>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.flyout.doneButtonLabel', {
|
||||
defaultMessage: 'Done'
|
||||
})}
|
||||
</EuiButton>
|
||||
);
|
||||
}
|
||||
|
||||
getDocumentationTitle() {
|
||||
const { productName } = this.props;
|
||||
|
||||
let documentationUrl = null;
|
||||
if (productName === KIBANA_SYSTEM_ID) {
|
||||
documentationUrl = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/monitoring-metricbeat.html`;
|
||||
}
|
||||
else if (productName === ELASTICSEARCH_CUSTOM_ID) {
|
||||
documentationUrl = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`;
|
||||
}
|
||||
|
||||
if (!documentationUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText size="s">
|
||||
<EuiLink href={documentationUrl} target="_blank">
|
||||
Read more about this migration.
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onClose, instance, productName } = this.props;
|
||||
|
||||
let instanceType = null;
|
||||
let instanceName = instance ? instance.name : null;
|
||||
|
||||
if (productName === KIBANA_SYSTEM_ID) {
|
||||
instanceType = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.kibanaInstance', {
|
||||
defaultMessage: 'instance',
|
||||
});
|
||||
}
|
||||
else if (productName === ELASTICSEARCH_CUSTOM_ID) {
|
||||
if (instance) {
|
||||
instanceType = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.elasticsearchNode', {
|
||||
defaultMessage: 'node',
|
||||
});
|
||||
}
|
||||
else {
|
||||
instanceName = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.elasticsearchNodesTitle', {
|
||||
defaultMessage: 'Elasticsearch nodes',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
onClose={onClose}
|
||||
aria-labelledby="flyoutTitle"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id="flyoutTitle">
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.flyout.flyoutTitle', {
|
||||
defaultMessage: 'Migrate {instanceName} {instanceType} to Metricbeat',
|
||||
values: {
|
||||
instanceName,
|
||||
instanceType
|
||||
}
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
{this.getDocumentationTitle()}
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
{this.renderActiveStep()}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
onClick={onClose}
|
||||
flush="left"
|
||||
>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.flyout.closeButtonLabel', {
|
||||
defaultMessage: 'Close'
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{this.renderActiveStepNextButton()}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { Flyout } from './flyout';
|
|
@ -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 { Monospace } from './monospace';
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const Monospace = ({ children }) => (
|
||||
<span style={{ fontFamily: 'monospace' }}>{children}</span>
|
||||
);
|
|
@ -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.elasticsearchInstructions.statusTitle', {
|
||||
defaultMessage: `Migration status`
|
||||
});
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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_elasticsearch_instructions';
|
||||
|
||||
export function getElasticsearchInstructionsForDisablingInternalCollection(product, meta, {
|
||||
checkForMigrationStatus,
|
||||
checkingMigrationStatus,
|
||||
hasCheckedStatus,
|
||||
autoCheckIntervalInMs,
|
||||
}) {
|
||||
const disableInternalCollectionStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollectionTitle', {
|
||||
defaultMessage: 'Disable internal collection of Elasticsearch monitoring metrics'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollectionDescription"
|
||||
defaultMessage="Disable internal collection of Elasticsearch monitoring metrics.
|
||||
Set {monospace} to false on each server in the production cluster."
|
||||
values={{
|
||||
monospace: (
|
||||
<Monospace>xpack.monitoring.elasticsearch.collection.enabled</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="curl"
|
||||
>
|
||||
{`PUT _cluster/settings
|
||||
{
|
||||
"persistent": {
|
||||
"xpack.monitoring.elasticsearch.collection.enabled": false
|
||||
}
|
||||
}
|
||||
`}
|
||||
</EuiCodeBlock>
|
||||
</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.elasticsearchInstructions.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.elasticsearchInstructions.partiallyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: `We still see data coming from internal collection of Elasticsearch.`
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.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.elasticsearchInstructions.disableInternalCollection.checkingStatusButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Checking...'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
buttonLabel = i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollection.checkStatusButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Check'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.elasticsearchInstructions.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.elasticsearchInstructions.disableInternalCollection.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollection.fullyMigratedStatusDescription"
|
||||
defaultMessage="We are not seeing any documents from internal collection. Migration complete!"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return [
|
||||
disableInternalCollectionStep,
|
||||
migrationStatusStep
|
||||
];
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* 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_elasticsearch_instructions';
|
||||
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
|
||||
|
||||
export function getElasticsearchInstructionsForEnablingMetricbeat(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.elasticsearchInstructions.metricbeatSecuritySetup"
|
||||
defaultMessage="If security features are enabled, there may be more setup required.{link}"
|
||||
values={{
|
||||
link: (
|
||||
<Fragment>
|
||||
{` `}
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.metricbeatSecuritySetupLinkText"
|
||||
defaultMessage="View more information."
|
||||
/>
|
||||
</EuiLink>
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const installMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.installMetricbeatTitle', {
|
||||
defaultMessage: 'Install Metricbeat on the same server as Elasticsearch'
|
||||
}),
|
||||
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.elasticsearchInstructions.installMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
const enableMetricbeatModuleStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.enableMetricbeatModuleTitle', {
|
||||
defaultMessage: 'Enable and configure the Elasticsearch x-pack module in Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
metricbeat modules enable elasticsearch-xpack
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.enableMetricbeatModuleDescription"
|
||||
defaultMessage="By default the module will collect Elasticsearch monitoring metrics from {url}.
|
||||
If the local Elasticsearch server has a different address,
|
||||
you must specify it via the hosts setting in the {module} file."
|
||||
values={{
|
||||
module: (
|
||||
<Monospace>modules.d/elasticsearch-xpack.yml</Monospace>
|
||||
),
|
||||
url: (
|
||||
<Monospace>http://localhost:9200</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
const configureMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.configureMetricbeatTitle', {
|
||||
defaultMessage: 'Configure Metricbeat to send to the monitoring cluster'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.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.elasticsearchInstructions.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.elasticsearchInstructions.startMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
let migrationStatusStep = null;
|
||||
if (product.isInternalCollector) {
|
||||
let status = null;
|
||||
if (hasCheckedStatus) {
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.isInternalCollectorStatusTitle', {
|
||||
defaultMessage: `We have not detected any monitoring data coming from Metricbeat for this Elasticsearch.
|
||||
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.elasticsearchInstructions.checkingStatusButtonLabel', {
|
||||
defaultMessage: 'Checking for data...'
|
||||
});
|
||||
} else {
|
||||
buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.checkStatusButtonLabel', {
|
||||
defaultMessage: 'Check for data'
|
||||
});
|
||||
}
|
||||
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.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.elasticsearchInstructions.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.elasticsearchInstructions.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 { getElasticsearchInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions';
|
||||
export { getElasticsearchInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions';
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 {
|
||||
getKibanaInstructionsForEnablingMetricbeat,
|
||||
getKibanaInstructionsForDisablingInternalCollection,
|
||||
} from './kibana';
|
||||
import {
|
||||
getElasticsearchInstructionsForEnablingMetricbeat,
|
||||
getElasticsearchInstructionsForDisablingInternalCollection
|
||||
} from './elasticsearch';
|
||||
import {
|
||||
INSTRUCTION_STEP_ENABLE_METRICBEAT,
|
||||
INSTRUCTION_STEP_DISABLE_INTERNAL
|
||||
} from '../constants';
|
||||
|
||||
export function getInstructionSteps(productName, product, step, meta, opts) {
|
||||
switch (productName) {
|
||||
case 'kibana':
|
||||
if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) {
|
||||
return getKibanaInstructionsForEnablingMetricbeat(product, meta, opts);
|
||||
}
|
||||
if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) {
|
||||
return getKibanaInstructionsForDisablingInternalCollection(product, meta, opts);
|
||||
}
|
||||
case 'elasticsearch':
|
||||
if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) {
|
||||
return getElasticsearchInstructionsForEnablingMetricbeat(product, meta, opts);
|
||||
}
|
||||
if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) {
|
||||
return getElasticsearchInstructionsForDisablingInternalCollection(product, meta, opts);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 * from './get_instruction_steps';
|
|
@ -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.kibanaInstructions.statusTitle', {
|
||||
defaultMessage: `Migration status`
|
||||
});
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* 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_kibana_instructions';
|
||||
|
||||
export function getKibanaInstructionsForDisablingInternalCollection(product, meta, {
|
||||
checkForMigrationStatus,
|
||||
checkingMigrationStatus,
|
||||
hasCheckedStatus,
|
||||
autoCheckIntervalInMs,
|
||||
}) {
|
||||
let restartWarning = null;
|
||||
if (product.isPrimary) {
|
||||
restartWarning = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.restartWarningTitle',
|
||||
{
|
||||
defaultMessage: 'Warning'
|
||||
}
|
||||
)}
|
||||
color="warning"
|
||||
iconType="help"
|
||||
>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.restartNote"
|
||||
defaultMessage="This step requires you to restart the Kibana server.
|
||||
Expect to see errors until the server is running again."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const disableInternalCollectionStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.title', {
|
||||
defaultMessage: 'Disable internal collection of Kibana monitoring metrics'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.description"
|
||||
defaultMessage="Add the following setting in the Kibana configuration file ({file}):"
|
||||
values={{
|
||||
file: (
|
||||
<Monospace>kibana.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
xpack.monitoring.kibana.collection.enabled: false
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.note"
|
||||
defaultMessage="Leave the {config} set to its default value ({defaultValue})."
|
||||
values={{
|
||||
config: (
|
||||
<Monospace>xpack.monitoring.enabled</Monospace>
|
||||
),
|
||||
defaultValue: (
|
||||
<Monospace>true</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
{restartWarning}
|
||||
</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.kibanaInstructions.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.kibanaInstructions.partiallyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: `We still see data coming from internal collection of Kibana.`
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.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.kibanaInstructions.disableInternalCollection.checkingStatusButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Checking...'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
buttonLabel = i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.checkStatusButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Check'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.monitoring.metricbeatMigration.kibanaInstructions.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.kibanaInstructions.disableInternalCollection.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.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_kibana_instructions';
|
||||
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
|
||||
|
||||
export function getKibanaInstructionsForEnablingMetricbeat(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.kibanaInstructions.metricbeatSecuritySetup"
|
||||
defaultMessage="If security features are enabled, there may be more setup required.{link}"
|
||||
values={{
|
||||
link: (
|
||||
<Fragment>
|
||||
{` `}
|
||||
<EuiLink
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.metricbeatSecuritySetupLinkText"
|
||||
defaultMessage="View more information."
|
||||
/>
|
||||
</EuiLink>
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
const installMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.installMetricbeatTitle', {
|
||||
defaultMessage: 'Install Metricbeat on the same server as Kibana'
|
||||
}),
|
||||
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.kibanaInstructions.installMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
const enableMetricbeatModuleStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.enableMetricbeatModuleTitle', {
|
||||
defaultMessage: 'Enable and configure the Kibana x-pack module in Metricbeat'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiCodeBlock
|
||||
isCopyable
|
||||
language="bash"
|
||||
>
|
||||
metricbeat modules enable kibana-xpack
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.enableMetricbeatModuleDescription"
|
||||
defaultMessage="By default the module will collect Kibana monitoring metrics from http://localhost:5601. If the local Kibana 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/kibana-xpack.yml</Monospace>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
{securitySetup}
|
||||
</Fragment>
|
||||
)
|
||||
};
|
||||
|
||||
const configureMetricbeatStep = {
|
||||
title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.configureMetricbeatTitle', {
|
||||
defaultMessage: 'Configure Metricbeat to send to the monitoring cluster'
|
||||
}),
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.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.kibanaInstructions.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.kibanaInstructions.startMetricbeatLinkText"
|
||||
defaultMessage="Follow the instructions here"
|
||||
/>
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
)
|
||||
};
|
||||
|
||||
let migrationStatusStep = null;
|
||||
if (product.isInternalCollector) {
|
||||
let status = null;
|
||||
if (hasCheckedStatus) {
|
||||
status = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
color="warning"
|
||||
title={i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.isInternalCollectorStatusTitle', {
|
||||
defaultMessage: `We have not detected any monitoring data coming from Metricbeat for this Kibana.
|
||||
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.kibanaInstructions.checkingStatusButtonLabel', {
|
||||
defaultMessage: 'Checking for data...'
|
||||
});
|
||||
} else {
|
||||
buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.checkStatusButtonLabel', {
|
||||
defaultMessage: 'Check for data'
|
||||
});
|
||||
}
|
||||
|
||||
migrationStatusStep = {
|
||||
title: statusTitle,
|
||||
status: 'incomplete',
|
||||
children: (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.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.kibanaInstructions.fullyMigratedStatusTitle',
|
||||
{
|
||||
defaultMessage: 'Congratulations!'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.metricbeatMigration.kibanaInstructions.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 { getKibanaInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions';
|
||||
export { getKibanaInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions';
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { SetupModeRenderer } from './setup_mode';
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { setAngularState, getSetupModeState, initSetupModeState, updateSetupModeData } from '../../lib/setup_mode';
|
||||
import { Flyout } from '../metricbeat_migration/flyout';
|
||||
import { ELASTICSEARCH_CUSTOM_ID } from '../../../common/constants';
|
||||
|
||||
export class SetupModeRenderer extends React.Component {
|
||||
state = {
|
||||
renderState: false,
|
||||
isFlyoutOpen: false,
|
||||
instance: null,
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { scope, injector } = this.props;
|
||||
setAngularState(scope, injector);
|
||||
initSetupModeState(() => this.setState({ renderState: true }));
|
||||
}
|
||||
|
||||
getFlyout(data, meta) {
|
||||
const { productName } = this.props;
|
||||
const { isFlyoutOpen, instance } = 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];
|
||||
}
|
||||
|
||||
return (
|
||||
<Flyout
|
||||
onClose={() => this.setState({ isFlyoutOpen: false })}
|
||||
productName={productName}
|
||||
product={product}
|
||||
meta={meta}
|
||||
instance={instance}
|
||||
updateProduct={updateSetupModeData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { render, productName } = this.props;
|
||||
const setupModeState = getSetupModeState();
|
||||
const data = setupModeState.data ? setupModeState.data[productName] : null;
|
||||
const meta = setupModeState.data ? setupModeState.data._meta : null;
|
||||
|
||||
return render({
|
||||
setupMode: {
|
||||
data,
|
||||
enabled: setupModeState.enabled,
|
||||
productName,
|
||||
updateSetupModeData,
|
||||
openFlyout: (instance) => this.setState({ isFlyoutOpen: true, instance }),
|
||||
closeFlyout: () => this.setState({ isFlyoutOpen: false }),
|
||||
},
|
||||
flyoutComponent: this.getFlyout(data, meta),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,9 +5,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
EuiInMemoryTable
|
||||
EuiInMemoryTable,
|
||||
EuiBadge,
|
||||
EuiButtonEmpty,
|
||||
EuiHealth
|
||||
} from '@elastic/eui';
|
||||
import { ELASTICSEARCH_CUSTOM_ID } from '../../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class EuiMonitoringTable extends React.PureComponent {
|
||||
render() {
|
||||
|
@ -15,6 +21,9 @@ export class EuiMonitoringTable extends React.PureComponent {
|
|||
rows: items,
|
||||
search = {},
|
||||
columns: _columns,
|
||||
setupMode,
|
||||
uuidField,
|
||||
nameField,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
|
@ -37,6 +46,89 @@ export class EuiMonitoringTable extends React.PureComponent {
|
|||
return column;
|
||||
});
|
||||
|
||||
if (setupMode && setupMode.enabled) {
|
||||
columns.push({
|
||||
name: i18n.translate('xpack.monitoring.euiTable.setupStatusTitle', {
|
||||
defaultMessage: 'Setup Status'
|
||||
}),
|
||||
field: uuidField,
|
||||
render: (uuid) => {
|
||||
const list = get(setupMode, 'data.byUuid', {});
|
||||
const status = list[uuid] || {};
|
||||
|
||||
let statusBadge = null;
|
||||
if (status.isInternalCollector) {
|
||||
statusBadge = (
|
||||
<EuiHealth color="danger">
|
||||
{i18n.translate('xpack.monitoring.euiTable.isInternalCollectorLabel', {
|
||||
defaultMessage: 'Internal collection'
|
||||
})}
|
||||
</EuiHealth>
|
||||
);
|
||||
}
|
||||
else if (status.isPartiallyMigrated) {
|
||||
statusBadge = (
|
||||
<EuiHealth color="warning">
|
||||
{i18n.translate('xpack.monitoring.euiTable.isPartiallyMigratedLabel', {
|
||||
defaultMessage: 'Internal collection and Metricbeat collection'
|
||||
})}
|
||||
</EuiHealth>
|
||||
);
|
||||
}
|
||||
else if (status.isFullyMigrated) {
|
||||
statusBadge = (
|
||||
<EuiBadge color="primary">
|
||||
{i18n.translate('xpack.monitoring.euiTable.isFullyMigratedLabel', {
|
||||
defaultMessage: 'Metricbeat collection'
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
else {
|
||||
statusBadge = i18n.translate('xpack.monitoring.euiTable.migrationStatusUnknown', {
|
||||
defaultMessage: 'N/A'
|
||||
});
|
||||
}
|
||||
|
||||
return statusBadge;
|
||||
}
|
||||
});
|
||||
|
||||
columns.push({
|
||||
name: i18n.translate('xpack.monitoring.euiTable.setupActionTitle', {
|
||||
defaultMessage: 'Setup Action'
|
||||
}),
|
||||
field: uuidField,
|
||||
render: (uuid, product) => {
|
||||
const list = get(setupMode, 'data.byUuid', {});
|
||||
const status = list[uuid] || {};
|
||||
const instance = {
|
||||
uuid: get(product, uuidField),
|
||||
name: get(product, nameField),
|
||||
};
|
||||
|
||||
// Migrating from partially to fully for Elasticsearch involves changing a cluster
|
||||
// setting which impacts all nodes in the cluster, which we have a separate callout
|
||||
// for. Since it does not make sense to do this on a per node basis, show nothing here
|
||||
if (status.isPartiallyMigrated && setupMode.productName === ELASTICSEARCH_CUSTOM_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status.isInternalCollector || status.isPartiallyMigrated) {
|
||||
return (
|
||||
<EuiButtonEmpty flush="left" size="s" color="primary" onClick={() => setupMode.openFlyout(instance)}>
|
||||
{i18n.translate('xpack.monitoring.euiTable.migrateButtonLabel', {
|
||||
defaultMessage: 'Migrate'
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj={`${this.props.className}Container`}>
|
||||
<EuiInMemoryTable
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="app-container">
|
||||
<kbn-top-nav name="{{ monitoringMain.name }}-nav">
|
||||
<kbn-top-nav name="{{ monitoringMain.name }}-nav" config="topNavMenu">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Tabs -->
|
||||
|
|
45
x-pack/plugins/monitoring/public/lib/ensure_minimum_time.js
Normal file
45
x-pack/plugins/monitoring/public/lib/ensure_minimum_time.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* When you make an async request, typically you want to show the user a spinner while they wait.
|
||||
* However, if the request takes less than 300 ms, the spinner will flicker in the UI and the user
|
||||
* won't have time to register it as a spinner. This function ensures the spinner (or whatever
|
||||
* you're showing the user) displays for at least 300 ms, even if the request completes before then.
|
||||
*/
|
||||
|
||||
export const DEFAULT_MINIMUM_TIME_MS = 300;
|
||||
|
||||
export async function ensureMinimumTime(promiseOrPromises, minimumTimeMs = DEFAULT_MINIMUM_TIME_MS) {
|
||||
let returnValue;
|
||||
|
||||
// https://kibana-ci.elastic.co/job/elastic+kibana+6.x+multijob-intake/128/console
|
||||
// We're having periodic failures around the timing here. I'm not exactly sure
|
||||
// why it's not consistent but I'm going to add some buffer space here to
|
||||
// prevent these random failures
|
||||
const bufferedMinimumTimeMs = minimumTimeMs + 5;
|
||||
|
||||
// Block on the async action and start the clock.
|
||||
const asyncActionStartTime = new Date().getTime();
|
||||
if (Array.isArray(promiseOrPromises)) {
|
||||
returnValue = await Promise.all(promiseOrPromises);
|
||||
} else {
|
||||
returnValue = await promiseOrPromises;
|
||||
}
|
||||
|
||||
// Measure how long the async action took to complete.
|
||||
const asyncActionCompletionTime = new Date().getTime();
|
||||
const asyncActionDuration = asyncActionCompletionTime - asyncActionStartTime;
|
||||
|
||||
// Wait longer if the async action completed too quickly.
|
||||
if (asyncActionDuration < bufferedMinimumTimeMs) {
|
||||
const additionalWaitingTime = bufferedMinimumTimeMs - (asyncActionCompletionTime - asyncActionStartTime);
|
||||
await new Promise(resolve => setTimeout(resolve, additionalWaitingTime));
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { ensureMinimumTime } from './ensure_minimum_time';
|
||||
|
||||
describe('ensureMinimumTime', () => {
|
||||
it('resolves single promise', async (done) => {
|
||||
const promiseA = new Promise(resolve => resolve('a'));
|
||||
const a = await ensureMinimumTime(promiseA, 0);
|
||||
expect(a).toBe('a');
|
||||
done();
|
||||
});
|
||||
|
||||
it('resolves multiple promises', async (done) => {
|
||||
const promiseA = new Promise(resolve => resolve('a'));
|
||||
const promiseB = new Promise(resolve => resolve('b'));
|
||||
const [ a, b ] = await ensureMinimumTime([promiseA, promiseB], 0);
|
||||
expect(a).toBe('a');
|
||||
expect(b).toBe('b');
|
||||
done();
|
||||
});
|
||||
|
||||
it('resolves in the amount of time provided, at minimum', async (done) => {
|
||||
const startTime = new Date().getTime();
|
||||
const promise = new Promise(resolve => resolve());
|
||||
await ensureMinimumTime(promise, 100);
|
||||
const endTime = new Date().getTime();
|
||||
expect(endTime - startTime).toBeGreaterThanOrEqual(100);
|
||||
done();
|
||||
});
|
||||
});
|
126
x-pack/plugins/monitoring/public/lib/setup_mode.js
Normal file
126
x-pack/plugins/monitoring/public/lib/setup_mode.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 { ajaxErrorHandlersProvider } from './ajax_error_handler';
|
||||
|
||||
const angularState = {
|
||||
injector: null,
|
||||
scope: null,
|
||||
};
|
||||
|
||||
export const setAngularState = ($scope, $injector) => {
|
||||
angularState.scope = $scope;
|
||||
angularState.injector = $injector;
|
||||
};
|
||||
const checkAngularState = () => {
|
||||
if (!angularState.injector || !angularState.scope) {
|
||||
throw 'Unable to interact with setup mode because the angular injector was not previously set.'
|
||||
+ ' This needs to be set by calling `setAngularState`.';
|
||||
}
|
||||
};
|
||||
|
||||
const setupModeState = {
|
||||
enabled: false,
|
||||
data: null,
|
||||
callbacks: []
|
||||
};
|
||||
|
||||
export const getSetupModeState = () => setupModeState;
|
||||
|
||||
export const fetchCollectionData = async () => {
|
||||
checkAngularState();
|
||||
|
||||
const http = angularState.injector.get('$http');
|
||||
const globalState = angularState.injector.get('globalState');
|
||||
const clusterUuid = globalState.cluster_uuid;
|
||||
const ccs = globalState.ccs;
|
||||
|
||||
let url = '../api/monitoring/v1/setup/collection';
|
||||
if (clusterUuid) {
|
||||
url += `/${clusterUuid}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await http.post(url, { ccs });
|
||||
return response.data;
|
||||
}
|
||||
catch (err) {
|
||||
const Private = angularState.injector.get('Private');
|
||||
const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
|
||||
return ajaxErrorHandlers(err);
|
||||
}
|
||||
};
|
||||
|
||||
const notifySetupModeDataChange = () => {
|
||||
setupModeState.callbacks.forEach(cb => cb());
|
||||
};
|
||||
|
||||
export const updateSetupModeData = async () => {
|
||||
setupModeState.data = await fetchCollectionData();
|
||||
notifySetupModeDataChange();
|
||||
};
|
||||
|
||||
export const toggleSetupMode = inSetupMode => {
|
||||
checkAngularState();
|
||||
|
||||
const globalState = angularState.injector.get('globalState');
|
||||
angularState.scope.$evalAsync(async () => {
|
||||
setupModeState.enabled = inSetupMode;
|
||||
globalState.inSetupMode = inSetupMode;
|
||||
globalState.save();
|
||||
setSetupModeMenuItem(); // eslint-disable-line no-use-before-define
|
||||
notifySetupModeDataChange();
|
||||
|
||||
if (inSetupMode) {
|
||||
await updateSetupModeData();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const setSetupModeMenuItem = () => {
|
||||
// Disabling this for this initial release. This will be added back in
|
||||
// in a subsequent PR
|
||||
// checkAngularState();
|
||||
|
||||
// const globalState = angularState.injector.get('globalState');
|
||||
// const navItems = globalState.inSetupMode
|
||||
// ? [
|
||||
// {
|
||||
// key: 'exit',
|
||||
// label: 'Exit Setup Mode',
|
||||
// description: 'Exit setup mode',
|
||||
// run: () => toggleSetupMode(false),
|
||||
// testId: 'exitSetupMode'
|
||||
// },
|
||||
// {
|
||||
// key: 'refresh',
|
||||
// label: 'Refresh Setup Data',
|
||||
// description: 'Refresh data used for setup mode',
|
||||
// run: () => updateSetupModeData(),
|
||||
// testId: 'refreshSetupModeData'
|
||||
// }
|
||||
// ]
|
||||
// : [{
|
||||
// key: 'enter',
|
||||
// label: 'Enter Setup Mode',
|
||||
// description: 'Enter setup mode',
|
||||
// run: () => toggleSetupMode(true),
|
||||
// testId: 'enterSetupMode'
|
||||
// }];
|
||||
|
||||
// angularState.scope.topNavMenu = [...navItems];
|
||||
};
|
||||
|
||||
export const initSetupModeState = (callback) => {
|
||||
checkAngularState();
|
||||
setSetupModeMenuItem();
|
||||
callback && setupModeState.callbacks.push(callback);
|
||||
|
||||
const globalState = angularState.injector.get('globalState');
|
||||
if (globalState.inSetupMode) {
|
||||
toggleSetupMode(true);
|
||||
}
|
||||
};
|
|
@ -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,7 @@ import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
|
|||
import { MonitoringViewBaseEuiTableController } from '../../';
|
||||
import { ElasticsearchNodes } from '../../../components';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
|
||||
uiRoutes.when('/elasticsearch/nodes', {
|
||||
template,
|
||||
|
@ -54,14 +55,25 @@ uiRoutes.when('/elasticsearch/nodes', {
|
|||
this.renderReact = ({ clusterStatus, nodes }) => {
|
||||
super.renderReact(
|
||||
<I18nContext>
|
||||
<SetupModeRenderer
|
||||
scope={$scope}
|
||||
injector={$injector}
|
||||
productName="elasticsearch"
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
<ElasticsearchNodes
|
||||
clusterStatus={clusterStatus}
|
||||
setupMode={setupMode}
|
||||
nodes={nodes}
|
||||
showCgroupMetricsElasticsearch={showCgroupMetricsElasticsearch}
|
||||
sorting={this.sorting}
|
||||
pagination={this.pagination}
|
||||
onTableChange={this.onTableChange}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
</I18nContext>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,111 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { capitalize } from 'lodash';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import React, { Fragment } from 'react';
|
||||
import uiRoutes from'ui/routes';
|
||||
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
|
||||
import { MonitoringViewBaseEuiTableController } from '../../';
|
||||
import { getPageData } from './get_page_data';
|
||||
import template from './index.html';
|
||||
import { EuiPage, EuiPageBody, EuiPageContent, EuiPanel, EuiSpacer, EuiLink } from '@elastic/eui';
|
||||
import { ClusterStatus } from '../../../components/kibana/cluster_status';
|
||||
import { EuiMonitoringTable } from '../../../components/table';
|
||||
import { KibanaStatusIcon } from '../../../components/kibana/status_icon';
|
||||
import { formatMetric, formatNumber } from '../../../lib/format_number';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
const getColumns = (kbnUrl, scope) => ([
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', {
|
||||
defaultMessage: 'Name'
|
||||
}),
|
||||
field: 'name',
|
||||
render: (name, kibana) => (
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
scope.$evalAsync(() => {
|
||||
kbnUrl.changePath(`/kibana/instances/${kibana.kibana.uuid}`);
|
||||
});
|
||||
}}
|
||||
data-test-subj={`kibanaLink-${name}`}
|
||||
>
|
||||
{ name }
|
||||
</EuiLink>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', {
|
||||
defaultMessage: 'Status'
|
||||
}),
|
||||
field: 'status',
|
||||
render: (status, kibana) => (
|
||||
<div
|
||||
title={`Instance status: ${status}`}
|
||||
className="monTableCell__status"
|
||||
>
|
||||
<KibanaStatusIcon status={status} availability={kibana.availability} />
|
||||
{ !kibana.availability ? (
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.kibana.listing.instanceStatus.offlineLabel"
|
||||
defaultMessage="Offline"
|
||||
/>
|
||||
) : capitalize(status) }
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.loadAverageColumnTitle', {
|
||||
defaultMessage: 'Load Average'
|
||||
}),
|
||||
field: 'os.load.1m',
|
||||
render: value => (
|
||||
<span>
|
||||
{formatMetric(value, '0.00')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.memorySizeColumnTitle', {
|
||||
defaultMessage: 'Memory Size'
|
||||
}),
|
||||
field: 'process.memory.resident_set_size_in_bytes',
|
||||
render: value => (
|
||||
<span>
|
||||
{formatNumber(value, 'byte')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.requestsColumnTitle', {
|
||||
defaultMessage: 'Requests'
|
||||
}),
|
||||
field: 'requests.total',
|
||||
render: value => (
|
||||
<span>
|
||||
{formatNumber(value, 'int_commas')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.kibana.listing.responseTimeColumnTitle', {
|
||||
defaultMessage: 'Response Times'
|
||||
}),
|
||||
field: 'response_times.average',
|
||||
render: (value, kibana) => (
|
||||
<div>
|
||||
<div className="monTableCell__splitNumber">
|
||||
{ value && (formatNumber(value, 'int_commas') + ' ms avg') }
|
||||
</div>
|
||||
<div className="monTableCell__splitNumber">
|
||||
{ formatNumber(kibana.response_times.max, 'int_commas') } ms max
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]);
|
||||
import { KibanaInstances } from 'plugins/monitoring/components/kibana/instances';
|
||||
import { SetupModeRenderer } from '../../../components/renderers';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
|
||||
uiRoutes.when('/kibana/instances', {
|
||||
template,
|
||||
|
@ -134,51 +38,41 @@ uiRoutes.when('/kibana/instances', {
|
|||
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
|
||||
const renderReact = () => {
|
||||
this.renderReact(
|
||||
<I18nContext>
|
||||
<SetupModeRenderer
|
||||
scope={$scope}
|
||||
injector={$injector}
|
||||
productName="kibana"
|
||||
render={({ setupMode, flyoutComponent }) => (
|
||||
<Fragment>
|
||||
{flyoutComponent}
|
||||
<KibanaInstances
|
||||
instances={this.data.kibanas}
|
||||
setupMode={setupMode}
|
||||
sorting={this.sorting}
|
||||
pagination={this.pagination}
|
||||
onTableChange={this.onTableChange}
|
||||
clusterStatus={this.data.clusterStatus}
|
||||
angular={{
|
||||
$scope,
|
||||
kbnUrl,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
</I18nContext>
|
||||
);
|
||||
};
|
||||
|
||||
$scope.$watch(() => this.data, data => {
|
||||
if (!data || !data.kibanas) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataFlattened = data.kibanas.map(item => ({
|
||||
...item,
|
||||
name: item.kibana.name,
|
||||
status: item.kibana.status,
|
||||
}));
|
||||
|
||||
this.renderReact(
|
||||
<I18nContext>
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPanel>
|
||||
<ClusterStatus stats={$scope.pageData.clusterStatus} />
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPageContent>
|
||||
<EuiMonitoringTable
|
||||
className="kibanaInstancesTable"
|
||||
rows={dataFlattened}
|
||||
columns={getColumns(kbnUrl, $scope)}
|
||||
sorting={this.sorting}
|
||||
pagination={this.pagination}
|
||||
search={{
|
||||
box: {
|
||||
incremental: true,
|
||||
placeholder: i18n.translate('xpack.monitoring.kibana.listing.filterInstancesPlaceholder', {
|
||||
defaultMessage: 'Filter Instances…'
|
||||
})
|
||||
},
|
||||
}}
|
||||
onTableChange={this.onTableChange}
|
||||
executeQueryOptions={{
|
||||
defaultFields: ['name']
|
||||
}}
|
||||
/>
|
||||
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
</I18nContext>
|
||||
);
|
||||
renderReact();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
*/
|
||||
|
||||
import { get, uniq } from 'lodash';
|
||||
import { METRICBEAT_INDEX_NAME_UNIQUE_TOKEN } from '../../../../common/constants';
|
||||
import { METRICBEAT_INDEX_NAME_UNIQUE_TOKEN, ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants';
|
||||
import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID, LOGSTASH_SYSTEM_ID } from '../../../../../telemetry/common/constants';
|
||||
|
||||
const NUMBER_OF_SECONDS_AGO_TO_LOOK = 30;
|
||||
const APM_CUSTOM_ID = 'apm';
|
||||
const ELASTICSEARCH_CUSTOM_ID = 'elasticsearch';
|
||||
|
||||
const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) => {
|
||||
const start = get(req.payload, 'timeRange.min', 'now-30s');
|
||||
const start = get(req.payload, 'timeRange.min', `now-${NUMBER_OF_SECONDS_AGO_TO_LOOK}s`);
|
||||
const end = get(req.payload, 'timeRange.max', 'now');
|
||||
|
||||
const filters = [
|
||||
|
@ -247,6 +247,9 @@ function shouldSkipBucket(product, bucket) {
|
|||
* @param {*} clusterUuid Optional and will be used to filter down the query if used
|
||||
*/
|
||||
export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
||||
const config = req.server.config();
|
||||
const kibanaUuid = config.get('server.uuid');
|
||||
|
||||
const PRODUCTS = [
|
||||
{ name: KIBANA_SYSTEM_ID },
|
||||
{ name: BEATS_SYSTEM_ID },
|
||||
|
@ -273,8 +276,9 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
|||
const productStatus = {
|
||||
totalUniqueInstanceCount: 0,
|
||||
totalUniqueFullyMigratedCount: 0,
|
||||
totalUniquePartiallyMigratedCount: 0,
|
||||
detected: null,
|
||||
byUuid: null,
|
||||
byUuid: {},
|
||||
};
|
||||
|
||||
const fullyMigratedUuidsMap = {};
|
||||
|
@ -291,7 +295,6 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
|||
const singleIndexBucket = indexBuckets[0];
|
||||
const isFullyMigrated = singleIndexBucket.key.includes(METRICBEAT_INDEX_NAME_UNIQUE_TOKEN);
|
||||
|
||||
|
||||
const map = isFullyMigrated ? fullyMigratedUuidsMap : internalCollectorsUuidsMap;
|
||||
const uuidBuckets = get(singleIndexBucket, `${uuidBucketName}.buckets`, []);
|
||||
for (const bucket of uuidBuckets) {
|
||||
|
@ -301,9 +304,13 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
|||
const { key, by_timestamp: byTimestamp } = bucket;
|
||||
if (!map[key]) {
|
||||
map[key] = { lastTimestamp: get(byTimestamp, 'value') };
|
||||
if (product.name === KIBANA_SYSTEM_ID && key === kibanaUuid) {
|
||||
map[key].isPrimary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
productStatus.totalUniqueInstanceCount = Object.keys(map).length;
|
||||
productStatus.totalUniquePartiallyMigratedCount = Object.keys(partiallyMigratedUuidsMap).length;
|
||||
productStatus.totalUniqueFullyMigratedCount = Object.keys(fullyMigratedUuidsMap).length;
|
||||
productStatus.byUuid = {
|
||||
...Object.keys(internalCollectorsUuidsMap).reduce((accum, uuid) => ({
|
||||
|
@ -337,11 +344,14 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
|||
const { key, by_timestamp: byTimestamp } = bucket;
|
||||
if (!map[key]) {
|
||||
if (otherMap[key]) {
|
||||
partiallyMigratedUuidsMap[key] = true;
|
||||
partiallyMigratedUuidsMap[key] = otherMap[key] || {};
|
||||
delete otherMap[key];
|
||||
}
|
||||
else {
|
||||
map[key] = true;
|
||||
map[key] = {};
|
||||
if (product.name === KIBANA_SYSTEM_ID && key === kibanaUuid) {
|
||||
map[key].isPrimary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isFullyMigrated) {
|
||||
|
@ -355,19 +365,30 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
|||
...Object.keys(fullyMigratedUuidsMap),
|
||||
...Object.keys(partiallyMigratedUuidsMap)
|
||||
]).length;
|
||||
productStatus.totalUniquePartiallyMigratedCount = Object.keys(partiallyMigratedUuidsMap).length;
|
||||
productStatus.totalUniqueFullyMigratedCount = Object.keys(fullyMigratedUuidsMap).length;
|
||||
productStatus.byUuid = {
|
||||
...Object.keys(internalCollectorsUuidsMap).reduce((accum, uuid) => ({
|
||||
...accum,
|
||||
[uuid]: { isInternalCollector: true }
|
||||
[uuid]: {
|
||||
isInternalCollector: true,
|
||||
...internalCollectorsUuidsMap[uuid]
|
||||
}
|
||||
}), {}),
|
||||
...Object.keys(partiallyMigratedUuidsMap).reduce((accum, uuid) => ({
|
||||
...accum,
|
||||
[uuid]: { isPartiallyMigrated: true, lastInternallyCollectedTimestamp: internalTimestamps[0] }
|
||||
[uuid]: {
|
||||
isPartiallyMigrated: true,
|
||||
lastInternallyCollectedTimestamp: internalTimestamps[0],
|
||||
...partiallyMigratedUuidsMap[uuid]
|
||||
}
|
||||
}), {}),
|
||||
...Object.keys(fullyMigratedUuidsMap).reduce((accum, uuid) => ({
|
||||
...accum,
|
||||
[uuid]: { isFullyMigrated: true }
|
||||
[uuid]: {
|
||||
isFullyMigrated: true,
|
||||
...fullyMigratedUuidsMap[uuid]
|
||||
}
|
||||
}), {}),
|
||||
};
|
||||
}
|
||||
|
@ -378,5 +399,9 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => {
|
|||
};
|
||||
}, {});
|
||||
|
||||
status._meta = {
|
||||
secondsAgo: NUMBER_OF_SECONDS_AGO_TO_LOOK,
|
||||
};
|
||||
|
||||
return status;
|
||||
};
|
||||
|
|
|
@ -1,42 +1,50 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"beats": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 1,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"5b2de169-2785-441b-ae8c-186a1936b17d": {
|
||||
"isFullyMigrated": true,
|
||||
"isPrimary": true,
|
||||
"lastTimestamp": 1554841069921
|
||||
}
|
||||
}
|
||||
|
@ -13,30 +18,34 @@
|
|||
"beats": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 1,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"agI8JhXhShasvuDgq0VxRg": {
|
||||
|
|
|
@ -1,42 +1,50 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"beats": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,50 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"beats": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,50 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"beats": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"mightExist": false
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
},
|
||||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": {
|
||||
"doesExist": true
|
||||
},
|
||||
"byUuid": null
|
||||
"byUuid": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 1,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"5b2de169-2785-441b-ae8c-186a1936b17d": {
|
||||
"isFullyMigrated": true,
|
||||
"isPrimary": true,
|
||||
"lastTimestamp": 1554821587077
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +18,7 @@
|
|||
"beats": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
|
@ -24,12 +30,14 @@
|
|||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
|
@ -41,6 +49,7 @@
|
|||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 1,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"agI8JhXhShasvuDgq0VxRg": {
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 1,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"5b2de169-2785-441b-ae8c-186a1936b17d": {
|
||||
"isPartiallyMigrated": true,
|
||||
"isPrimary": true,
|
||||
"lastInternallyCollectedTimestamp": 1554821412725
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +18,7 @@
|
|||
"beats": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
|
@ -24,12 +30,14 @@
|
|||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
|
@ -41,6 +49,7 @@
|
|||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 1,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"agI8JhXhShasvuDgq0VxRg": {
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 1,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"5b2de169-2785-441b-ae8c-186a1936b17d": {
|
||||
"isFullyMigrated": true,
|
||||
"isPrimary": true,
|
||||
"lastTimestamp": 1554821537079
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +18,7 @@
|
|||
"beats": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
|
@ -24,12 +30,14 @@
|
|||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
|
@ -41,6 +49,7 @@
|
|||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 1,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"agI8JhXhShasvuDgq0VxRg": {
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{
|
||||
"_meta": {
|
||||
"secondsAgo": 30
|
||||
},
|
||||
"kibana": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 1,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"5b2de169-2785-441b-ae8c-186a1936b17d": {
|
||||
"isPartiallyMigrated": true,
|
||||
"isPrimary": true,
|
||||
"lastInternallyCollectedTimestamp": 1554821352739
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +18,7 @@
|
|||
"beats": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"8eba4902-df80-43b0-b6c2-ed8ca290984e": {
|
||||
|
@ -24,12 +30,14 @@
|
|||
"apm": {
|
||||
"totalUniqueInstanceCount": 0,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {}
|
||||
},
|
||||
"logstash": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"4134a00e-89e4-4896-a3d4-c3a9aa03a594": {
|
||||
|
@ -41,6 +49,7 @@
|
|||
"elasticsearch": {
|
||||
"totalUniqueInstanceCount": 1,
|
||||
"totalUniqueFullyMigratedCount": 0,
|
||||
"totalUniquePartiallyMigratedCount": 0,
|
||||
"detected": null,
|
||||
"byUuid": {
|
||||
"agI8JhXhShasvuDgq0VxRg": {
|
||||
|
|
Loading…
Reference in a new issue