[Monitoring] NP Migration complete client cutover (#62908)

* Final phase before the complete cutover

* NP migration

* lint fix

* More NP stuff

* Moved Stack Monitoring client plugin outside legacy and fixed all tests

* ...

* Removed unused files

* Fix for main links

* Fixed more tests

* Fixed redirect when clicking on SM icon again

* Code review feedback

* Addressed code review feedback

* Fixed return value
This commit is contained in:
igoristic 2020-04-30 15:59:35 -04:00 committed by GitHub
parent 6e3791ea12
commit 59315bc84d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
569 changed files with 11410 additions and 2967 deletions

View file

@ -30,6 +30,7 @@ target
/x-pack/legacy/plugins/canvas/canvas_plugin_src/lib/flot-charts
/x-pack/legacy/plugins/canvas/shareable_runtime/build
/x-pack/legacy/plugins/canvas/storybook
/x-pack/plugins/monitoring/public/lib/jquery_flot
/x-pack/legacy/plugins/infra/common/graphql/types.ts
/x-pack/legacy/plugins/infra/public/graphql/types.ts
/x-pack/legacy/plugins/infra/server/graphql/types.ts

View file

@ -963,6 +963,12 @@ module.exports = {
jquery: true,
},
},
{
files: ['x-pack/plugins/monitoring/public/lib/jquery_flot/**/*.js'],
env: {
jquery: true,
},
},
/**
* TSVB overrides

1
.github/CODEOWNERS vendored
View file

@ -84,6 +84,7 @@
/x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management
/x-pack/plugins/observability/ @elastic/logs-metrics-ui @elastic/apm-ui @elastic/uptime @elastic/ingest-management
/x-pack/legacy/plugins/monitoring/ @elastic/stack-monitoring-ui
/x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui
/x-pack/plugins/uptime @elastic/uptime
# Machine Learning

View file

@ -38,6 +38,7 @@ export const IGNORE_FILE_GLOBS = [
'x-pack/legacy/plugins/apm/**/*',
'x-pack/legacy/plugins/canvas/tasks/**/*',
'x-pack/legacy/plugins/canvas/canvas_plugin_src/**/*',
'x-pack/plugins/monitoring/public/lib/jquery_flot/**/*',
'**/.*',
'**/{webpackShims,__mocks__}/**/*',
'x-pack/docs/**/*',
@ -160,12 +161,11 @@ export const TEMPORARILY_IGNORED_PATHS = [
'webpackShims/ui-bootstrap.js',
'x-pack/legacy/plugins/index_management/public/lib/editSettings.js',
'x-pack/legacy/plugins/license_management/public/store/reducers/licenseManagement.js',
'x-pack/legacy/plugins/monitoring/public/components/sparkline/__mocks__/plugins/xpack_main/jquery_flot.js',
'x-pack/legacy/plugins/monitoring/public/icons/alert-blue.svg',
'x-pack/legacy/plugins/monitoring/public/icons/health-gray.svg',
'x-pack/legacy/plugins/monitoring/public/icons/health-green.svg',
'x-pack/legacy/plugins/monitoring/public/icons/health-red.svg',
'x-pack/legacy/plugins/monitoring/public/icons/health-yellow.svg',
'x-pack/plugins/monitoring/public/components/sparkline/__mocks__/plugins/xpack_main/jquery_flot.js',
'x-pack/plugins/monitoring/public/icons/health-gray.svg',
'x-pack/plugins/monitoring/public/icons/health-green.svg',
'x-pack/plugins/monitoring/public/icons/health-red.svg',
'x-pack/plugins/monitoring/public/icons/health-yellow.svg',
'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Medium.ttf',
'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/noto/NotoSansCJKtc-Regular.ttf',
'x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/pdf/assets/fonts/roboto/Roboto-Italic.ttf',

View file

@ -1,3 +0,0 @@
agent
node_modules
bower_components

View file

@ -1,128 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import moment from 'moment';
import { formatTimestampToDuration } from '../format_timestamp_to_duration';
import { CALCULATE_DURATION_SINCE, CALCULATE_DURATION_UNTIL } from '../constants';
const testTime = moment('2010-05-01'); // pick a date where adding/subtracting 2 months formats roundly to '2 months 0 days'
const getTestTime = () => moment(testTime); // clones the obj so it's not mutated with .adds and .subtracts
/**
* Test the moment-duration-format template
*/
describe('formatTimestampToDuration', () => {
describe('format timestamp to duration - time since', () => {
it('should format timestamp to human-readable duration', () => {
// time inputs are a few "moments" extra from the time advertised by name
const fiftyNineSeconds = getTestTime().subtract(59, 'seconds');
expect(
formatTimestampToDuration(fiftyNineSeconds, CALCULATE_DURATION_SINCE, getTestTime())
).to.be('59 seconds');
const fiveMins = getTestTime()
.subtract(5, 'minutes')
.subtract(30, 'seconds');
expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'6 mins'
);
const sixHours = getTestTime()
.subtract(6, 'hours')
.subtract(30, 'minutes');
expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'6 hrs 30 mins'
);
const sevenDays = getTestTime()
.subtract(7, 'days')
.subtract(6, 'hours')
.subtract(18, 'minutes');
expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'7 days 6 hrs 18 mins'
);
const eightWeeks = getTestTime()
.subtract(8, 'weeks')
.subtract(7, 'days')
.subtract(6, 'hours')
.subtract(18, 'minutes');
expect(formatTimestampToDuration(eightWeeks, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'2 months 2 days'
);
const oneHour = getTestTime().subtract(1, 'hour'); // should trim 0 min
expect(formatTimestampToDuration(oneHour, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'1 hr'
);
const oneDay = getTestTime().subtract(1, 'day'); // should trim 0 hrs
expect(formatTimestampToDuration(oneDay, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'1 day'
);
const twoMonths = getTestTime().subtract(2, 'month'); // should trim 0 days
expect(formatTimestampToDuration(twoMonths, CALCULATE_DURATION_SINCE, getTestTime())).to.be(
'2 months'
);
});
});
describe('format timestamp to duration - time until', () => {
it('should format timestamp to human-readable duration', () => {
// time inputs are a few "moments" extra from the time advertised by name
const fiftyNineSeconds = getTestTime().add(59, 'seconds');
expect(
formatTimestampToDuration(fiftyNineSeconds, CALCULATE_DURATION_UNTIL, getTestTime())
).to.be('59 seconds');
const fiveMins = getTestTime().add(10, 'minutes');
expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'10 mins'
);
const sixHours = getTestTime()
.add(6, 'hours')
.add(30, 'minutes');
expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'6 hrs 30 mins'
);
const sevenDays = getTestTime()
.add(7, 'days')
.add(6, 'hours')
.add(18, 'minutes');
expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'7 days 6 hrs 18 mins'
);
const eightWeeks = getTestTime()
.add(8, 'weeks')
.add(7, 'days')
.add(6, 'hours')
.add(18, 'minutes');
expect(formatTimestampToDuration(eightWeeks, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'2 months 2 days'
);
const oneHour = getTestTime().add(1, 'hour'); // should trim 0 min
expect(formatTimestampToDuration(oneHour, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'1 hr'
);
const oneDay = getTestTime().add(1, 'day'); // should trim 0 hrs
expect(formatTimestampToDuration(oneDay, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'1 day'
);
const twoMonths = getTestTime().add(2, 'month'); // should trim 0 days
expect(formatTimestampToDuration(twoMonths, CALCULATE_DURATION_UNTIL, getTestTime())).to.be(
'2 months'
);
});
});
});

View file

@ -1,70 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export enum Status {
Canceled,
Failed,
Resolved,
Awaiting,
Idle,
}
/**
* Simple [PromiseWithCancel] factory
*/
export class PromiseWithCancel {
private _promise: Promise<any>;
private _status: Status = Status.Idle;
/**
* @param {Promise} promise Promise you want to cancel / track
*/
constructor(promise: Promise<any>) {
this._promise = promise;
}
/**
* Cancel the promise in any state
*/
public cancel = (): void => {
this._status = Status.Canceled;
};
/**
* @returns status based on [Status]
*/
public status = (): Status => {
return this._status;
};
/**
* @returns promise passed in [constructor]
* This sets the state to Status.Awaiting
*/
public promise = (): Promise<any> => {
if (this._status === Status.Canceled) {
throw Error('Getting a canceled promise is not allowed');
} else if (this._status !== Status.Idle) {
return this._promise;
}
return new Promise((resolve, reject) => {
this._status = Status.Awaiting;
return this._promise
.then(response => {
if (this._status !== Status.Canceled) {
this._status = Status.Resolved;
return resolve(response);
}
})
.catch(error => {
if (this._status !== Status.Canceled) {
this._status = Status.Failed;
return reject(error);
}
});
});
};
}

View file

@ -1,268 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/**
* Helper string to add as a tag in every logging call
*/
export const LOGGING_TAG = 'monitoring';
/**
* Helper string to add as a tag in every logging call related to Kibana monitoring
*/
export const KIBANA_MONITORING_LOGGING_TAG = 'kibana-monitoring';
/**
* The Monitoring API version is the expected API format that we export and expect to import.
* @type {string}
*/
export const MONITORING_SYSTEM_API_VERSION = '7';
/**
* The type name used within the Monitoring index to publish Kibana ops stats.
* @type {string}
*/
export const KIBANA_STATS_TYPE_MONITORING = 'kibana_stats'; // similar to KIBANA_STATS_TYPE but rolled up into 10s stats from 5s intervals through ops_buffer
/**
* The type name used within the Monitoring index to publish Kibana stats.
* @type {string}
*/
export const KIBANA_SETTINGS_TYPE = 'kibana_settings';
/**
* The type name used within the Monitoring index to publish Kibana usage stats.
* NOTE: this string shows as-is in the stats API as a field name for the kibana usage stats
* @type {string}
*/
export const KIBANA_USAGE_TYPE = 'kibana';
/*
* Key for the localStorage service
*/
export const STORAGE_KEY = 'xpack.monitoring.data';
/**
* Units for derivative metric values
*/
export const NORMALIZED_DERIVATIVE_UNIT = '1s';
/*
* Values for column sorting in table options
* @type {number} 1 or -1
*/
export const EUI_SORT_ASCENDING = 'asc';
export const EUI_SORT_DESCENDING = 'desc';
export const SORT_ASCENDING = 1;
export const SORT_DESCENDING = -1;
/*
* Chart colors
* @type {string}
*/
export const CHART_LINE_COLOR = '#d2d2d2';
export const CHART_TEXT_COLOR = '#9c9c9c';
/*
* Number of cluster alerts to show on overview page
* @type {number}
*/
export const CLUSTER_ALERTS_SEARCH_SIZE = 3;
/*
* Format for moment-duration-format timestamp-to-duration template if the time diffs are gte 1 month
* @type {string}
*/
export const FORMAT_DURATION_TEMPLATE_LONG = 'M [months] d [days]';
/*
* Format for moment-duration-format timestamp-to-duration template if the time diffs are lt 1 month but gt 1 minute
* @type {string}
*/
export const FORMAT_DURATION_TEMPLATE_SHORT = ' d [days] h [hrs] m [min]';
/*
* Format for moment-duration-format timestamp-to-duration template if the time diffs are lt 1 minute
* @type {string}
*/
export const FORMAT_DURATION_TEMPLATE_TINY = ' s [seconds]';
/*
* Simple unique values for Timestamp to duration flags. These are used for
* determining if calculation should be formatted as "time until" (now to
* timestamp) or "time since" (timestamp to now)
*/
export const CALCULATE_DURATION_SINCE = 'since';
export const CALCULATE_DURATION_UNTIL = 'until';
/**
* In order to show ML Jobs tab in the Elasticsearch section / tab navigation, license must be supported
*/
export const ML_SUPPORTED_LICENSES = ['trial', 'platinum', 'enterprise'];
/**
* Metadata service URLs for the different cloud services that have constant URLs (e.g., unlike GCP, which is a constant prefix).
*
* @type {Object}
*/
export const CLOUD_METADATA_SERVICES = {
// We explicitly call out the version, 2016-09-02, rather than 'latest' to avoid unexpected changes
AWS_URL: 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document',
// 2017-04-02 is the first GA release of this API
AZURE_URL: 'http://169.254.169.254/metadata/instance?api-version=2017-04-02',
// GCP documentation shows both 'metadata.google.internal' (mostly) and '169.254.169.254' (sometimes)
// To bypass potential DNS changes, the IP was used because it's shared with other cloud services
GCP_URL_PREFIX: 'http://169.254.169.254/computeMetadata/v1/instance',
};
/**
* Constants used by Logstash monitoring code
*/
export const LOGSTASH = {
MAJOR_VER_REQD_FOR_PIPELINES: 6,
/*
* Names ES keys on for different Logstash pipeline queues.
* @type {string}
*/
QUEUE_TYPES: {
MEMORY: 'memory',
PERSISTED: 'persisted',
},
};
export const DEBOUNCE_SLOW_MS = 17; // roughly how long it takes to render a frame at 60fps
export const DEBOUNCE_FAST_MS = 10; // roughly how long it takes to render a frame at 100fps
/**
* Configuration key for setting the email address used for cluster alert notifications.
*/
export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notifications.email_address';
export const STANDALONE_CLUSTER_CLUSTER_UUID = '__standalone_cluster__';
export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*';
export const INDEX_PATTERN_KIBANA = '.monitoring-kibana-6-*,.monitoring-kibana-7-*';
export const INDEX_PATTERN_LOGSTASH = '.monitoring-logstash-6-*,.monitoring-logstash-7-*';
export const INDEX_PATTERN_BEATS = '.monitoring-beats-6-*,.monitoring-beats-7-*';
export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7';
export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*,.monitoring-es-7-*';
// 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_SYSTEM_ID = 'elasticsearch';
/**
* The id of the infra source owned by the monitoring plugin.
*/
export const INFRA_SOURCE_ID = 'internal-stack-monitoring';
/*
* These constants represent code paths within `getClustersFromRequest`
* that an api call wants to invoke. This is meant as an optimization to
* avoid unnecessary ES queries (looking at you logstash) when the data
* is not used. In the long term, it'd be nice to have separate api calls
* instead of this path logic.
*/
export const CODE_PATH_ALL = 'all';
export const CODE_PATH_ALERTS = 'alerts';
export const CODE_PATH_KIBANA = 'kibana';
export const CODE_PATH_ELASTICSEARCH = 'elasticsearch';
export const CODE_PATH_ML = 'ml';
export const CODE_PATH_BEATS = 'beats';
export const CODE_PATH_LOGSTASH = 'logstash';
export const CODE_PATH_APM = 'apm';
export const CODE_PATH_LICENSE = 'license';
export const CODE_PATH_LOGS = 'logs';
/**
* The header sent by telemetry service when hitting Elasticsearch to identify query source
* @type {string}
*/
export const TELEMETRY_QUERY_SOURCE = 'TELEMETRY';
/**
* The name of the Kibana System ID used to publish and look up Kibana stats through the Monitoring system.
* @type {string}
*/
export const KIBANA_SYSTEM_ID = 'kibana';
/**
* The name of the Beats System ID used to publish and look up Beats stats through the Monitoring system.
* @type {string}
*/
export const BEATS_SYSTEM_ID = 'beats';
/**
* The name of the Apm System ID used to publish and look up Apm stats through the Monitoring system.
* @type {string}
*/
export const APM_SYSTEM_ID = 'apm';
/**
* The name of the Kibana System ID used to look up Logstash stats through the Monitoring system.
* @type {string}
*/
export const LOGSTASH_SYSTEM_ID = 'logstash';
/**
* The name of the Kibana System ID used to look up Reporting stats through the Monitoring system.
* @type {string}
*/
export const REPORTING_SYSTEM_ID = 'reporting';
/**
* The amount of time, in milliseconds, to wait between collecting kibana stats from es.
*
* Currently 24 hours kept in sync with reporting interval.
* @type {Number}
*/
export const TELEMETRY_COLLECTION_INTERVAL = 86400000;
/**
* We want to slowly rollout the migration from watcher-based cluster alerts to
* kibana alerts and we only want to enable the kibana alerts once all
* watcher-based cluster alerts have been migrated so this flag will serve
* as the only way to see the new UI and actually run Kibana alerts. It will
* be false until all alerts have been migrated, then it will be removed
*/
export const KIBANA_ALERTING_ENABLED = false;
/**
* The prefix for all alert types used by monitoring
*/
export const ALERT_TYPE_PREFIX = 'monitoring_';
/**
* This is the alert type id for the license expiration alert
*/
export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`;
/**
* This is the alert type id for the cluster state alert
*/
export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`;
/**
* A listing of all alert types
*/
export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE];
/**
* Matches the id for the built-in in email action type
* See x-pack/plugins/actions/server/builtin_action_types/email.ts
*/
export const ALERT_ACTION_TYPE_EMAIL = '.email';
/**
* The number of alerts that have been migrated
*/
export const NUMBER_OF_MIGRATED_ALERTS = 2;
/**
* The advanced settings config name for the email address
*/
export const MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS = 'monitoring:alertingEmailAddress';
export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo'];

View file

@ -1,54 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment';
import 'moment-duration-format';
import {
FORMAT_DURATION_TEMPLATE_TINY,
FORMAT_DURATION_TEMPLATE_SHORT,
FORMAT_DURATION_TEMPLATE_LONG,
CALCULATE_DURATION_SINCE,
CALCULATE_DURATION_UNTIL,
} from './constants';
/*
* Formats a timestamp string
* @param timestamp: ISO time string
* @param calculationFlag: control "since" or "until" logic
* @param initialTime {Object} moment object (not required)
* @return string
*/
export function formatTimestampToDuration(timestamp, calculationFlag, initialTime) {
initialTime = initialTime || moment();
let timeDuration;
if (calculationFlag === CALCULATE_DURATION_SINCE) {
timeDuration = moment.duration(initialTime - moment(timestamp)); // since: now - timestamp
} else if (calculationFlag === CALCULATE_DURATION_UNTIL) {
timeDuration = moment.duration(moment(timestamp) - initialTime); // until: timestamp - now
} else {
throw new Error(
'[formatTimestampToDuration] requires a [calculationFlag] parameter to specify format as "since" or "until" the given time.'
);
}
// See https://github.com/elastic/x-pack-kibana/issues/3554
let duration;
if (Math.abs(initialTime.diff(timestamp, 'months')) >= 1) {
// time diff is greater than 1 month, show months / days
duration = moment.duration(timeDuration).format(FORMAT_DURATION_TEMPLATE_LONG);
} else if (Math.abs(initialTime.diff(timestamp, 'minutes')) >= 1) {
// time diff is less than 1 month but greater than a minute, show days / hours / minutes
duration = moment.duration(timeDuration).format(FORMAT_DURATION_TEMPLATE_SHORT);
} else {
// time diff is less than a minute, show seconds
duration = moment.duration(timeDuration).format(FORMAT_DURATION_TEMPLATE_TINY);
}
return duration
.replace(/ 0 mins$/, '')
.replace(/ 0 hrs$/, '')
.replace(/ 0 days$/, ''); // See https://github.com/jsmreese/moment-duration-format/issues/64
}

View file

@ -1,35 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment-timezone';
export const LARGE_FLOAT = '0,0.[00]';
export const SMALL_FLOAT = '0.[00]';
export const LARGE_BYTES = '0,0.0 b';
export const SMALL_BYTES = '0.0 b';
export const LARGE_ABBREVIATED = '0,0.[0]a';
/**
* Format the {@code date} in the user's expected date/time format using their <em>dateFormat:tz</em> defined time zone.
* @param date Either a numeric Unix timestamp or a {@code Date} object
* @returns The date formatted using 'LL LTS'
*/
export function formatDateTimeLocal(date, timezone) {
if (timezone === 'Browser') {
timezone = moment.tz.guess() || 'utc';
}
return moment.tz(date, timezone).format('LL LTS');
}
/**
* Shorten a Logstash Pipeline's hash for display purposes
* @param {string} hash The complete hash
* @return {string} The shortened hash
*/
export function shortenPipelineHash(hash) {
return hash.substr(0, 6);
}

View file

@ -9,7 +9,7 @@
* @param {Object} Joi - HapiJS Joi module that allows for schema validation
* @return {Object} config schema
*/
export const config = Joi => {
export const config = (Joi: any) => {
const DEFAULT_REQUEST_HEADERS = ['authorization'];
return Joi.object({

View file

@ -1,57 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
import { resolve } from 'path';
import { config } from './config';
import { getUiExports } from './ui_exports';
import { KIBANA_ALERTING_ENABLED } from './common/constants';
/**
* Invokes plugin modules to instantiate the Monitoring plugin for Kibana
* @param kibana {Object} Kibana plugin instance
* @return {Object} Monitoring UI Kibana plugin object
*/
const deps = ['kibana', 'elasticsearch', 'xpack_main'];
if (KIBANA_ALERTING_ENABLED) {
deps.push(...['alerting', 'actions']);
}
export const monitoring = kibana => {
return new kibana.Plugin({
require: deps,
id: 'monitoring',
configPrefix: 'monitoring',
publicDir: resolve(__dirname, 'public'),
init(server) {
const serverConfig = server.config();
const npMonitoring = server.newPlatform.setup.plugins.monitoring;
if (npMonitoring) {
const kbnServerStatus = this.kbnServer.status;
npMonitoring.registerLegacyAPI({
getServerStatus: () => {
const status = kbnServerStatus.toJSON();
return get(status, 'overall.state');
},
});
}
server.injectUiAppVars('monitoring', () => {
return {
maxBucketSize: serverConfig.get('monitoring.ui.max_bucket_size'),
minIntervalSeconds: serverConfig.get('monitoring.ui.min_interval_seconds'),
kbnIndex: serverConfig.get('kibana.index'),
showLicenseExpiration: serverConfig.get('monitoring.ui.show_license_expiration'),
showCgroupMetricsElasticsearch: serverConfig.get(
'monitoring.ui.container.elasticsearch.enabled'
),
showCgroupMetricsLogstash: serverConfig.get('monitoring.ui.container.logstash.enabled'), // Note, not currently used, but see https://github.com/elastic/x-pack-kibana/issues/1559 part 2
};
});
},
config,
uiExports: getUiExports(),
});
};

View file

@ -0,0 +1,41 @@
/*
* 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 Hapi from 'hapi';
import { config } from './config';
import { KIBANA_ALERTING_ENABLED } from '../../../plugins/monitoring/common/constants';
/**
* Invokes plugin modules to instantiate the Monitoring plugin for Kibana
* @param kibana {Object} Kibana plugin instance
* @return {Object} Monitoring UI Kibana plugin object
*/
const deps = ['kibana', 'elasticsearch', 'xpack_main'];
if (KIBANA_ALERTING_ENABLED) {
deps.push(...['alerting', 'actions']);
}
export const monitoring = (kibana: any) => {
return new kibana.Plugin({
require: deps,
id: 'monitoring',
configPrefix: 'monitoring',
init(server: Hapi.Server) {
const npMonitoring = server.newPlatform.setup.plugins.monitoring as object & {
registerLegacyAPI: (api: unknown) => void;
};
if (npMonitoring) {
const kbnServerStatus = this.kbnServer.status;
npMonitoring.registerLegacyAPI({
getServerStatus: () => {
const status = kbnServerStatus.toJSON();
return status?.overall?.state;
},
});
}
},
config,
});
};

View file

@ -1,76 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import expect from '@kbn/expect';
import { shallow } from 'enzyme';
import { ChartTarget } from './chart_target';
const props = {
seriesToShow: ['Max Heap', 'Max Heap Used'],
series: [
{
color: '#3ebeb0',
label: 'Max Heap',
id: 'Max Heap',
data: [
[1562958960000, 1037959168],
[1562958990000, 1037959168],
[1562959020000, 1037959168],
],
},
{
color: '#3b73ac',
label: 'Max Heap Used',
id: 'Max Heap Used',
data: [
[1562958960000, 639905768],
[1562958990000, 622312416],
[1562959020000, 555967504],
],
},
],
timeRange: {
min: 1562958939851,
max: 1562962539851,
},
hasLegend: true,
onBrush: () => void 0,
tickFormatter: () => void 0,
updateLegend: () => void 0,
};
jest.mock('../../np_imports/ui/chrome', () => {
return {
getBasePath: () => '',
};
});
// TODO: Skipping for now, seems flaky in New Platform (needs more investigation)
describe.skip('Test legends to toggle series: ', () => {
const ids = props.series.map(item => item.id);
describe('props.series: ', () => {
it('should toggle based on seriesToShow array', () => {
const component = shallow(<ChartTarget {...props} />);
const componentClass = component.instance();
const seriesA = componentClass.filterData(props.series, [ids[0]]);
expect(seriesA.length).to.be(1);
expect(seriesA[0].id).to.be(ids[0]);
const seriesB = componentClass.filterData(props.series, [ids[1]]);
expect(seriesB.length).to.be(1);
expect(seriesB[0].id).to.be(ids[1]);
const seriesAB = componentClass.filterData(props.series, ids);
expect(seriesAB.length).to.be(2);
expect(seriesAB[0].id).to.be(ids[0]);
expect(seriesAB[1].id).to.be(ids[1]);
});
});
});

View file

@ -1,91 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import {
EuiPage,
EuiPageBody,
EuiSpacer,
EuiCodeBlock,
EuiPanel,
EuiText,
EuiLink,
EuiFlexGroup,
EuiFlexItem,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { LicenseStatus, AddLicense } from 'plugins/xpack_main/components';
import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'plugins/monitoring/np_imports/ui/chrome';
const licenseManagement = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management`;
const LicenseUpdateInfoForPrimary = ({ isPrimaryCluster, uploadLicensePath }) => {
if (!isPrimaryCluster) {
return null;
}
// viewed license is for the cluster directly connected to Kibana
return <AddLicense uploadPath={uploadLicensePath} />;
};
const LicenseUpdateInfoForRemote = ({ isPrimaryCluster }) => {
if (isPrimaryCluster) {
return null;
}
// viewed license is for a remote monitored cluster not directly connected to Kibana
return (
<EuiPanel>
<p>
<FormattedMessage
id="xpack.monitoring.license.howToUpdateLicenseDescription"
defaultMessage="To update the license for this cluster, provide the license file through
the Elasticsearch {apiText}:"
values={{
apiText: 'API',
}}
/>
</p>
<EuiSpacer />
<EuiCodeBlock>
{`curl -XPUT -u <user> 'https://<host>:<port>/_license' -H 'Content-Type: application/json' -d @license.json`}
</EuiCodeBlock>
</EuiPanel>
);
};
export function License(props) {
const { status, type, isExpired, expiryDate } = props;
return (
<EuiPage>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage id="xpack.monitoring.license.heading" defaultMessage="License" />
</h1>
</EuiScreenReaderOnly>
<EuiPageBody>
<LicenseStatus isExpired={isExpired} status={status} type={type} expiryDate={expiryDate} />
<EuiSpacer />
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<LicenseUpdateInfoForPrimary {...props} />
<LicenseUpdateInfoForRemote {...props} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<EuiText size="s" textAlign="center">
<p>
For more license options please visit&nbsp;
<EuiLink href={licenseManagement}>License Management</EuiLink>.
</p>
</EuiText>
</EuiPageBody>
</EuiPage>
);
}

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './main';
import './elasticsearch/ml_job_listing';
import './beats/overview';
import './beats/beat';

View file

@ -1,30 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { capitalize } from 'lodash';
import { uiModules } from 'plugins/monitoring/np_imports/ui/modules';
import { formatNumber, formatMetric } from 'plugins/monitoring/lib/format_number';
import { extractIp } from 'plugins/monitoring/lib/extract_ip';
const uiModule = uiModules.get('monitoring/filters', []);
uiModule.filter('capitalize', function() {
return function(input) {
return capitalize(input.toLowerCase());
};
});
uiModule.filter('formatNumber', function() {
return formatNumber;
});
uiModule.filter('formatMetric', function() {
return formatMetric;
});
uiModule.filter('extractIp', function() {
return extractIp;
});

View file

@ -1,9 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
uiModules.get('kibana').constant('monitoringUiEnabled', true);

View file

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import { npStart } from 'ui/new_platform';
uiModules.get('monitoring/hacks').run(monitoringUiEnabled => {
if (monitoringUiEnabled) {
return;
}
npStart.core.chrome.navLinks.update('monitoring', { hidden: true });
});

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="20px" height="20px" viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M1.9,12.4h2.8l1.6-3c0.1-0.2,0.3-0.3,0.5-0.3c0.2,0,0.4,0.1,0.4,0.3l1.5,4.2l2.5-7.8c0.1-0.2,0.2-0.3,0.5-0.3
c0.3,0,0.4,0.1,0.5,0.3l2.6,6.5h3.2l0.2-0.2c2.3-2.3,2.2-6-0.1-8.3C16,1.6,12.3,1.5,10,3.7c-2.3-2.3-6-2.3-8.3,0
c-2.3,2.3-2.3,6.1,0,8.5L1.9,12.4z"/>
<path class="st0" d="M14.5,13.4c-0.2,0-0.4-0.1-0.5-0.3l-2.2-5.6l-2.6,7.9c-0.1,0.2-0.3,0.3-0.5,0.3c0,0,0,0,0,0
c-0.2,0-0.4-0.1-0.5-0.3l-1.6-4.5l-1.2,2.3c-0.1,0.2-0.3,0.3-0.4,0.3H2.9l5.8,5.9c0,0,0,0,0,0C9,19.7,9.4,19.9,9.7,20c0,0,0,0,0,0
c0.1,0,0.1,0,0.2,0c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0c0.3,0,0.6-0.2,0.9-0.4l6-6.1H14.5z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import 'plugins/monitoring/filters';
import 'plugins/monitoring/services/clusters';
import 'plugins/monitoring/services/features';
import 'plugins/monitoring/services/executor';
import 'plugins/monitoring/services/license';
import 'plugins/monitoring/services/title';
import 'plugins/monitoring/services/breadcrumbs';
import 'plugins/monitoring/directives/all';
import 'plugins/monitoring/views/all';
import { npSetup, npStart } from '../public/np_imports/legacy_imports';
import { plugin } from './np_ready';
import { localApplicationService } from '../../../../../src/legacy/core_plugins/kibana/public/local_application_service';
const pluginInstance = plugin({} as any);
pluginInstance.setup(npSetup.core, npSetup.plugins);
pluginInstance.start(npStart.core, {
...npStart.plugins,
__LEGACY: {
localApplicationService,
},
});

View file

@ -1,157 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
ICompileProvider,
IHttpProvider,
IHttpService,
ILocationProvider,
IModule,
IRootScopeService,
} from 'angular';
import $ from 'jquery';
import _, { cloneDeep, forOwn, get, set } from 'lodash';
import * as Rx from 'rxjs';
import { CoreStart, LegacyCoreStart } from 'kibana/public';
const isSystemApiRequest = (request: any) =>
Boolean(request && request.headers && !!request.headers['kbn-system-api']);
export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => {
const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata();
forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => {
if (name !== undefined) {
// The legacy platform modifies some of these values, clone to an unfrozen object.
angularModule.value(name, cloneDeep(val));
}
});
angularModule
.value('kbnVersion', newPlatform.injectedMetadata.getKibanaVersion())
.value('buildNum', legacyMetadata.buildNum)
.value('buildSha', legacyMetadata.buildSha)
.value('serverName', legacyMetadata.serverName)
.value('esUrl', getEsUrl(newPlatform))
.value('uiCapabilities', newPlatform.application.capabilities)
.config(setupCompileProvider(newPlatform))
.config(setupLocationProvider())
.config($setupXsrfRequestInterceptor(newPlatform))
.run(capture$httpLoadingCount(newPlatform))
.run($setupUICapabilityRedirect(newPlatform));
};
const getEsUrl = (newPlatform: CoreStart) => {
const a = document.createElement('a');
a.href = newPlatform.http.basePath.prepend('/elasticsearch');
const protocolPort = /https/.test(a.protocol) ? 443 : 80;
const port = a.port || protocolPort;
return {
host: a.hostname,
port,
protocol: a.protocol,
pathname: a.pathname,
};
};
const setupCompileProvider = (newPlatform: LegacyCoreStart) => (
$compileProvider: ICompileProvider
) => {
if (!newPlatform.injectedMetadata.getLegacyMetadata().devMode) {
$compileProvider.debugInfoEnabled(false);
}
};
const setupLocationProvider = () => ($locationProvider: ILocationProvider) => {
$locationProvider.html5Mode({
enabled: false,
requireBase: false,
rewriteLinks: false,
});
$locationProvider.hashPrefix('');
};
const $setupXsrfRequestInterceptor = (newPlatform: LegacyCoreStart) => {
const version = newPlatform.injectedMetadata.getLegacyMetadata().version;
// Configure jQuery prefilter
$.ajaxPrefilter(({ kbnXsrfToken = true }: any, originalOptions, jqXHR) => {
if (kbnXsrfToken) {
jqXHR.setRequestHeader('kbn-version', version);
}
});
return ($httpProvider: IHttpProvider) => {
// Configure $httpProvider interceptor
$httpProvider.interceptors.push(() => {
return {
request(opts) {
const { kbnXsrfToken = true } = opts as any;
if (kbnXsrfToken) {
set(opts, ['headers', 'kbn-version'], version);
}
return opts;
},
};
});
};
};
/**
* Injected into angular module by ui/chrome angular integration
* and adds a root-level watcher that will capture the count of
* active $http requests on each digest loop and expose the count to
* the core.loadingCount api
* @param {Angular.Scope} $rootScope
* @param {HttpService} $http
* @return {undefined}
*/
const capture$httpLoadingCount = (newPlatform: CoreStart) => (
$rootScope: IRootScopeService,
$http: IHttpService
) => {
newPlatform.http.addLoadingCountSource(
new Rx.Observable(observer => {
const unwatch = $rootScope.$watch(() => {
const reqs = $http.pendingRequests || [];
observer.next(reqs.filter(req => !isSystemApiRequest(req)).length);
});
return unwatch;
})
);
};
/**
* integrates with angular to automatically redirect to home if required
* capability is not met
*/
const $setupUICapabilityRedirect = (newPlatform: CoreStart) => (
$rootScope: IRootScopeService,
$injector: any
) => {
const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana');
// this feature only works within kibana app for now after everything is
// switched to the application service, this can be changed to handle all
// apps.
if (!isKibanaAppRoute) {
return;
}
$rootScope.$on(
'$routeChangeStart',
(event, { $$route: route }: { $$route?: { requireUICapability: boolean } } = {}) => {
if (!route || !route.requireUICapability) {
return;
}
if (!get(newPlatform.application.capabilities, route.requireUICapability)) {
$injector.get('kbnUrl').change('/home');
event.preventDefault();
}
}
);
};

View file

@ -1,48 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import angular, { IModule } from 'angular';
import { AppMountContext, LegacyCoreStart } from 'kibana/public';
// @ts-ignore TODO: change to absolute path
import uiRoutes from 'plugins/monitoring/np_imports/ui/routes';
// @ts-ignore TODO: change to absolute path
import chrome from 'plugins/monitoring/np_imports/ui/chrome';
// @ts-ignore TODO: change to absolute path
import { uiModules } from 'plugins/monitoring/np_imports/ui/modules';
// @ts-ignore TODO: change to absolute path
import { registerTimefilterWithGlobalState } from 'plugins/monitoring/np_imports/ui/timefilter';
import { configureAppAngularModule } from './angular_config';
import { localAppModule, appModuleName } from './modules';
export class AngularApp {
private injector?: angular.auto.IInjectorService;
constructor({ core }: AppMountContext, { element }: { element: HTMLElement }) {
uiModules.addToModule();
const app: IModule = localAppModule(core);
app.config(($routeProvider: any) => {
$routeProvider.eagerInstantiationEnabled(false);
uiRoutes.addToProvider($routeProvider);
});
configureAppAngularModule(app, core as LegacyCoreStart);
registerTimefilterWithGlobalState(app);
const appElement = document.createElement('div');
appElement.setAttribute('style', 'height: 100%');
appElement.innerHTML = '<div ng-view style="height: 100%" id="monitoring-angular-app"></div>';
this.injector = angular.bootstrap(appElement, [appModuleName]);
chrome.setInjector(this.injector);
angular.element(element).append(appElement);
}
public destroy = () => {
if (this.injector) {
this.injector.get('$rootScope').$destroy();
}
};
}

View file

@ -1,148 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import angular, { IWindowService } from 'angular';
// required for `ngSanitize` angular module
import 'angular-sanitize';
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
import { AppMountContext } from 'kibana/public';
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
import {
createTopNavDirective,
createTopNavHelper,
} from '../../../../../../../src/plugins/kibana_legacy/public';
import {
GlobalStateProvider,
StateManagementConfigProvider,
AppStateProvider,
KbnUrlProvider,
npStart,
} from '../legacy_imports';
// @ts-ignore
import { PromiseServiceCreator } from './providers/promises';
// @ts-ignore
import { PrivateProvider } from './providers/private';
import { getSafeForExternalLink } from '../../lib/get_safe_for_external_link';
type IPrivate = <T>(provider: (...injectable: any[]) => T) => T;
export const appModuleName = 'monitoring';
const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react'];
export const localAppModule = (core: AppMountContext['core']) => {
createLocalI18nModule();
createLocalPrivateModule();
createLocalPromiseModule();
createLocalStorage();
createLocalConfigModule(core);
createLocalKbnUrlModule();
createLocalStateModule();
createLocalTopNavModule(npStart.plugins.navigation);
createHrefModule(core);
const appModule = angular.module(appModuleName, [
...thirdPartyAngularDependencies,
'monitoring/Config',
'monitoring/I18n',
'monitoring/Private',
'monitoring/TopNav',
'monitoring/State',
'monitoring/Storage',
'monitoring/href',
'monitoring/services',
'monitoring/filters',
'monitoring/directives',
]);
return appModule;
};
function createLocalStateModule() {
angular
.module('monitoring/State', [
'monitoring/Private',
'monitoring/Config',
'monitoring/KbnUrl',
'monitoring/Promise',
])
.factory('AppState', function(Private: IPrivate) {
return Private(AppStateProvider);
})
.service('globalState', function(Private: IPrivate) {
return Private(GlobalStateProvider);
});
}
function createLocalKbnUrlModule() {
angular
.module('monitoring/KbnUrl', ['monitoring/Private', 'ngRoute'])
.service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider));
}
function createLocalConfigModule(core: AppMountContext['core']) {
angular
.module('monitoring/Config', ['monitoring/Private'])
.provider('stateManagementConfig', StateManagementConfigProvider)
.provider('config', () => {
return {
$get: () => ({
get: core.uiSettings.get.bind(core.uiSettings),
}),
};
});
}
function createLocalPromiseModule() {
angular.module('monitoring/Promise', []).service('Promise', PromiseServiceCreator);
}
function createLocalStorage() {
angular
.module('monitoring/Storage', [])
.service('localStorage', ($window: IWindowService) => new Storage($window.localStorage))
.service('sessionStorage', ($window: IWindowService) => new Storage($window.sessionStorage))
.service('sessionTimeout', () => {});
}
function createLocalPrivateModule() {
angular.module('monitoring/Private', []).provider('Private', PrivateProvider);
}
function createLocalTopNavModule({ ui }: any) {
angular
.module('monitoring/TopNav', ['react'])
.directive('kbnTopNav', createTopNavDirective)
.directive('kbnTopNavHelper', createTopNavHelper(ui));
}
function createLocalI18nModule() {
angular
.module('monitoring/I18n', [])
.provider('i18n', I18nProvider)
.filter('i18n', i18nFilter)
.directive('i18nId', i18nDirective);
}
function createHrefModule(core: AppMountContext['core']) {
const name: string = 'kbnHref';
angular.module('monitoring/href', []).directive(name, () => {
return {
restrict: 'A',
link: {
pre: (_$scope, _$el, $attr) => {
$attr.$observe(name, val => {
if (val) {
const url = getSafeForExternalLink(val as string);
$attr.$set('href', core.http.basePath.prepend(url));
}
});
},
},
};
});
}

View file

@ -1,116 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
export function PromiseServiceCreator($q, $timeout) {
function Promise(fn) {
if (typeof this === 'undefined')
throw new Error('Promise constructor must be called with "new"');
const defer = $q.defer();
try {
fn(defer.resolve, defer.reject);
} catch (e) {
defer.reject(e);
}
return defer.promise;
}
Promise.all = Promise.props = $q.all;
Promise.resolve = function(val) {
const defer = $q.defer();
defer.resolve(val);
return defer.promise;
};
Promise.reject = function(reason) {
const defer = $q.defer();
defer.reject(reason);
return defer.promise;
};
Promise.cast = $q.when;
Promise.delay = function(ms) {
return $timeout(_.noop, ms);
};
Promise.method = function(fn) {
return function() {
const args = Array.prototype.slice.call(arguments);
return Promise.try(fn, args, this);
};
};
Promise.nodeify = function(promise, cb) {
promise.then(function(val) {
cb(void 0, val);
}, cb);
};
Promise.map = function(arr, fn) {
return Promise.all(
arr.map(function(i, el, list) {
return Promise.try(fn, [i, el, list]);
})
);
};
Promise.each = function(arr, fn) {
const queue = arr.slice(0);
let i = 0;
return (function next() {
if (!queue.length) return arr;
return Promise.try(fn, [arr.shift(), i++]).then(next);
})();
};
Promise.is = function(obj) {
// $q doesn't create instances of any constructor, promises are just objects with a then function
// https://github.com/angular/angular.js/blob/58f5da86645990ef984353418cd1ed83213b111e/src/ng/q.js#L335
return obj && typeof obj.then === 'function';
};
Promise.halt = _.once(function() {
const promise = new Promise(() => {});
promise.then = _.constant(promise);
promise.catch = _.constant(promise);
return promise;
});
Promise.try = function(fn, args, ctx) {
if (typeof fn !== 'function') {
return Promise.reject(new TypeError('fn must be a function'));
}
let value;
if (Array.isArray(args)) {
try {
value = fn.apply(ctx, args);
} catch (e) {
return Promise.reject(e);
}
} else {
try {
value = fn.call(ctx, args);
} catch (e) {
return Promise.reject(e);
}
}
return Promise.resolve(value);
};
Promise.fromNode = function(takesCbFn) {
return new Promise(function(resolve, reject) {
takesCbFn(function(err, ...results) {
if (err) reject(err);
else if (results.length > 1) resolve(results);
else resolve(results[0]);
});
});
};
Promise.race = function(iterable) {
return new Promise((resolve, reject) => {
for (const i of iterable) {
Promise.resolve(i).then(resolve, reject);
}
});
};
return Promise;
}

View file

@ -1,22 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/**
* Last remaining 'ui/*' imports that will eventually be shimmed with their np alternatives
*/
export { npSetup, npStart } from 'ui/new_platform';
// @ts-ignore
export { GlobalStateProvider } from 'ui/state_management/global_state';
// @ts-ignore
export { StateManagementConfigProvider } from 'ui/state_management/config_provider';
// @ts-ignore
export { AppStateProvider } from 'ui/state_management/app_state';
// @ts-ignore
export { EventsProvider } from 'ui/events';
// @ts-ignore
export { KbnUrlProvider } from 'ui/url';
export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router';

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { npStart } from '../legacy_imports';
export const capabilities = { get: () => npStart.core.application.capabilities };

View file

@ -1,33 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import angular from 'angular';
import { npStart, npSetup } from '../legacy_imports';
type OptionalInjector = void | angular.auto.IInjectorService;
class Chrome {
private injector?: OptionalInjector;
public setInjector = (injector: OptionalInjector): void => void (this.injector = injector);
public dangerouslyGetActiveInjector = (): OptionalInjector => this.injector;
public getBasePath = (): string => npStart.core.http.basePath.get();
public getInjected = (name?: string, defaultValue?: any): string | unknown => {
const { getInjectedVar, getInjectedVars } = npSetup.core.injectedMetadata;
return name ? getInjectedVar(name, defaultValue) : getInjectedVars();
};
public get breadcrumbs() {
const set = (...args: any[]) => npStart.core.chrome.setBreadcrumbs.apply(this, args as any);
return { set };
}
}
const chrome = new Chrome();
export default chrome; // eslint-disable-line import/no-default-export

View file

@ -1,55 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import angular from 'angular';
type PrivateProvider = (...args: any) => any;
interface Provider {
name: string;
provider: PrivateProvider;
}
class Modules {
private _services: Provider[] = [];
private _filters: Provider[] = [];
private _directives: Provider[] = [];
public get = (_name: string, _dep?: string[]) => {
return this;
};
public service = (...args: any) => {
this._services.push(args);
};
public filter = (...args: any) => {
this._filters.push(args);
};
public directive = (...args: any) => {
this._directives.push(args);
};
public addToModule = () => {
angular.module('monitoring/services', []);
angular.module('monitoring/filters', []);
angular.module('monitoring/directives', []);
this._services.forEach(args => {
angular.module('monitoring/services').service.apply(null, args as any);
});
this._filters.forEach(args => {
angular.module('monitoring/filters').filter.apply(null, args as any);
});
this._directives.forEach(args => {
angular.module('monitoring/directives').directive.apply(null, args as any);
});
};
}
export const uiModules = new Modules();

View file

@ -1,31 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { IModule, IRootScopeService } from 'angular';
import { npStart, registerTimefilterWithGlobalStateFactory } from '../legacy_imports';
const {
core: { uiSettings },
} = npStart;
export const { timefilter } = npStart.plugins.data.query.timefilter;
uiSettings.overrideLocalDefault(
'timepicker:refreshIntervalDefaults',
JSON.stringify({ value: 10000, pause: false })
);
uiSettings.overrideLocalDefault(
'timepicker:timeDefaults',
JSON.stringify({ from: 'now-1h', to: 'now' })
);
export const registerTimefilterWithGlobalState = (app: IModule) => {
app.run((globalState: any, $rootScope: IRootScopeService) => {
globalState.fetch();
globalState.$inheritedGlobalState = true;
globalState.save();
registerTimefilterWithGlobalStateFactory(timefilter, globalState, $rootScope);
});
};

View file

@ -1,28 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { App, CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public';
export class MonitoringPlugin implements Plugin {
constructor(ctx: PluginInitializerContext) {}
public setup(core: CoreSetup, plugins: any) {
const app: App = {
id: 'monitoring',
title: 'Monitoring',
mount: async (context, params) => {
const { AngularApp } = await import('../np_imports/angular');
const monitoringApp = new AngularApp(context, params);
return monitoringApp.destroy;
},
};
core.application.register(app);
}
public start(core: CoreStart, plugins: any) {}
public stop() {}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import chrome from 'ui/chrome';
import { npSetup } from 'ui/new_platform';
import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public';
const {
plugins: { home },
} = npSetup;
if (chrome.getInjected('monitoringUiEnabled')) {
home.featureCatalogue.register({
id: 'monitoring',
title: i18n.translate('xpack.monitoring.monitoringTitle', {
defaultMessage: 'Monitoring',
}),
description: i18n.translate('xpack.monitoring.monitoringDescription', {
defaultMessage: 'Track the real-time health and performance of your Elastic Stack.',
}),
icon: 'monitoringApp',
path: '/app/monitoring',
showOnHomePage: true,
category: FeatureCatalogueCategory.ADMIN,
});
}

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'plugins/monitoring/np_imports/ui/modules';
import { breadcrumbsProvider } from './breadcrumbs_provider';
const uiModule = uiModules.get('monitoring/breadcrumbs', []);
uiModule.service('breadcrumbs', breadcrumbsProvider);

View file

@ -1,10 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'plugins/monitoring/np_imports/ui/modules';
import { executorProvider } from './executor_provider';
const uiModule = uiModules.get('monitoring/executor', []);
uiModule.service('$executor', executorProvider);

View file

@ -1,153 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/*
* Kibana Instance
*/
import React from 'react';
import { get } from 'lodash';
import uiRoutes from 'plugins/monitoring/np_imports/ui/routes';
import { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler';
import { routeInitProvider } from 'plugins/monitoring/lib/route_init';
import template from './index.html';
import { timefilter } from 'plugins/monitoring/np_imports/ui/timefilter';
import {
EuiPage,
EuiPageBody,
EuiPageContent,
EuiSpacer,
EuiFlexGrid,
EuiFlexItem,
EuiPanel,
} from '@elastic/eui';
import { MonitoringTimeseriesContainer } from '../../../components/chart';
import { DetailStatus } from 'plugins/monitoring/components/kibana/detail_status';
import { I18nContext } from 'ui/i18n';
import { MonitoringViewBaseController } from '../../base_controller';
import { CODE_PATH_KIBANA } from '../../../../common/constants';
function getPageData($injector) {
const $http = $injector.get('$http');
const globalState = $injector.get('globalState');
const $route = $injector.get('$route');
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/kibana/${$route.current.params.uuid}`;
const timeBounds = timefilter.getBounds();
return $http
.post(url, {
ccs: globalState.ccs,
timeRange: {
min: timeBounds.min.toISOString(),
max: timeBounds.max.toISOString(),
},
})
.then(response => response.data)
.catch(err => {
const Private = $injector.get('Private');
const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
return ajaxErrorHandlers(err);
});
}
uiRoutes.when('/kibana/instances/:uuid', {
template,
resolve: {
clusters(Private) {
const routeInit = Private(routeInitProvider);
return routeInit({ codePaths: [CODE_PATH_KIBANA] });
},
pageData: getPageData,
},
controllerAs: 'monitoringKibanaInstanceApp',
controller: class extends MonitoringViewBaseController {
constructor($injector, $scope) {
super({
title: `Kibana - ${get($scope.pageData, 'kibanaSummary.name')}`,
defaultData: {},
getPageData,
reactNodeId: 'monitoringKibanaInstanceApp',
$scope,
$injector,
});
$scope.$watch(
() => this.data,
data => {
if (!data || !data.metrics) {
return;
}
this.setTitle(`Kibana - ${get(data, 'kibanaSummary.name')}`);
this.renderReact(
<I18nContext>
<EuiPage>
<EuiPageBody>
<EuiPanel>
<DetailStatus stats={data.kibanaSummary} />
</EuiPanel>
<EuiSpacer size="m" />
<EuiPageContent>
<EuiFlexGrid columns={2} gutterSize="s">
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={data.metrics.kibana_requests}
onBrush={this.onBrush}
zoomInfo={this.zoomInfo}
/>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={data.metrics.kibana_response_times}
onBrush={this.onBrush}
zoomInfo={this.zoomInfo}
/>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={data.metrics.kibana_memory}
onBrush={this.onBrush}
zoomInfo={this.zoomInfo}
/>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={data.metrics.kibana_average_concurrent_connections}
onBrush={this.onBrush}
zoomInfo={this.zoomInfo}
/>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={data.metrics.kibana_os_load}
onBrush={this.onBrush}
zoomInfo={this.zoomInfo}
/>
<EuiSpacer />
</EuiFlexItem>
<EuiFlexItem grow={true}>
<MonitoringTimeseriesContainer
series={data.metrics.kibana_process_delay}
onBrush={this.onBrush}
zoomInfo={this.zoomInfo}
/>
<EuiSpacer />
</EuiFlexItem>
</EuiFlexGrid>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nContext>
);
}
);
}
},
});

View file

@ -1,5 +0,0 @@
<monitoring-main name="loading">
<div data-test-subj="loadingContainer">
<div id="monitoringLoadingReactApp"></div>
</div>
</monitoring-main>

View file

@ -1,51 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { PageLoading } from 'plugins/monitoring/components';
import uiRoutes from 'plugins/monitoring/np_imports/ui/routes';
import { I18nContext } from 'ui/i18n';
import template from './index.html';
import { CODE_PATH_LICENSE } from '../../../common/constants';
const REACT_DOM_ID = 'monitoringLoadingReactApp';
uiRoutes.when('/loading', {
template,
controller: class {
constructor($injector, $scope) {
const monitoringClusters = $injector.get('monitoringClusters');
const kbnUrl = $injector.get('kbnUrl');
$scope.$on('$destroy', () => {
unmountComponentAtNode(document.getElementById(REACT_DOM_ID));
});
$scope.$$postDigest(() => {
this.renderReact();
});
monitoringClusters(undefined, undefined, [CODE_PATH_LICENSE]).then(clusters => {
if (clusters && clusters.length) {
kbnUrl.changePath('/home');
return;
}
kbnUrl.changePath('/no-data');
return;
});
}
renderReact() {
render(
<I18nContext>
<PageLoading />
</I18nContext>,
document.getElementById(REACT_DOM_ID)
);
}
},
});

View file

@ -1,65 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { resolve } from 'path';
import {
MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS,
KIBANA_ALERTING_ENABLED,
} from './common/constants';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils';
/**
* Configuration of dependency objects for the UI, which are needed for the
* Monitoring UI app and views and data for outside the monitoring
* app (injectDefaultVars and hacks)
* @return {Object} data per Kibana plugin uiExport schema
*/
export const getUiExports = () => {
const uiSettingDefaults = {};
if (KIBANA_ALERTING_ENABLED) {
uiSettingDefaults[MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS] = {
name: i18n.translate('xpack.monitoring.alertingEmailAddress.name', {
defaultMessage: 'Alerting email address',
}),
value: '',
description: i18n.translate('xpack.monitoring.alertingEmailAddress.description', {
defaultMessage: `The default email address to receive alerts from Stack Monitoring`,
}),
category: ['monitoring'],
};
}
return {
app: {
title: i18n.translate('xpack.monitoring.stackMonitoringTitle', {
defaultMessage: 'Stack Monitoring',
}),
order: 9002,
description: i18n.translate('xpack.monitoring.uiExportsDescription', {
defaultMessage: 'Monitoring for Elastic Stack',
}),
icon: 'plugins/monitoring/icons/monitoring.svg',
euiIconType: 'monitoringApp',
linkToLastSubUrl: false,
main: 'plugins/monitoring/legacy',
category: DEFAULT_APP_CATEGORIES.management,
},
injectDefaultVars(server) {
const config = server.config();
return {
monitoringUiEnabled: config.get('monitoring.ui.enabled'),
monitoringLegacyEmailAddress: config.get(
'monitoring.cluster_alerts.email_notifications.email_address'
),
};
},
uiSettingDefaults,
hacks: ['plugins/monitoring/hacks/toggle_app_link_in_nav'],
home: ['plugins/monitoring/register_feature'],
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
};
};

View file

@ -6,7 +6,7 @@
import { boomify } from 'boom';
import { get } from 'lodash';
import { KIBANA_SETTINGS_TYPE } from '../../../../../monitoring/common/constants';
import { KIBANA_SETTINGS_TYPE } from '../../../../../../../plugins/monitoring/common/constants';
const getClusterUuid = async callCluster => {
const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid' });

View file

@ -3,8 +3,8 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["monitoring"],
"requiredPlugins": ["licensing", "features"],
"optionalPlugins": ["alerting", "actions", "infra", "telemetryCollectionManager", "usageCollection"],
"requiredPlugins": ["licensing", "features", "data", "navigation", "kibanaLegacy"],
"optionalPlugins": ["alerting", "actions", "infra", "telemetryCollectionManager", "usageCollection", "home"],
"server": true,
"ui": false
"ui": true
}

View file

@ -0,0 +1,248 @@
/*
* 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 angular, { IWindowService } from 'angular';
import '../views/all';
// required for `ngSanitize` angular module
import 'angular-sanitize';
import 'angular-route';
import '../index.scss';
import { capitalize } from 'lodash';
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
import { AppMountContext } from 'kibana/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import {
createTopNavDirective,
createTopNavHelper,
} from '../../../../../src/plugins/kibana_legacy/public';
import { MonitoringPluginDependencies } from '../types';
import { GlobalState } from '../url_state';
import { getSafeForExternalLink } from '../lib/get_safe_for_external_link';
// @ts-ignore
import { formatNumber, formatMetric } from '../lib/format_number';
// @ts-ignore
import { extractIp } from '../lib/extract_ip';
// @ts-ignore
import { PrivateProvider } from './providers/private';
// @ts-ignore
import { KbnUrlProvider } from './providers/url';
// @ts-ignore
import { breadcrumbsProvider } from '../services/breadcrumbs';
// @ts-ignore
import { monitoringClustersProvider } from '../services/clusters';
// @ts-ignore
import { executorProvider } from '../services/executor';
// @ts-ignore
import { featuresProvider } from '../services/features';
// @ts-ignore
import { licenseProvider } from '../services/license';
// @ts-ignore
import { titleProvider } from '../services/title';
// @ts-ignore
import { monitoringBeatsBeatProvider } from '../directives/beats/beat';
// @ts-ignore
import { monitoringBeatsOverviewProvider } from '../directives/beats/overview';
// @ts-ignore
import { monitoringMlListingProvider } from '../directives/elasticsearch/ml_job_listing';
// @ts-ignore
import { monitoringMainProvider } from '../directives/main';
export const appModuleName = 'monitoring';
type IPrivate = <T>(provider: (...injectable: unknown[]) => T) => T;
const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react', 'ui.bootstrap'];
export const localAppModule = ({
core,
data: { query },
navigation,
externalConfig,
}: MonitoringPluginDependencies) => {
createLocalI18nModule();
createLocalPrivateModule();
createLocalStorage();
createLocalConfigModule(core);
createLocalKbnUrlModule();
createLocalStateModule(query);
createLocalTopNavModule(navigation);
createHrefModule(core);
createMonitoringAppServices();
createMonitoringAppDirectives();
createMonitoringAppConfigConstants(externalConfig);
createMonitoringAppFilters();
const appModule = angular.module(appModuleName, [
...thirdPartyAngularDependencies,
'monitoring/I18n',
'monitoring/Private',
'monitoring/KbnUrl',
'monitoring/Storage',
'monitoring/Config',
'monitoring/State',
'monitoring/TopNav',
'monitoring/href',
'monitoring/constants',
'monitoring/services',
'monitoring/filters',
'monitoring/directives',
]);
return appModule;
};
function createMonitoringAppConfigConstants(keys: MonitoringPluginDependencies['externalConfig']) {
let constantsModule = angular.module('monitoring/constants', []);
keys.map(([key, value]) => (constantsModule = constantsModule.constant(key as string, value)));
}
function createLocalStateModule(query: any) {
angular
.module('monitoring/State', ['monitoring/Private'])
.service('globalState', function(
Private: IPrivate,
$rootScope: ng.IRootScopeService,
$location: ng.ILocationService
) {
function GlobalStateProvider(this: any) {
const state = new GlobalState(query, $rootScope, $location, this);
const initialState: any = state.getState();
for (const key in initialState) {
if (!initialState.hasOwnProperty(key)) {
continue;
}
this[key] = initialState[key];
}
this.save = () => {
const newState = { ...this };
delete newState.save;
state.setState(newState);
};
}
return Private(GlobalStateProvider);
});
}
function createLocalKbnUrlModule() {
angular
.module('monitoring/KbnUrl', ['monitoring/Private', 'ngRoute'])
.service('kbnUrl', function(Private: IPrivate) {
return Private(KbnUrlProvider);
});
}
function createMonitoringAppServices() {
angular
.module('monitoring/services', ['monitoring/Private'])
.service('breadcrumbs', function(Private: IPrivate) {
return Private(breadcrumbsProvider);
})
.service('monitoringClusters', function(Private: IPrivate) {
return Private(monitoringClustersProvider);
})
.service('$executor', function(Private: IPrivate) {
return Private(executorProvider);
})
.service('features', function(Private: IPrivate) {
return Private(featuresProvider);
})
.service('license', function(Private: IPrivate) {
return Private(licenseProvider);
})
.service('title', function(Private: IPrivate) {
return Private(titleProvider);
});
}
function createMonitoringAppDirectives() {
angular
.module('monitoring/directives', [])
.directive('monitoringBeatsBeat', monitoringBeatsBeatProvider)
.directive('monitoringBeatsOverview', monitoringBeatsOverviewProvider)
.directive('monitoringMlListing', monitoringMlListingProvider)
.directive('monitoringMain', monitoringMainProvider);
}
function createMonitoringAppFilters() {
angular
.module('monitoring/filters', [])
.filter('capitalize', function() {
return function(input: string) {
return capitalize(input?.toLowerCase());
};
})
.filter('formatNumber', function() {
return formatNumber;
})
.filter('formatMetric', function() {
return formatMetric;
})
.filter('extractIp', function() {
return extractIp;
});
}
function createLocalConfigModule(core: MonitoringPluginDependencies['core']) {
angular.module('monitoring/Config', []).provider('config', function() {
return {
$get: () => ({
get: (key: string) => core.uiSettings?.get(key),
}),
};
});
}
function createLocalStorage() {
angular
.module('monitoring/Storage', [])
.service('localStorage', function($window: IWindowService) {
return new Storage($window.localStorage);
})
.service('sessionStorage', function($window: IWindowService) {
return new Storage($window.sessionStorage);
})
.service('sessionTimeout', function() {
return {};
});
}
function createLocalPrivateModule() {
angular.module('monitoring/Private', []).provider('Private', PrivateProvider);
}
function createLocalTopNavModule({ ui }: MonitoringPluginDependencies['navigation']) {
angular
.module('monitoring/TopNav', ['react'])
.directive('kbnTopNav', createTopNavDirective)
.directive('kbnTopNavHelper', createTopNavHelper(ui));
}
function createLocalI18nModule() {
angular
.module('monitoring/I18n', [])
.provider('i18n', I18nProvider)
.filter('i18n', i18nFilter)
.directive('i18nId', i18nDirective);
}
function createHrefModule(core: AppMountContext['core']) {
const name: string = 'kbnHref';
angular.module('monitoring/href', []).directive(name, function() {
return {
restrict: 'A',
link: {
pre: (_$scope, _$el, $attr) => {
$attr.$observe(name, val => {
if (val) {
const url = getSafeForExternalLink(val as string);
$attr.$set('href', core.http.basePath.prepend(url));
}
});
},
},
};
});
}

View file

@ -4,36 +4,35 @@
* you may not use this file except in compliance with the Elastic License.
*/
type RouteObject = [string, any];
type RouteObject = [string, { reloadOnSearch: boolean }];
interface Redirect {
redirectTo: string;
}
class Routes {
private _routes: RouteObject[] = [];
private _redirect?: Redirect;
private routes: RouteObject[] = [];
public redirect?: Redirect = { redirectTo: '/no-data' };
public when = (...args: RouteObject) => {
const [, routeOptions] = args;
routeOptions.reloadOnSearch = false;
this._routes.push(args);
this.routes.push(args);
return this;
};
public otherwise = (redirect: Redirect) => {
this._redirect = redirect;
this.redirect = redirect;
return this;
};
public addToProvider = ($routeProvider: any) => {
this._routes.forEach(args => {
this.routes.forEach(args => {
$routeProvider.when.apply(this, args);
});
if (this._redirect) {
$routeProvider.otherwise(this._redirect);
if (this.redirect) {
$routeProvider.otherwise(this.redirect);
}
};
}
const uiRoutes = new Routes();
export default uiRoutes; // eslint-disable-line import/no-default-export
export const uiRoutes = new Routes();

View file

@ -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 angular, { IModule } from 'angular';
import { uiRoutes } from './helpers/routes';
import { Legacy } from '../legacy_shims';
import { configureAppAngularModule } from '../../../../../src/plugins/kibana_legacy/public';
import { localAppModule, appModuleName } from './app_modules';
import { MonitoringPluginDependencies } from '../types';
const APP_WRAPPER_CLASS = 'monitoringApplicationWrapper';
export class AngularApp {
private injector?: angular.auto.IInjectorService;
constructor(deps: MonitoringPluginDependencies) {
const {
core,
element,
data,
navigation,
isCloud,
pluginInitializerContext,
externalConfig,
} = deps;
const app: IModule = localAppModule(deps);
app.run(($injector: angular.auto.IInjectorService) => {
this.injector = $injector;
Legacy.init(
{ core, element, data, navigation, isCloud, pluginInitializerContext, externalConfig },
this.injector
);
});
app.config(($routeProvider: unknown) => uiRoutes.addToProvider($routeProvider));
const np = { core, env: pluginInitializerContext.env };
configureAppAngularModule(app, np, true);
const appElement = document.createElement('div');
appElement.setAttribute('style', 'height: 100%');
appElement.innerHTML = '<div ng-view style="height: 100%" id="monitoring-angular-app"></div>';
if (!element.classList.contains(APP_WRAPPER_CLASS)) {
element.classList.add(APP_WRAPPER_CLASS);
}
angular.bootstrap(appElement, [appModuleName]);
angular.element(element).append(appElement);
}
public destroy = () => {
if (this.injector) {
this.injector.get('$rootScope').$destroy();
}
};
public applyScope = () => {
if (!this.injector) {
return;
}
const rootScope = this.injector.get('$rootScope');
rootScope.$applyAsync();
};
}

View file

@ -193,4 +193,6 @@ export function PrivateProvider() {
return Private;
},
];
return provider;
}

View file

@ -0,0 +1,217 @@
/*
* 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 _ from 'lodash';
export function KbnUrlProvider($injector, $location, $rootScope, $parse) {
/**
* the `kbnUrl` service was created to smooth over some of the
* inconsistent behavior that occurs when modifying the url via
* the `$location` api. In general it is recommended that you use
* the `kbnUrl` service any time you want to modify the url.
*
* "features" that `kbnUrl` does it's best to guarantee, which
* are not guaranteed with the `$location` service:
* - calling `kbnUrl.change()` with a url that resolves to the current
* route will force a full transition (rather than just updating the
* properties of the $route object)
*
* Additional features of `kbnUrl`
* - parameterized urls
* - easily include an app state with the url
*
* @type {KbnUrl}
*/
const self = this;
/**
* Navigate to a url
*
* @param {String} url - the new url, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the url template
* @return {undefined}
*/
self.change = function(url, paramObj, appState) {
self._changeLocation('url', url, paramObj, false, appState);
};
/**
* Same as #change except only changes the url's path,
* leaving the search string and such intact
*
* @param {String} path - the new path, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the path template
* @return {undefined}
*/
self.changePath = function(path, paramObj) {
self._changeLocation('path', path, paramObj);
};
/**
* Same as #change except that it removes the current url from history
*
* @param {String} url - the new url, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the url template
* @return {undefined}
*/
self.redirect = function(url, paramObj, appState) {
self._changeLocation('url', url, paramObj, true, appState);
};
/**
* Same as #redirect except only changes the url's path,
* leaving the search string and such intact
*
* @param {String} path - the new path, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the path template
* @return {undefined}
*/
self.redirectPath = function(path, paramObj) {
self._changeLocation('path', path, paramObj, true);
};
/**
* Evaluate a url template. templates can contain double-curly wrapped
* expressions that are evaluated in the context of the paramObj
*
* @param {String} template - the url template to evaluate
* @param {Object} [paramObj] - the variables to expose to the template
* @return {String} - the evaluated result
* @throws {Error} If any of the expressions can't be parsed.
*/
self.eval = function(template, paramObj) {
paramObj = paramObj || {};
return template.replace(/\{\{([^\}]+)\}\}/g, function(match, expr) {
// remove filters
const key = expr.split('|')[0].trim();
// verify that the expression can be evaluated
const p = $parse(key)(paramObj);
// if evaluation can't be made, throw
if (_.isUndefined(p)) {
throw new Error(`Replacement failed, unresolved expression: ${expr}`);
}
return encodeURIComponent($parse(expr)(paramObj));
});
};
/**
* convert an object's route to an href, compatible with
* window.location.href= and <a href="">
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {string} - the computed href
*/
self.getRouteHref = function(obj, route) {
return '#' + self.getRouteUrl(obj, route);
};
/**
* convert an object's route to a url, compatible with url.change() or $location.url()
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {string} - the computed url
*/
self.getRouteUrl = function(obj, route) {
const template = obj && obj.routes && obj.routes[route];
if (template) return self.eval(template, obj);
};
/**
* Similar to getRouteUrl, supports objects which list their routes,
* and redirects to the named route. See #redirect
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {undefined}
*/
self.redirectToRoute = function(obj, route) {
self.redirect(self.getRouteUrl(obj, route));
};
/**
* Similar to getRouteUrl, supports objects which list their routes,
* and changes the url to the named route. See #change
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {undefined}
*/
self.changeToRoute = function(obj, route) {
self.change(self.getRouteUrl(obj, route));
};
/**
* Removes the given parameter from the url. Does so without modifying the browser
* history.
* @param param
*/
self.removeParam = function(param) {
$location.search(param, null).replace();
};
/////
// private api
/////
let reloading;
self._changeLocation = function(type, url, paramObj, replace, appState) {
const prev = {
path: $location.path(),
search: $location.search(),
};
url = self.eval(url, paramObj);
$location[type](url);
if (replace) $location.replace();
if (appState) {
$location.search(appState.getQueryParamName(), appState.toQueryParam());
}
const next = {
path: $location.path(),
search: $location.search(),
};
if ($injector.has('$route')) {
const $route = $injector.get('$route');
if (self._shouldForceReload(next, prev, $route)) {
reloading = $rootScope.$on('$locationChangeSuccess', function() {
// call the "unlisten" function returned by $on
reloading();
reloading = false;
$route.reload();
});
}
}
};
// determine if the router will automatically reload the route
self._shouldForceReload = function(next, prev, $route) {
if (reloading) return false;
const route = $route.current && $route.current.$$route;
if (!route) return false;
// for the purposes of determining whether the router will
// automatically be reloading, '' and '/' are equal
const nextPath = next.path || '/';
const prevPath = prev.path || '/';
if (nextPath !== prevPath) return false;
const reloadOnSearch = route.reloadOnSearch;
const searchSame = _.isEqual(next.search, prev.search);
return (reloadOnSearch && searchSame) || !reloadOnSearch;
};
}

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import chrome from '../../np_imports/ui/chrome';
import { Legacy } from '../../legacy_shims';
import { capitalize, get } from 'lodash';
import { formatDateTimeLocal } from '../../../common/formatting';
import { formatTimestampToDuration } from '../../../common';
@ -16,8 +16,8 @@ import {
ALERT_TYPE_CLUSTER_STATE,
} from '../../../common/constants';
import { mapSeverity } from './map_severity';
import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { FormattedAlert } from '../../components/alerts/formatted_alert';
import { EuiMonitoringTable } from '../../components/table';
import { EuiHealth, EuiIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -162,7 +162,7 @@ export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange })
category: get(alert, 'metadata.link', get(alert, 'type', null)),
}));
const injector = chrome.dangerouslyGetActiveInjector();
const injector = Legacy.shims.getAngularInjector();
const timezone = injector.get('config').get('dateFormat:tz');
return (

View file

@ -7,11 +7,15 @@
import React from 'react';
import { mockUseEffects } from '../../../jest.helpers';
import { shallow, ShallowWrapper } from 'enzyme';
import { kfetch } from 'ui/kfetch';
import { Legacy } from '../../../legacy_shims';
import { AlertsConfiguration, AlertsConfigurationProps } from './configuration';
jest.mock('ui/kfetch', () => ({
kfetch: jest.fn(),
jest.mock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch: jest.fn(),
},
},
}));
const defaultProps: AlertsConfigurationProps = {
@ -61,7 +65,7 @@ describe('Configuration', () => {
beforeEach(async () => {
mockUseEffects(2);
(kfetch as jest.Mock).mockImplementation(() => {
(Legacy.shims.kfetch as jest.Mock).mockImplementation(() => {
return {
data: [
{
@ -101,7 +105,7 @@ describe('Configuration', () => {
describe('edit action', () => {
let component: ShallowWrapper;
beforeEach(async () => {
(kfetch as jest.Mock).mockImplementation(() => {
(Legacy.shims.kfetch as jest.Mock).mockImplementation(() => {
return {
data: [],
};
@ -124,7 +128,7 @@ describe('Configuration', () => {
describe('no email address', () => {
let component: ShallowWrapper;
beforeEach(async () => {
(kfetch as jest.Mock).mockImplementation(() => {
(Legacy.shims.kfetch as jest.Mock).mockImplementation(() => {
return {
data: [
{

View file

@ -5,10 +5,10 @@
*/
import React, { ReactNode } from 'react';
import { kfetch } from 'ui/kfetch';
import { EuiSteps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionResult } from '../../../../../../../plugins/actions/common';
import { Legacy } from '../../../legacy_shims';
import { ActionResult } from '../../../../../../plugins/actions/common';
import { ALERT_ACTION_TYPE_EMAIL } from '../../../../common/constants';
import { getMissingFieldErrors } from '../../../lib/form_validation';
import { Step1 } from './step1';
@ -59,7 +59,7 @@ export const AlertsConfiguration: React.FC<AlertsConfigurationProps> = (
}, [emailAddress]);
async function fetchEmailActions() {
const kibanaActions = await kfetch({
const kibanaActions = await Legacy.shims.kfetch({
method: 'GET',
pathname: `/api/action/_getAll`,
});
@ -84,7 +84,7 @@ export const AlertsConfiguration: React.FC<AlertsConfigurationProps> = (
setShowFormErrors(false);
try {
await kfetch({
await Legacy.shims.kfetch({
method: 'POST',
pathname: `/api/monitoring/v1/alerts`,
body: JSON.stringify({ selectedEmailActionId, emailAddress }),

View file

@ -49,9 +49,13 @@ describe('Step1', () => {
beforeEach(() => {
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch: () => {
return {};
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch: () => {
return {};
},
},
},
}));
setModules();
@ -97,8 +101,12 @@ describe('Step1', () => {
it('should send up the create to the server', async () => {
const kfetch = jest.fn().mockImplementation(() => {});
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch,
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch,
},
},
}));
setModules();
});
@ -152,8 +160,12 @@ describe('Step1', () => {
it('should send up the edit to the server', async () => {
const kfetch = jest.fn().mockImplementation(() => {});
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch,
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch,
},
},
}));
setModules();
});
@ -194,13 +206,17 @@ describe('Step1', () => {
describe('testing', () => {
it('should allow for testing', async () => {
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch: jest.fn().mockImplementation(arg => {
if (arg.pathname === '/api/action/1/_execute') {
return { status: 'ok' };
}
return {};
}),
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch: jest.fn().mockImplementation(arg => {
if (arg.pathname === '/api/action/1/_execute') {
return { status: 'ok' };
}
return {};
}),
},
},
}));
setModules();
});
@ -234,12 +250,16 @@ describe('Step1', () => {
it('should show a successful test', async () => {
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch: (arg: any) => {
if (arg.pathname === '/api/action/1/_execute') {
return { status: 'ok' };
}
return {};
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch: (arg: any) => {
if (arg.pathname === '/api/action/1/_execute') {
return { status: 'ok' };
}
return {};
},
},
},
}));
setModules();
@ -257,12 +277,16 @@ describe('Step1', () => {
it('should show a failed test error', async () => {
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch: (arg: any) => {
if (arg.pathname === '/api/action/1/_execute') {
return { message: 'Very detailed error message' };
}
return {};
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch: (arg: any) => {
if (arg.pathname === '/api/action/1/_execute') {
return { message: 'Very detailed error message' };
}
return {};
},
},
},
}));
setModules();
@ -304,8 +328,12 @@ describe('Step1', () => {
it('should send up the delete to the server', async () => {
const kfetch = jest.fn().mockImplementation(() => {});
jest.isolateModules(() => {
jest.doMock('ui/kfetch', () => ({
kfetch,
jest.doMock('../../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch,
},
},
}));
setModules();
});

View file

@ -16,10 +16,10 @@ import {
EuiToolTip,
EuiCallOut,
} from '@elastic/eui';
import { kfetch } from 'ui/kfetch';
import { omit, pick } from 'lodash';
import { i18n } from '@kbn/i18n';
import { ActionResult, BASE_ACTION_API_PATH } from '../../../../../../../plugins/actions/common';
import { Legacy } from '../../../legacy_shims';
import { ActionResult, BASE_ACTION_API_PATH } from '../../../../../../plugins/actions/common';
import { ManageEmailAction, EmailActionData } from '../manage_email_action';
import { ALERT_ACTION_TYPE_EMAIL } from '../../../../common/constants';
import { NEW_ACTION_ID } from './configuration';
@ -42,7 +42,7 @@ export const Step1: React.FC<GetStep1Props> = (props: GetStep1Props) => {
async function createEmailAction(data: EmailActionData) {
if (props.editAction) {
await kfetch({
await Legacy.shims.kfetch({
method: 'PUT',
pathname: `${BASE_ACTION_API_PATH}/${props.editAction.id}`,
body: JSON.stringify({
@ -53,7 +53,7 @@ export const Step1: React.FC<GetStep1Props> = (props: GetStep1Props) => {
});
props.setEditAction(null);
} else {
await kfetch({
await Legacy.shims.kfetch({
method: 'POST',
pathname: BASE_ACTION_API_PATH,
body: JSON.stringify({
@ -73,7 +73,7 @@ export const Step1: React.FC<GetStep1Props> = (props: GetStep1Props) => {
async function deleteEmailAction(id: string) {
setIsDeleting(true);
await kfetch({
await Legacy.shims.kfetch({
method: 'DELETE',
pathname: `${BASE_ACTION_API_PATH}/${id}`,
});
@ -99,7 +99,7 @@ export const Step1: React.FC<GetStep1Props> = (props: GetStep1Props) => {
to: [props.emailAddress],
};
const result = await kfetch({
const result = await Legacy.shims.kfetch({
method: 'POST',
pathname: `${BASE_ACTION_API_PATH}/${props.selectedEmailActionId}/_execute`,
body: JSON.stringify({ params }),

View file

@ -21,7 +21,7 @@ import {
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionResult } from '../../../../../../plugins/actions/common';
import { ActionResult } from '../../../../../plugins/actions/common';
import { getMissingFieldErrors, hasErrors, getRequiredFieldError } from '../../lib/form_validation';
import { ALERT_EMAIL_SERVICES } from '../../../common/constants';

View file

@ -6,7 +6,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { kfetch } from 'ui/kfetch';
import { Legacy } from '../../legacy_shims';
import { AlertsStatus, AlertsStatusProps } from './status';
import { ALERT_TYPES } from '../../../common/constants';
import { getSetupModeState } from '../../lib/setup_mode';
@ -18,8 +18,16 @@ jest.mock('../../lib/setup_mode', () => ({
toggleSetupMode: jest.fn(),
}));
jest.mock('ui/kfetch', () => ({
kfetch: jest.fn(),
jest.mock('../../legacy_shims', () => ({
Legacy: {
shims: {
kfetch: jest.fn(),
docLinks: {
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
DOC_LINK_VERSION: 'current',
},
},
},
}));
const defaultProps: AlertsStatusProps = {
@ -35,7 +43,7 @@ describe('Status', () => {
enabled: false,
});
(kfetch as jest.Mock).mockImplementation(({ pathname }) => {
(Legacy.shims.kfetch as jest.Mock).mockImplementation(({ pathname }) => {
if (pathname === '/internal/security/api_key/privileges') {
return { areApiKeysEnabled: true };
}
@ -62,7 +70,7 @@ describe('Status', () => {
});
it('should render a success message if all alerts have been migrated and in setup mode', async () => {
(kfetch as jest.Mock).mockReturnValue({
(Legacy.shims.kfetch as jest.Mock).mockReturnValue({
data: ALERT_TYPES.map(type => ({ alertTypeId: type })),
});

View file

@ -5,7 +5,6 @@
*/
import React, { Fragment } from 'react';
import { kfetch } from 'ui/kfetch';
import {
EuiSpacer,
EuiCallOut,
@ -18,8 +17,8 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
import { Alert, BASE_ALERT_API_PATH } from '../../../../../../plugins/alerting/common';
import { Legacy } from '../../legacy_shims';
import { Alert, BASE_ALERT_API_PATH } from '../../../../../plugins/alerting/common';
import { getSetupModeState, addSetupModeCallback, toggleSetupMode } from '../../lib/setup_mode';
import { NUMBER_OF_MIGRATED_ALERTS, ALERT_TYPE_PREFIX } from '../../../common/constants';
import { AlertsConfiguration } from './configuration';
@ -39,7 +38,10 @@ export const AlertsStatus: React.FC<AlertsStatusProps> = (props: AlertsStatusPro
React.useEffect(() => {
async function fetchAlertsStatus() {
const alerts = await kfetch({ method: 'GET', pathname: `${BASE_ALERT_API_PATH}/_find` });
const alerts = await Legacy.shims.kfetch({
method: 'GET',
pathname: `${BASE_ALERT_API_PATH}/_find`,
});
const monitoringAlerts = alerts.data.filter((alert: Alert) =>
alert.alertTypeId.startsWith(ALERT_TYPE_PREFIX)
);
@ -57,7 +59,9 @@ export const AlertsStatus: React.FC<AlertsStatusProps> = (props: AlertsStatusPro
}, [setupModeEnabled, showMigrationFlyout]);
async function fetchSecurityConfigured() {
const response = await kfetch({ pathname: '/internal/security/api_key/privileges' });
const response = await Legacy.shims.kfetch({
pathname: '/internal/security/api_key/privileges',
});
setIsSecurityConfigured(response.areApiKeysEnabled);
}
@ -72,7 +76,7 @@ export const AlertsStatus: React.FC<AlertsStatusProps> = (props: AlertsStatusPro
if (isSecurityConfigured) {
return null;
}
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = Legacy.shims.docLinks;
const link = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/security-settings.html#api-key-service-settings`;
return (
<Fragment>

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { StatusIcon } from 'plugins/monitoring/components/status_icon';
import { StatusIcon } from '../../components/status_icon';
import { i18n } from '@kbn/i18n';
export function ApmStatusIcon({ status, availability = true }) {

View file

@ -14,9 +14,9 @@ import {
EuiLink,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { Stats } from 'plugins/monitoring/components/beats';
import { formatMetric } from 'plugins/monitoring/lib/format_number';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { Stats } from '../../beats';
import { formatMetric } from '../../../lib/format_number';
import { EuiMonitoringTable } from '../../table';
import { i18n } from '@kbn/i18n';
import { BEATS_SYSTEM_ID } from '../../../../common/constants';
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';

View file

@ -10,16 +10,10 @@ import { shallow } from 'enzyme';
jest.mock('../stats', () => ({
Stats: () => 'Stats',
}));
jest.mock('../../', () => ({
jest.mock('../../chart', () => ({
MonitoringTimeseriesContainer: () => 'MonitoringTimeseriesContainer',
}));
jest.mock('../../../np_imports/ui/chrome', () => {
return {
getBasePath: () => '',
};
});
import { BeatsOverview } from './overview';
describe('Overview', () => {

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { formatMetric } from 'plugins/monitoring/lib/format_number';
import { formatMetric } from '../../lib/format_number';
import { SummaryStatus } from '../summary_status';
import { i18n } from '@kbn/i18n';

Some files were not shown because too many files have changed in this diff Show more