diff --git a/x-pack/plugins/cross_cluster_replication/public/app/app.js b/x-pack/plugins/cross_cluster_replication/public/app/app.js
index 0663b6fae8c35..985306ddd67b9 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/app.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/app.js
@@ -7,9 +7,25 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Route, Switch, Redirect } from 'react-router-dom';
+import chrome from 'ui/chrome';
+import { fatalError } from 'ui/notify';
+import { i18n } from '@kbn/i18n';
+import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiEmptyPrompt,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingSpinner,
+ EuiPageContent,
+ EuiTitle,
+} from '@elastic/eui';
-import routing from './services/routing';
import { BASE_PATH } from '../../common/constants';
+import { SectionUnauthorized, SectionError } from './components';
+import routing from './services/routing';
+import { isAvailable, isActive, getReason } from './services/license';
+import { loadPermissions } from './services/api';
import {
CrossClusterReplicationHome,
@@ -19,47 +35,181 @@ import {
FollowerIndexEdit,
} from './sections';
-export class App extends Component {
- static contextTypes = {
- router: PropTypes.shape({
- history: PropTypes.shape({
- push: PropTypes.func.isRequired,
- createHref: PropTypes.func.isRequired
+export const App = injectI18n(
+ class extends Component {
+ static contextTypes = {
+ router: PropTypes.shape({
+ history: PropTypes.shape({
+ push: PropTypes.func.isRequired,
+ createHref: PropTypes.func.isRequired
+ }).isRequired
}).isRequired
- }).isRequired
- }
+ }
- constructor(...args) {
- super(...args);
- this.registerRouter();
- }
+ constructor(...args) {
+ super(...args);
+ this.registerRouter();
- componentWillMount() {
- routing.userHasLeftApp = false;
- }
+ this.state = {
+ isFetchingPermissions: false,
+ fetchPermissionError: undefined,
+ hasPermission: false,
+ missingPermissions: [],
+ };
+ }
- componentWillUnmount() {
- routing.userHasLeftApp = true;
- }
+ componentWillMount() {
+ routing.userHasLeftApp = false;
+ }
- registerRouter() {
- const { router } = this.context;
- routing.reactRouter = router;
- }
+ componentDidMount() {
+ this.checkPermissions();
+ }
- render() {
- return (
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
+ componentWillUnmount() {
+ routing.userHasLeftApp = true;
+ }
+
+ async checkPermissions() {
+ this.setState({
+ isFetchingPermissions: true,
+ });
+ try {
+ const { hasPermission, missingPermissions } = await loadPermissions();
+
+ this.setState({
+ isFetchingPermissions: false,
+ hasPermission,
+ missingPermissions,
+ });
+ } catch (error) {
+ // Expect an error in the shape provided by Angular's $http service.
+ if (error && error.data) {
+ return this.setState({
+ isFetchingPermissions: false,
+ fetchPermissionError: error,
+ });
+ }
+
+ // This error isn't an HTTP error, so let the fatal error screen tell the user something
+ // unexpected happened.
+ fatalError(error, i18n.translate('xpack.crossClusterReplication.app.checkPermissionsFatalErrorTitle', {
+ defaultMessage: 'Cross Cluster Replication app',
+ }));
+ }
+ }
+
+ registerRouter() {
+ const { router } = this.context;
+ routing.reactRouter = router;
+ }
+
+ render() {
+ const {
+ isFetchingPermissions,
+ fetchPermissionError,
+ hasPermission,
+ missingPermissions,
+ } = this.state;
+
+ if (!isAvailable() || !isActive()) {
+ return (
+
+ )}
+ >
+ {getReason()}
+ {' '}
+
+
+
+
+ );
+ }
+
+ if (isFetchingPermissions) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ if (fetchPermissionError) {
+ return (
+
+ )}
+ error={fetchPermissionError}
+ />
+ );
+ }
+
+ if (!hasPermission) {
+ return (
+
+
+
+ }
+ body={
+
+
+
}
+ />
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ }
+);
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js
index 0017e30e3cb42..d4d60cf33bbf5 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js
@@ -190,7 +190,7 @@ export const FollowerIndexForm = injectI18n(
if (error && error.data) {
// All validation does is check for a name collision, so we can just let the user attempt
// to save the follower index and get an error back from the API.
- this.setState({
+ return this.setState({
isValidatingIndexName: false,
});
}
@@ -198,7 +198,7 @@ export const FollowerIndexForm = injectI18n(
// This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened.
fatalError(error, i18n.translate('xpack.crossClusterReplication.followerIndexForm.indexNameValidationFatalErrorTitle', {
- defaultMessage: 'Follower Index Forn index name validation',
+ defaultMessage: 'Follower Index Form index name validation',
}));
}
};
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js b/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js
index 958065e87424e..90737613479d4 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js
@@ -5,15 +5,10 @@
*/
import React, { Fragment } from 'react';
-import { injectI18n } from '@kbn/i18n/react';
import { EuiCallOut } from '@elastic/eui';
-export function SectionUnauthorizedUI({ intl, children }) {
- const title = intl.formatMessage({
- id: 'xpack.crossClusterReplication.remoteClusterList.noPermissionTitle',
- defaultMessage: 'Permission error',
- });
+export function SectionUnauthorized({ title, children }) {
return (
);
}
-
-export const SectionUnauthorized = injectI18n(SectionUnauthorizedUI);
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js
index 3181b5aebf2bf..89b691ecb1c61 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js
@@ -10,7 +10,6 @@ import { Route, Switch } from 'react-router-dom';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
-import { BASE_PATH } from '../../../../common/constants';
import {
EuiButton,
@@ -25,6 +24,7 @@ import {
EuiTitle,
} from '@elastic/eui';
+import { BASE_PATH } from '../../../../common/constants';
import { listBreadcrumb } from '../../services/breadcrumbs';
import routing from '../../services/routing';
import { AutoFollowPatternList } from './auto_follow_pattern_list';
@@ -36,8 +36,8 @@ export const CrossClusterReplicationHome = injectI18n(
static propTypes = {
autoFollowPatterns: PropTypes.array,
isAutoFollowApiAuthorized: PropTypes.bool,
- followerIndices: PropTypes.array,
isFollowerIndexApiAuthorized: PropTypes.bool,
+ followerIndices: PropTypes.array,
}
state = {
@@ -77,16 +77,11 @@ export const CrossClusterReplicationHome = injectI18n(
routing.navigate(`/${section}`);
}
- getHeaderSection() {
- if(this.state.activeSection === 'follower_indices') {
- const { isFollowerIndexApiAuthorized, followerIndices } = this.props;
-
-
- // We want to show the title when the user isn't authorized.
- if (isFollowerIndexApiAuthorized && !followerIndices.length) {
- return null;
- }
+ renderHeaderSection() {
+ const { followerIndices, autoFollowPatterns } = this.props;
+ // If we're rendering the empty prompt, we don't want to render the header.
+ if (this.state.activeSection === 'follower_indices' && followerIndices.length > 0) {
return (
@@ -102,32 +97,26 @@ export const CrossClusterReplicationHome = injectI18n(
- {isFollowerIndexApiAuthorized && (
-
-
-
- )}
+
+
+
);
- } else {
- const { isAutoFollowApiAuthorized, autoFollowPatterns } = this.props;
-
- // We want to show the title when the user isn't authorized.
- if (isAutoFollowApiAuthorized && !autoFollowPatterns.length) {
- return null;
- }
+ }
+ // If we're rendering the empty prompt, we don't want to render the header.
+ if (autoFollowPatterns.length > 0) {
return (
@@ -144,18 +133,16 @@ export const CrossClusterReplicationHome = injectI18n(
- {isAutoFollowApiAuthorized && (
-
-
-
- )}
+
+
+
@@ -165,18 +152,51 @@ export const CrossClusterReplicationHome = injectI18n(
}
}
- getUnauthorizedSection() {
- const { isAutoFollowApiAuthorized } = this.props;
- if (!isAutoFollowApiAuthorized) {
+ renderContent() {
+ const { isAutoFollowApiAuthorized, isFollowerIndexApiAuthorized } = this.props;
+
+ if (!isAutoFollowApiAuthorized || !isFollowerIndexApiAuthorized) {
return (
-
+
+ )}
+ >
);
}
+
+ return (
+
+
+ {this.tabs.map(tab => (
+ this.onSectionChange(tab.id)}
+ isSelected={tab.id === this.state.activeSection}
+ key={tab.id}
+ >
+ {tab.name}
+
+ ))}
+
+
+
+
+ {this.renderHeaderSection()}
+
+
+
+
+
+
+ );
}
render() {
@@ -194,27 +214,7 @@ export const CrossClusterReplicationHome = injectI18n(
-
- {this.tabs.map(tab => (
- this.onSectionChange(tab.id)}
- isSelected={tab.id === this.state.activeSection}
- key={tab.id}
- >
- {tab.name}
-
- ))}
-
-
-
-
- {this.getHeaderSection()}
- {this.getUnauthorizedSection()}
-
-
-
-
-
+ {this.renderContent()}
);
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js
index d122b1fbf17ff..d1e1386003ed2 100644
--- a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js
+++ b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js
@@ -95,7 +95,7 @@ export const updateFollowerIndex = (id, followerIndex) => (
/* Stats */
export const loadAutoFollowStats = () => (
- httpClient.get(`${apiPrefixIndexManagement}/stats/auto-follow`).then(extractData)
+ httpClient.get(`${apiPrefixIndexManagement}/stats/auto_follow`).then(extractData)
);
/* Indices */
@@ -112,3 +112,7 @@ export const loadIndices = () => {
return extractData(response);
});
};
+
+export const loadPermissions = () => (
+ httpClient.get(`${apiPrefix}/permissions`).then(extractData)
+);
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/license.js b/x-pack/plugins/cross_cluster_replication/public/app/services/license.js
new file mode 100644
index 0000000000000..c61a363472149
--- /dev/null
+++ b/x-pack/plugins/cross_cluster_replication/public/app/services/license.js
@@ -0,0 +1,15 @@
+/*
+ * 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 let isAvailable;
+export let isActive;
+export let getReason;
+
+export function setLicense(isAvailableCallback, isActiveCallback, getReasonCallback) {
+ isAvailable = isAvailableCallback;
+ isActive = isActiveCallback;
+ getReason = getReasonCallback;
+}
diff --git a/x-pack/plugins/cross_cluster_replication/public/index.js b/x-pack/plugins/cross_cluster_replication/public/index.js
index d9e036d2dfbfb..e92c44da34474 100644
--- a/x-pack/plugins/cross_cluster_replication/public/index.js
+++ b/x-pack/plugins/cross_cluster_replication/public/index.js
@@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import './register_ccr_section';
import './register_routes';
diff --git a/x-pack/plugins/cross_cluster_replication/public/register_ccr_section.js b/x-pack/plugins/cross_cluster_replication/public/register_ccr_section.js
deleted file mode 100644
index 4383e29e5548d..0000000000000
--- a/x-pack/plugins/cross_cluster_replication/public/register_ccr_section.js
+++ /dev/null
@@ -1,21 +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 { management } from 'ui/management';
-import { i18n } from '@kbn/i18n';
-import chrome from 'ui/chrome';
-import { BASE_PATH } from '../common/constants';
-
-if (chrome.getInjected('ccrUiEnabled')) {
- const esSection = management.getSection('elasticsearch');
-
- esSection.register('ccr', {
- visible: true,
- display: i18n.translate('xpack.crossClusterReplication.appTitle', { defaultMessage: 'Cross Cluster Replication' }),
- order: 3,
- url: `#${BASE_PATH}`
- });
-}
diff --git a/x-pack/plugins/cross_cluster_replication/public/register_routes.js b/x-pack/plugins/cross_cluster_replication/public/register_routes.js
index 6ab2229f0e6eb..d78e9fd12cb84 100644
--- a/x-pack/plugins/cross_cluster_replication/public/register_routes.js
+++ b/x-pack/plugins/cross_cluster_replication/public/register_routes.js
@@ -4,16 +4,29 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import routes from 'ui/routes';
import { unmountComponentAtNode } from 'react-dom';
import chrome from 'ui/chrome';
+import { management } from 'ui/management';
+import routes from 'ui/routes';
+import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
+import { i18n } from '@kbn/i18n';
import template from './main.html';
-import { BASE_PATH } from '../common/constants/base_path';
+import { BASE_PATH } from '../common/constants';
import { renderReact } from './app';
import { setHttpClient } from './app/services/api';
+import { setLicense } from './app/services/license';
if (chrome.getInjected('ccrUiEnabled')) {
+ const esSection = management.getSection('elasticsearch');
+
+ esSection.register('ccr', {
+ visible: true,
+ display: i18n.translate('xpack.crossClusterReplication.appTitle', { defaultMessage: 'Cross Cluster Replication' }),
+ order: 3,
+ url: `#${BASE_PATH}`
+ });
+
let elem;
const CCR_REACT_ROOT = 'ccrReactRoot';
@@ -21,16 +34,27 @@ if (chrome.getInjected('ccrUiEnabled')) {
const unmountReactApp = () => elem && unmountComponentAtNode(elem);
routes.when(`${BASE_PATH}/:section?/:subsection?/:view?/:id?`, {
- template: template,
+ template,
+ resolve: {
+ license(Private) {
+ const xpackInfo = Private(XPackInfoProvider);
+ return {
+ isAvailable: () => xpackInfo.get('features.crossClusterReplication.isAvailable'),
+ isActive: () => xpackInfo.get('features.crossClusterReplication.isActive'),
+ getReason: () => xpackInfo.get('features.crossClusterReplication.message'),
+ };
+ }
+ },
controllerAs: 'ccr',
controller: class CrossClusterReplicationController {
constructor($scope, $route, $http, $q) {
- /**
- * React-router's does not play well with the angular router. It will cause this controller
- * to re-execute without the $destroy handler being called. This means that the app will be mounted twice
- * creating a memory leak when leaving (only 1 app will be unmounted).
- * To avoid this, we unmount the React app each time we enter the controller.
- */
+ const { license: { isAvailable, isActive, getReason } } = $route.current.locals;
+ setLicense(isAvailable, isActive, getReason);
+
+ // React-router's does not play well with the angular router. It will cause this controller
+ // to re-execute without the $destroy handler being called. This means that the app will be mounted twice
+ // creating a memory leak when leaving (only 1 app will be unmounted).
+ // To avoid this, we unmount the React app each time we enter the controller.
unmountReactApp();
// NOTE: We depend upon Angular's $http service because it's decorated with interceptors,
diff --git a/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js
index 716e4954c69b1..4ee39cb08c8d8 100644
--- a/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js
+++ b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js
@@ -10,6 +10,16 @@ export const elasticsearchJsPlugin = (Client, config, components) => {
Client.prototype.ccr = components.clientAction.namespaceFactory();
const ccr = Client.prototype.ccr.prototype;
+ ccr.permissions = ca({
+ urls: [
+ {
+ fmt: '/_security/user/_has_privileges',
+ }
+ ],
+ needBody: true,
+ method: 'POST'
+ });
+
ccr.autoFollowPatterns = ca({
urls: [
{
diff --git a/x-pack/plugins/cross_cluster_replication/server/lib/check_license/check_license.js b/x-pack/plugins/cross_cluster_replication/server/lib/check_license/check_license.js
index 35e5e3783e628..fb99de8ab5d97 100644
--- a/x-pack/plugins/cross_cluster_replication/server/lib/check_license/check_license.js
+++ b/x-pack/plugins/cross_cluster_replication/server/lib/check_license/check_license.js
@@ -26,7 +26,7 @@ export function checkLicense(xpackLicenseInfo) {
};
}
- const VALID_LICENSE_MODES = ['trial', 'basic', 'standard', 'gold', 'platinum'];
+ const VALID_LICENSE_MODES = [ 'trial', 'platinum' ];
const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES);
const isLicenseActive = xpackLicenseInfo.license.isActive();
@@ -36,7 +36,7 @@ export function checkLicense(xpackLicenseInfo) {
if (!isLicenseModeValid) {
return {
isAvailable: false,
- showLinks: false,
+ isActive: false,
message: i18n.translate(
'xpack.crossClusterReplication.checkLicense.errorUnsupportedMessage',
{
@@ -50,9 +50,8 @@ export function checkLicense(xpackLicenseInfo) {
// License is valid but not active
if (!isLicenseActive) {
return {
- isAvailable: false,
- showLinks: true,
- enableLinks: false,
+ isAvailable: true,
+ isActive: false,
message: i18n.translate(
'xpack.crossClusterReplication.checkLicense.errorExpiredMessage',
{
@@ -66,7 +65,6 @@ export function checkLicense(xpackLicenseInfo) {
// License is valid and active
return {
isAvailable: true,
- showLinks: true,
- enableLinks: true,
+ isActive: true,
};
}
diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/ccr.js b/x-pack/plugins/cross_cluster_replication/server/routes/api/ccr.js
index 781f4d6ec6cd5..b17634585c9cf 100644
--- a/x-pack/plugins/cross_cluster_replication/server/routes/api/ccr.js
+++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/ccr.js
@@ -50,7 +50,7 @@ export const registerCcrRoutes = (server) => {
* Returns Auto-follow stats
*/
server.route({
- path: `${API_BASE_PATH}/stats/auto-follow`,
+ path: `${API_BASE_PATH}/stats/auto_follow`,
method: 'GET',
config: {
pre: [ licensePreRouting ]
@@ -60,4 +60,46 @@ export const registerCcrRoutes = (server) => {
return autoFollow;
},
});
+
+ /**
+ * Returns whether the user has CCR permissions
+ */
+ server.route({
+ path: `${API_BASE_PATH}/permissions`,
+ method: 'GET',
+ config: {
+ pre: [ licensePreRouting ]
+ },
+ handler: async (request) => {
+ const callWithRequest = callWithRequestFactory(server, request);
+
+ try {
+ const {
+ has_all_requested: hasPermission,
+ cluster,
+ } = await callWithRequest('ccr.permissions', {
+ body: {
+ cluster: ['manage', 'manage_ccr'],
+ },
+ });
+
+ const missingPermissions = Object.keys(cluster).reduce((permissions, permissionName) => {
+ if (!cluster[permissionName]) {
+ permissions.push(permissionName);
+ return permissions;
+ }
+ }, []);
+
+ return {
+ hasPermission,
+ missingPermissions,
+ };
+ } catch(err) {
+ if (isEsError(err)) {
+ throw wrapEsError(err);
+ }
+ throw wrapUnknownError(err);
+ }
+ },
+ });
};
diff --git a/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
index a2833461674a4..18c015dc3c15a 100644
--- a/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
+++ b/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap
@@ -164,7 +164,7 @@ Array [
class="euiFormHelpText euiFormRow__text"
id="my-id-help"
>
- An IP address or host name, followed by the port.
+ An IP address or host name, followed by the transport port of the remote cluster.
diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/__snapshots__/remote_cluster_table.test.js.snap b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/__snapshots__/remote_cluster_table.test.js.snap
index 1328265be20df..0a46eb8ff18aa 100644
--- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/__snapshots__/remote_cluster_table.test.js.snap
+++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/__snapshots__/remote_cluster_table.test.js.snap
@@ -65,27 +65,58 @@ exports[`RemoteClusterTable renders a row for a default remote cluster 1`] = `
-
- Not connected
-
+
+
@@ -281,27 +312,58 @@ exports[`RemoteClusterTable renders a row for a remote cluster defined in elasti
-
- Not connected
-
+
+
diff --git a/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js b/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js
index c589cbd0c8965..973ece733eedb 100644
--- a/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js
+++ b/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js
@@ -26,6 +26,7 @@ export function checkLicense(xpackLicenseInfo) {
};
}
+ // Remote Clusters are used in both CCS and CCR, and CCS is available for all licenses.
const VALID_LICENSE_MODES = [
'trial',
'basic',
diff --git a/x-pack/plugins/xpack_main/public/services/xpack_info.js b/x-pack/plugins/xpack_main/public/services/xpack_info.js
index 8c0d972dad2f4..f02e0be8f9ffe 100644
--- a/x-pack/plugins/xpack_main/public/services/xpack_info.js
+++ b/x-pack/plugins/xpack_main/public/services/xpack_info.js
@@ -27,7 +27,8 @@ export function XPackInfoProvider($window, $injector, Private) {
};
setAll = (updatedXPackInfo) => {
- $window.sessionStorage.setItem(XPACK_INFO_KEY, JSON.stringify(updatedXPackInfo));
+ const camelCasedXPackInfo = convertKeysToCamelCaseDeep(updatedXPackInfo);
+ $window.sessionStorage.setItem(XPACK_INFO_KEY, JSON.stringify(camelCasedXPackInfo));
};
clear = () => {
@@ -52,7 +53,7 @@ export function XPackInfoProvider($window, $injector, Private) {
throw err;
})
.then((xpackInfoResponse) => {
- this.setAll(convertKeysToCamelCaseDeep(xpackInfoResponse.data));
+ this.setAll(xpackInfoResponse.data);
xpackInfoSignature.set(xpackInfoResponse.headers('kbn-xpack-sig'));
})
.finally(() => {