diff --git a/src/server/sass/build.js b/src/server/sass/build.js index 07deedec5958c..fde59b96334ef 100644 --- a/src/server/sass/build.js +++ b/src/server/sass/build.js @@ -23,7 +23,7 @@ import fs from 'fs'; import sass from 'node-sass'; import autoprefixer from 'autoprefixer'; import postcss from 'postcss'; -import postcssUrl from 'postcss-url'; +import postcssUrl from 'postcss-url'; // eslint-disable-line import/no-unresolved import mkdirp from 'mkdirp'; import chalk from 'chalk'; import isPathInside from 'is-path-inside'; 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 67461e156f28b..9dadb1a8960da 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 @@ -512,7 +512,7 @@ export const FollowerIndexForm = injectI18n( {advancedSettingsFields.map((advancedSetting) => { - const { field, title, description, label, helpText, defaultValue } = advancedSetting; + const { field, title, description, label, helpText, defaultValue, type } = advancedSetting; return ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js b/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js index bfc7d8e3c6c7b..533559c7cd604 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js @@ -49,7 +49,14 @@ export class FormEntryRow extends PureComponent { onFieldChange = (value) => { const { field, onValueUpdate, type } = this.props; const isNumber = type === 'number'; - onValueUpdate({ [field]: isNumber ? parseInt(value, 10) : value }); + + let valueParsed = value; + + if (isNumber) { + valueParsed = !!value ? Math.max(0, parseInt(value, 10)) : value; // make sure we don't send NaN value or a negative number + } + + onValueUpdate({ [field]: valueParsed }); } renderField = (isInvalid) => { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js index 33f9072068a08..7e6465f00a198 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_form_field.js @@ -99,13 +99,10 @@ export const RemoteClustersFormField = injectI18n( const hasClusters = Boolean(remoteClusters.length); const remoteClustersOptions = hasClusters ? remoteClusters.map(({ name, isConnected }) => ({ value: name, - text: isConnected ? name : ( - - ), + text: isConnected ? name : this.props.intl.formatMessage({ + id: 'xpack.crossClusterReplication.forms.remoteClusterDropdownNotConnected', + defaultMessage: '{name} (not connected)', + }, { name }), 'data-test-subj': `option-${name}` })) : []; const errorMessage = this.renderErrorMessage(); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js index 6cee0f21236b2..f7d47b57dac33 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js @@ -14,8 +14,8 @@ import { AutoFollowPatternAdd as AutoFollowPatternAddView } from './auto_follow_ const scope = SECTIONS.AUTO_FOLLOW_PATTERN; const mapStateToProps = (state) => ({ - apiStatus: getApiStatus(scope)(state), - apiError: getApiError(scope)(state), + apiStatus: getApiStatus(`${scope}-save`)(state), + apiError: getApiError(`${scope}-save`)(state), }); const mapDispatchToProps = dispatch => ({ diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js index 3a30e5f1b036f..99900d0fdd596 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js @@ -20,7 +20,6 @@ import { AutoFollowPatternPageTitle, RemoteClustersProvider, SectionLoading, - SectionError, } from '../../components'; export const AutoFollowPatternAdd = injectI18n( @@ -41,7 +40,7 @@ export const AutoFollowPatternAdd = injectI18n( } render() { - const { saveAutoFollowPattern, apiStatus, apiError, intl, match: { url: currentUrl } } = this.props; + const { saveAutoFollowPattern, apiStatus, apiError, match: { url: currentUrl } } = this.props; return ( @@ -67,20 +66,12 @@ export const AutoFollowPatternAdd = injectI18n( ); } - if (error) { - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClustersErrorTitle', - defaultMessage: 'Error loading remote clusters', - }); - return ; - } - return ( ); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 64b6cd56a2495..5099555daa6d8 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -119,7 +119,7 @@ export const AutoFollowPatternEdit = injectI18n( } render() { - const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, intl, match: { url: currentUrl } } = this.props; + const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, match: { url: currentUrl } } = this.props; return ( ; - } - return ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js index 2e12804d37f40..460c22393db1b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js @@ -20,7 +20,6 @@ import { FollowerIndexPageTitle, RemoteClustersProvider, SectionLoading, - SectionError, } from '../../components'; export const FollowerIndexAdd = injectI18n( @@ -41,7 +40,7 @@ export const FollowerIndexAdd = injectI18n( } render() { - const { saveFollowerIndex, clearApiError, apiStatus, apiError, intl, match: { url: currentUrl } } = this.props; + const { saveFollowerIndex, clearApiError, apiStatus, apiError, match: { url: currentUrl } } = this.props; return ( ; - } - return ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js index 25f12267bfa33..370d4f53aca53 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js @@ -242,17 +242,13 @@ export const FollowerIndexEdit = injectI18n( ); } - if (error) { - remoteClusters = []; - } - return ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index 50dd65b2bb27c..f541370fe247d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -7,12 +7,19 @@ import React, { PureComponent, Fragment } from 'react'; import PropTypes from 'prop-types'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { + EuiButton, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSpacer, +} from '@elastic/eui'; import routing from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; import { API_STATUS } from '../../../constants'; -import { SectionLoading, SectionError } from '../../../components'; +import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components'; import { AutoFollowPatternTable, DetailPanel } from './components'; const REFRESH_RATE_MS = 30000; @@ -88,6 +95,79 @@ export const AutoFollowPatternList = injectI18n( clearInterval(this.interval); } + renderHeader() { + const { isAuthorized } = this.props; + return ( + + + + +

+ +

+
+
+ + + {isAuthorized && ( + + + + )} + +
+ + +
+ ); + } + + renderContent(isEmpty) { + const { apiError, isAuthorized, intl } = this.props; + if (!isAuthorized) { + return ( + + )} + > + + + ); + } + + if (apiError) { + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle', + defaultMessage: 'Error loading auto-follow patterns', + }); + return ; + } + + if (isEmpty) { + return this.renderEmpty(); + } + + return this.renderList(); + } + renderEmpty() { return ( ; - } + const { autoFollowPatterns, apiStatus, } = this.props; + const isEmpty = apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length; - return this.renderList(); + return ( + + {!isEmpty && this.renderHeader()} + {this.renderContent(isEmpty)} + + ); } } ); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index b20c375e9d4ac..8c1dfabe7ff32 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -7,12 +7,19 @@ import React, { PureComponent, Fragment } from 'react'; import PropTypes from 'prop-types'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { + EuiButton, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSpacer, +} from '@elastic/eui'; import routing from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; import { API_STATUS } from '../../../constants'; -import { SectionLoading, SectionError } from '../../../components'; +import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components'; import { FollowerIndicesTable, DetailPanel } from './components'; const REFRESH_RATE_MS = 30000; @@ -84,6 +91,80 @@ export const FollowerIndicesList = injectI18n( clearInterval(this.interval); } + renderHeader() { + const { isAuthorized } = this.props; + + return ( + + + + +

+ +

+
+
+ + + {isAuthorized && ( + + + + )} + +
+ + +
+ ); + } + + renderContent(isEmpty) { + const { apiError, isAuthorized, intl } = this.props; + + if (!isAuthorized) { + return ( + + )} + > + + + ); + } + + if (apiError) { + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.followerIndexList.loadingErrorTitle', + defaultMessage: 'Error loading follower indices', + }); + return ; + } + + if (isEmpty) { + return this.renderEmpty(); + } + + return this.renderList(); + } + renderEmpty() { return ( ; - } - - return this.renderList(); + const { followerIndices, apiStatus } = this.props; + const isEmpty = apiStatus === API_STATUS.IDLE && !followerIndices.length; + return ( + + {!isEmpty && this.renderHeader()} + {this.renderContent(isEmpty)} + + ); } } ); 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 89b691ecb1c61..e987c69ee1afd 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 @@ -4,23 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; +import React, { PureComponent } from 'react'; 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 { - EuiButton, - EuiFlexGroup, - EuiFlexItem, EuiPageBody, EuiPageContent, EuiSpacer, EuiTab, EuiTabs, - EuiText, EuiTitle, } from '@elastic/eui'; @@ -29,17 +24,9 @@ import { listBreadcrumb } from '../../services/breadcrumbs'; import routing from '../../services/routing'; import { AutoFollowPatternList } from './auto_follow_pattern_list'; import { FollowerIndicesList } from './follower_indices_list'; -import { SectionUnauthorized } from '../../components'; export const CrossClusterReplicationHome = injectI18n( class extends PureComponent { - static propTypes = { - autoFollowPatterns: PropTypes.array, - isAutoFollowApiAuthorized: PropTypes.bool, - isFollowerIndexApiAuthorized: PropTypes.bool, - followerIndices: PropTypes.array, - } - state = { activeSection: 'follower_indices' } @@ -77,128 +64,6 @@ export const CrossClusterReplicationHome = injectI18n( routing.navigate(`/${section}`); } - 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 ( - - - - -

- -

-
-
- - - - - - -
- - -
- ); - } - - // If we're rendering the empty prompt, we don't want to render the header. - if (autoFollowPatterns.length > 0) { - return ( - - - - -

- -

-
-
- - - - - - -
- - -
- ); - } - } - - 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() { return ( @@ -214,7 +79,24 @@ export const CrossClusterReplicationHome = injectI18n( - {this.renderContent()} + + {this.tabs.map(tab => ( + this.onSectionChange(tab.id)} + isSelected={tab.id === this.state.activeSection} + key={tab.id} + > + {tab.name} + + ))} + + + + + + + +
); 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 d1e1386003ed2..10676826c1b77 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(`${apiPrefix}/stats/auto-follow`).then(extractData) ); /* Indices */ diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/__snapshots__/remote_cluster_list.test.js.snap b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/__snapshots__/remote_cluster_list.test.js.snap index 0f8537289951f..f87ba977b06e5 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/__snapshots__/remote_cluster_list.test.js.snap +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/__snapshots__/remote_cluster_list.test.js.snap @@ -5,7 +5,7 @@ exports[`RemoteClusterList renders empty state when loading is complete and ther class="euiPageBody" >
- -

- -

-
- + + + + +

+ +

+
+
+ + { isAuthorized && ( + + + + + + )} +
+ +
); } @@ -124,20 +143,16 @@ export const RemoteClusterList = injectI18n( defaultMessage: 'Permission error', }); return ( - - {this.getHeaderSection()} - - - - - + + + ); } @@ -155,17 +170,13 @@ export const RemoteClusterList = injectI18n( defaultMessage: 'Error loading remote clusters', }); return ( - - {this.getHeaderSection()} - - - {statusCode} {errorString} - - + + {statusCode} {errorString} + ); } @@ -208,58 +219,37 @@ export const RemoteClusterList = injectI18n( ); } - renderList() { - const { isLoading, clusters } = this.props; - - let table; - - if (isLoading) { - table = ( - - - - - - - - - - - - - - ); - } else { - table = ; - } - + renderLoading() { return ( - - - {this.getHeaderSection()} - - - + + + + + + + + - - - + + + + + ); + } - {table} + renderList() { + const { clusters } = this.props; + return ( + + ); @@ -267,28 +257,31 @@ export const RemoteClusterList = injectI18n( render() { const { isLoading, clusters, clusterLoadError } = this.props; + const isEmpty = !isLoading && !clusters.length; + const isAuthorized = !clusterLoadError || clusterLoadError.status !== 403; + const isHeaderVisible = clusterLoadError || !isEmpty; let content; if (clusterLoadError) { - if (clusterLoadError.status === 403) { + if (!isAuthorized) { content = this.renderNoPermission(); } else { content = this.renderError(clusterLoadError); } - } else if (!isLoading && !clusters.length) { + } else if (isEmpty) { content = this.renderEmpty(); + } else if (isLoading) { + content = this.renderLoading(); } else { content = this.renderList(); } return ( - + + {(isHeaderVisible) && this.getHeaderSection(isAuthorized)} {content} - {this.renderBlockingAction()}