[CCR] i18n feedback (#30028)

* Remove unused Chinese translations.
This commit is contained in:
CJ Cenizal 2019-02-14 17:36:56 -08:00 committed by GitHub
parent cc6c31cd1c
commit 1d2a6fafd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 2580 additions and 2541 deletions

View file

@ -10,7 +10,7 @@ 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 { FormattedMessage } from '@kbn/i18n/react';
import {
EuiEmptyPrompt,
@ -36,186 +36,187 @@ import {
FollowerIndexEdit,
} from './sections';
export const App = injectI18n(
class extends Component {
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
createHref: PropTypes.func.isRequired
}).isRequired
export class App extends Component {
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
createHref: PropTypes.func.isRequired
}).isRequired
}
}).isRequired
}
constructor(...args) {
super(...args);
this.registerRouter();
constructor(...args) {
super(...args);
this.registerRouter();
this.state = {
isFetchingPermissions: false,
fetchPermissionError: undefined,
hasPermission: false,
missingClusterPrivileges: [],
};
}
this.state = {
isFetchingPermissions: false,
fetchPermissionError: undefined,
hasPermission: false,
missingClusterPrivileges: [],
};
}
componentWillMount() {
routing.userHasLeftApp = false;
}
componentWillMount() {
routing.userHasLeftApp = false;
}
componentDidMount() {
this.checkPermissions();
}
componentDidMount() {
this.checkPermissions();
}
componentWillUnmount() {
routing.userHasLeftApp = true;
}
componentWillUnmount() {
routing.userHasLeftApp = true;
}
async checkPermissions() {
this.setState({
isFetchingPermissions: true,
});
try {
const { hasPermission, missingClusterPrivileges } = await loadPermissions();
async checkPermissions() {
this.setState({
isFetchingPermissions: true,
});
try {
const { hasPermission, missingClusterPrivileges } = await loadPermissions();
this.setState({
isFetchingPermissions: false,
hasPermission,
missingClusterPrivileges,
});
} 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,
isFetchingPermissions: false,
hasPermission,
missingClusterPrivileges,
} = this.state;
if (!isAvailable() || !isActive()) {
return (
<SectionUnauthorized
title={(
<FormattedMessage
id="xpack.crossClusterReplication.app.licenseErrorTitle"
defaultMessage="License error"
/>
)}
>
{getReason()}
{' '}
<a href={chrome.addBasePath('/app/kibana#/management/elasticsearch/license_management/home')}>
<FormattedMessage
id="xpack.crossClusterReplication.app.licenseErrorLinkText"
defaultMessage="Manage your license."
/>
</a>
</SectionUnauthorized>
);
});
} 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,
});
}
if (isFetchingPermissions) {
return (
<EuiPageContent horizontalPosition="center">
<EuiFlexGroup>
<EuiFlexItem>
<EuiLoadingSpinner size="xl"/>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle>
<h2>
<FormattedMessage
id="xpack.crossClusterReplication.app.permissionCheckTitle"
defaultMessage="Checking permissions..."
/>
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
);
}
if (fetchPermissionError) {
return (
<Fragment>
<SectionError
title={(
<FormattedMessage
id="xpack.crossClusterReplication.app.permissionCheckErrorTitle"
defaultMessage="Error checking permissions"
/>
)}
error={fetchPermissionError}
/>
<EuiSpacer size="m" />
</Fragment>
);
}
if (!hasPermission) {
return (
<EuiPageContent horizontalPosition="center">
<EuiEmptyPrompt
iconType="securityApp"
iconColor={null}
title={
<h2>
<FormattedMessage
id="xpack.crossClusterReplication.app.deniedPermissionTitle"
defaultMessage="You're missing cluster privileges"
/>
</h2>}
body={
<p>
<FormattedMessage
id="xpack.crossClusterReplication.app.deniedPermissionDescription"
defaultMessage="To use Cross Cluster Replication, you must have {clusterPrivileges,
plural, one {this cluster privilege} other {these cluster privileges}}: {clusterPrivileges}."
values={{ clusterPrivileges: missingClusterPrivileges.join(', ') }}
/>
</p>}
/>
</EuiPageContent>
);
}
return (
<div>
<Switch>
<Redirect exact from={`${BASE_PATH}`} to={`${BASE_PATH}/follower_indices`} />
<Route exact path={`${BASE_PATH}/auto_follow_patterns/add`} component={AutoFollowPatternAdd} />
<Route exact path={`${BASE_PATH}/auto_follow_patterns/edit/:id`} component={AutoFollowPatternEdit} />
<Route exact path={`${BASE_PATH}/follower_indices/add`} component={FollowerIndexAdd} />
<Route exact path={`${BASE_PATH}/follower_indices/edit/:id`} component={FollowerIndexEdit} />
<Route exact path={`${BASE_PATH}/:section`} component={CrossClusterReplicationHome} />
</Switch>
</div>
);
// 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,
missingClusterPrivileges,
} = this.state;
if (!isAvailable() || !isActive()) {
return (
<SectionUnauthorized
title={(
<FormattedMessage
id="xpack.crossClusterReplication.app.licenseErrorTitle"
defaultMessage="License error"
/>
)}
>
{getReason()}
{' '}
<a href={chrome.addBasePath('/app/kibana#/management/elasticsearch/license_management/home')}>
<FormattedMessage
id="xpack.crossClusterReplication.app.licenseErrorLinkText"
defaultMessage="Manage your license."
/>
</a>
</SectionUnauthorized>
);
}
if (isFetchingPermissions) {
return (
<EuiPageContent horizontalPosition="center">
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.crossClusterReplication.app.permissionCheckTitle"
defaultMessage="Checking permissions…"
/>
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
);
}
if (fetchPermissionError) {
return (
<Fragment>
<SectionError
title={(
<FormattedMessage
id="xpack.crossClusterReplication.app.permissionCheckErrorTitle"
defaultMessage="Error checking permissions"
/>
)}
error={fetchPermissionError}
/>
<EuiSpacer size="m" />
</Fragment>
);
}
if (!hasPermission) {
return (
<EuiPageContent horizontalPosition="center">
<EuiEmptyPrompt
iconType="securityApp"
iconColor={null}
title={
<h2>
<FormattedMessage
id="xpack.crossClusterReplication.app.deniedPermissionTitle"
defaultMessage="You're missing cluster privileges"
/>
</h2>}
body={
<p>
<FormattedMessage
id="xpack.crossClusterReplication.app.deniedPermissionDescription"
defaultMessage="To use Cross Cluster Replication, you must have {clusterPrivilegesCount,
plural, one {this cluster privilege} other {these cluster privileges}}: {clusterPrivileges}."
values={{
clusterPrivileges: missingClusterPrivileges.join(', '),
clusterPrivilegesCount: missingClusterPrivileges.length,
}}
/>
</p>}
/>
</EuiPageContent>
);
}
return (
<div>
<Switch>
<Redirect exact from={`${BASE_PATH}`} to={`${BASE_PATH}/follower_indices`} />
<Route exact path={`${BASE_PATH}/auto_follow_patterns/add`} component={AutoFollowPatternAdd} />
<Route exact path={`${BASE_PATH}/auto_follow_patterns/edit/:id`} component={AutoFollowPatternEdit} />
<Route exact path={`${BASE_PATH}/follower_indices/add`} component={FollowerIndexAdd} />
<Route exact path={`${BASE_PATH}/follower_indices/edit/:id`} component={FollowerIndexEdit} />
<Route exact path={`${BASE_PATH}/:section`} component={CrossClusterReplicationHome} />
</Switch>
</div>
);
}
}

View file

@ -7,7 +7,8 @@
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'react-redux';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiConfirmModal,
EuiOverlayMask,
@ -16,7 +17,7 @@ import {
import { deleteAutoFollowPattern } from '../store/actions';
import { arrify } from '../../../common/services/utils';
class Provider extends PureComponent {
class AutoFollowPatternDeleteProviderUi extends PureComponent {
state = {
isModalOpen: false,
ids: null
@ -44,18 +45,22 @@ class Provider extends PureComponent {
};
renderModal = () => {
const { intl } = this.props;
const { ids } = this.state;
const isSingle = ids.length === 1;
const title = isSingle
? intl.formatMessage({
id: 'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.deleteSingleTitle',
defaultMessage: 'Remove auto-follow pattern \'{name}\'?',
}, { name: ids[0] })
: intl.formatMessage({
id: 'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.deleteMultipleTitle',
defaultMessage: 'Remove {count} auto-follow patterns?',
}, { count: ids.length });
? i18n.translate(
'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.deleteSingleTitle',
{
defaultMessage: `Remove auto-follow pattern '{name}'?`,
values: { name: ids[0] }
}
) : i18n.translate(
'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.deleteMultipleTitle',
{
defaultMessage: `Remove {count} auto-follow patterns?`,
values: { count: ids.length }
}
);
return (
<EuiOverlayMask>
@ -65,17 +70,21 @@ class Provider extends PureComponent {
onCancel={this.closeConfirmModal}
onConfirm={this.onConfirm}
cancelButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.cancelButtonText',
defaultMessage: 'Cancel',
})
i18n.translate(
'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.cancelButtonText',
{
defaultMessage: 'Cancel'
}
)
}
buttonColor="danger"
confirmButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.confirmButtonText',
defaultMessage: 'Remove',
})
i18n.translate(
'xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.confirmButtonText',
{
defaultMessage: 'Remove'
}
)
}
onMouseOver={this.onMouseOverModal}
>
@ -115,5 +124,5 @@ const mapDispatchToProps = dispatch => ({
export const AutoFollowPatternDeleteProvider = connect(
undefined,
mapDispatchToProps
)(injectI18n(Provider));
)(AutoFollowPatternDeleteProviderUi);

View file

@ -6,7 +6,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
@ -58,7 +59,7 @@ export const updateFormErrors = (errors, existingErrors) => ({
}
});
export class AutoFollowPatternFormUI extends PureComponent {
export class AutoFollowPatternForm extends PureComponent {
static propTypes = {
saveAutoFollowPattern: PropTypes.func.isRequired,
autoFollowPattern: PropTypes.object,
@ -154,11 +155,12 @@ export class AutoFollowPatternFormUI extends PureComponent {
const { autoFollowPattern: { leaderIndexPatterns } } = this.state;
if (leaderIndexPatterns.includes(leaderIndexPattern)) {
const { intl } = this.props;
const errorMsg = intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternForm.leaderIndexPatternError.duplicateMessage',
defaultMessage: `Duplicate leader index pattern aren't allowed.`,
});
const errorMsg = i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternForm.leaderIndexPatternError.duplicateMessage',
{
defaultMessage: `Duplicate leader index pattern aren't allowed.`
}
);
const errors = {
leaderIndexPatterns: {
@ -212,12 +214,11 @@ export class AutoFollowPatternFormUI extends PureComponent {
* Secctions Renders
*/
renderApiErrors() {
const { apiError, intl } = this.props;
const { apiError } = this.props;
if (apiError) {
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternForm.savingErrorTitle',
defaultMessage: `Can't create auto-follow pattern`,
const title = i18n.translate('xpack.crossClusterReplication.autoFollowPatternForm.savingErrorTitle', {
defaultMessage: `Can't create auto-follow pattern`
});
return (
@ -232,7 +233,6 @@ export class AutoFollowPatternFormUI extends PureComponent {
}
renderForm = () => {
const { intl } = this.props;
const {
autoFollowPattern: {
name,
@ -436,10 +436,12 @@ export class AutoFollowPatternFormUI extends PureComponent {
>
<EuiComboBox
noSuggestions
placeholder={intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternForm.fieldLeaderIndexPatternsPlaceholder',
defaultMessage: 'Type and then hit ENTER',
})}
placeholder={i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternForm.fieldLeaderIndexPatternsPlaceholder',
{
defaultMessage: 'Type and then hit ENTER'
}
)}
selectedOptions={formattedLeaderIndexPatterns}
onCreateOption={this.onCreateLeaderIndexPattern}
onChange={this.onLeaderIndexPatternChange}
@ -670,5 +672,3 @@ export class AutoFollowPatternFormUI extends PureComponent {
);
}
}
export const AutoFollowPatternForm = injectI18n(AutoFollowPatternFormUI);

View file

@ -7,20 +7,23 @@
import React from 'react';
import { EuiCallOut } from '@elastic/eui';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { getPreviewIndicesFromAutoFollowPattern } from '../services/auto_follow_pattern';
export const AutoFollowPatternIndicesPreview = injectI18n(({ prefix, suffix, leaderIndexPatterns, intl }) => {
export const AutoFollowPatternIndicesPreview = ({ prefix, suffix, leaderIndexPatterns }) => {
const { indicesPreview } = getPreviewIndicesFromAutoFollowPattern({
prefix,
suffix,
leaderIndexPatterns
});
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternForm.indicesPreviewTitle',
defaultMessage: 'Index name examples',
});
const title = i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternForm.indicesPreviewTitle',
{
defaultMessage: 'Index name examples'
}
);
return (
<EuiCallOut
@ -40,4 +43,4 @@ export const AutoFollowPatternIndicesPreview = injectI18n(({ prefix, suffix, lea
</ul>
</EuiCallOut>
);
});
};

View file

@ -160,9 +160,9 @@ export const advancedSettingsFields = [
),
description: i18n.translate(
'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferCountDescription', {
defaultMessage: `The maximum number of operations that can be queued for writing; when this
limit is reached, reads from the remote cluster will be deferred until the number of queued
operations goes below the limit.`
defaultMessage: 'The maximum number of operations that can be queued for writing; when this ' +
'limit is reached, reads from the remote cluster will be deferred until the number of queued ' +
'operations goes below the limit.'
}
),
label: i18n.translate(
@ -181,9 +181,9 @@ export const advancedSettingsFields = [
),
description: i18n.translate(
'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxWriteBufferSizeDescription', {
defaultMessage: `The maximum total bytes of operations that can be queued for writing; when
this limit is reached, reads from the remote cluster will be deferred until the total bytes
of queued operations goes below the limit.`
defaultMessage: 'The maximum total bytes of operations that can be queued for writing; when ' +
'this limit is reached, reads from the remote cluster will be deferred until the total bytes ' +
'of queued operations goes below the limit.'
}
),
label: i18n.translate(
@ -202,8 +202,8 @@ export const advancedSettingsFields = [
),
description: i18n.translate(
'xpack.crossClusterReplication.followerIndexForm.advancedSettings.maxRetryDelayDescription', {
defaultMessage: `The maximum time to wait before retrying an operation that failed exceptionally;
an exponential backoff strategy is employed when retrying.`
defaultMessage: 'The maximum time to wait before retrying an operation that failed exceptionally; ' +
'an exponential backoff strategy is employed when retrying.'
}
),
label: i18n.translate(
@ -222,10 +222,10 @@ export const advancedSettingsFields = [
),
description: i18n.translate(
'xpack.crossClusterReplication.followerIndexForm.advancedSettings.readPollTimeoutDescription', {
defaultMessage: `The maximum time to wait for new operations on the remote cluster when the
follower index is synchronized with the leader index; when the timeout has elapsed, the
poll for operations will return to the follower so that it can update some statistics, and
then the follower will immediately attempt to read from the leader again.`
defaultMessage: 'The maximum time to wait for new operations on the remote cluster when the ' +
'follower index is synchronized with the leader index; when the timeout has elapsed, the ' +
'poll for operations will return to the follower so that it can update some statistics, and ' +
'then the follower will immediately attempt to read from the leader again.'
}
),
label: i18n.translate(

View file

@ -7,7 +7,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiConfirmModal,
EuiOverlayMask,
@ -17,7 +18,7 @@ import { pauseFollowerIndex } from '../store/actions';
import { arrify } from '../../../common/services/utils';
import { areAllSettingsDefault } from '../services/follower_index_default_settings';
class Provider extends PureComponent {
class FollowerIndexPauseProviderUi extends PureComponent {
static propTypes = {
onConfirm: PropTypes.func,
}
@ -50,18 +51,17 @@ class Provider extends PureComponent {
};
renderModal = () => {
const { intl } = this.props;
const { indices } = this.state;
const isSingle = indices.length === 1;
const title = isSingle
? intl.formatMessage({
id: 'xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.pauseSingleTitle',
defaultMessage: 'Pause replication to follower index \'{name}\'?',
}, { name: indices[0].name })
: intl.formatMessage({
id: 'xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.pauseMultipleTitle',
? i18n.translate('xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.pauseSingleTitle', {
defaultMessage: `Pause replication to follower index '{name}'?`,
values: { name: indices[0].name },
})
: i18n.translate('xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.pauseMultipleTitle', {
defaultMessage: 'Pause replication to {count} follower indices?',
}, { count: indices.length });
values: { count: indices.length },
});
const hasCustomSettings = indices.some(index => !areAllSettingsDefault(index));
return (
@ -72,14 +72,12 @@ class Provider extends PureComponent {
onCancel={this.closeConfirmModal}
onConfirm={this.onConfirm}
cancelButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.cancelButtonText',
i18n.translate('xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.cancelButtonText', {
defaultMessage: 'Cancel',
})
}
buttonColor={hasCustomSettings ? 'danger' : 'primary'}
confirmButtonText={intl.formatMessage({
id: 'xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.confirmButtonText',
confirmButtonText={i18n.translate('xpack.crossClusterReplication.pauseFollowerIndex.confirmModal.confirmButtonText', {
defaultMessage: 'Pause replication',
})}
onMouseOver={this.onMouseOverModal}
@ -141,5 +139,5 @@ const mapDispatchToProps = (dispatch) => ({
export const FollowerIndexPauseProvider = connect(
undefined,
mapDispatchToProps
)(injectI18n(Provider));
)(FollowerIndexPauseProviderUi);

View file

@ -7,7 +7,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiConfirmModal,
EuiLink,
@ -18,7 +19,7 @@ import routing from '../services/routing';
import { resumeFollowerIndex } from '../store/actions';
import { arrify } from '../../../common/services/utils';
class Provider extends PureComponent {
class FollowerIndexResumeProviderUi extends PureComponent {
static propTypes = {
onConfirm: PropTypes.func,
}
@ -51,18 +52,22 @@ class Provider extends PureComponent {
};
renderModal = () => {
const { intl } = this.props;
const { ids } = this.state;
const isSingle = ids.length === 1;
const title = isSingle
? intl.formatMessage({
id: 'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.resumeSingleTitle',
defaultMessage: 'Resume replication to follower index \'{name}\'?',
}, { name: ids[0] })
: intl.formatMessage({
id: 'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.resumeMultipleTitle',
defaultMessage: 'Resume replication to {count} follower indices?',
}, { count: ids.length });
? i18n.translate(
'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.resumeSingleTitle',
{
defaultMessage: `Resume replication to follower index '{name}'?`,
values: { name: ids[0] }
}
) : i18n.translate(
'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.resumeMultipleTitle',
{
defaultMessage: `Resume replication to {count} follower indices?`,
values: { count: ids.length }
}
);
return (
<EuiOverlayMask>
@ -72,17 +77,21 @@ class Provider extends PureComponent {
onCancel={this.closeConfirmModal}
onConfirm={this.onConfirm}
cancelButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.cancelButtonText',
defaultMessage: 'Cancel',
})
i18n.translate(
'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.cancelButtonText',
{
defaultMessage: 'Cancel'
}
)
}
buttonColor="primary"
confirmButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.confirmButtonText',
defaultMessage: 'Resume replication',
})
i18n.translate(
'xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.confirmButtonText',
{
defaultMessage: 'Resume replication'
}
)
}
onMouseOver={this.onMouseOverModal}
>
@ -150,5 +159,5 @@ const mapDispatchToProps = (dispatch) => ({
export const FollowerIndexResumeProvider = connect(
undefined,
mapDispatchToProps
)(injectI18n(Provider));
)(FollowerIndexResumeProviderUi);

View file

@ -7,7 +7,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiConfirmModal,
EuiOverlayMask,
@ -16,7 +17,7 @@ import {
import { unfollowLeaderIndex } from '../store/actions';
import { arrify } from '../../../common/services/utils';
class Provider extends PureComponent {
class FollowerIndexUnfollowProviderUi extends PureComponent {
static propTypes = {
onConfirm: PropTypes.func,
}
@ -49,18 +50,22 @@ class Provider extends PureComponent {
};
renderModal = () => {
const { intl } = this.props;
const { ids } = this.state;
const isSingle = ids.length === 1;
const title = isSingle
? intl.formatMessage({
id: 'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.unfollowSingleTitle',
defaultMessage: `Unfollow leader index of '{name}'?`,
}, { name: ids[0] })
: intl.formatMessage({
id: 'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.unfollowMultipleTitle',
defaultMessage: 'Unfollow {count} leader indices?',
}, { count: ids.length });
? i18n.translate(
'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.unfollowSingleTitle',
{
defaultMessage: `Unfollow leader index of '{name}'?`,
values: { name: ids[0] }
}
) : i18n.translate(
'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.unfollowMultipleTitle',
{
defaultMessage: `Unfollow {count} leader indices?`,
values: { count: ids.length }
}
);
return (
<EuiOverlayMask>
@ -70,17 +75,21 @@ class Provider extends PureComponent {
onCancel={this.closeConfirmModal}
onConfirm={this.onConfirm}
cancelButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.cancelButtonText',
defaultMessage: 'Cancel',
})
i18n.translate(
'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.cancelButtonText',
{
defaultMessage: 'Cancel'
}
)
}
buttonColor="danger"
confirmButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.confirmButtonText',
defaultMessage: 'Unfollow leader',
})
i18n.translate(
'xpack.crossClusterReplication.unfollowLeaderIndex.confirmModal.confirmButtonText',
{
defaultMessage: 'Unfollow leader'
}
)
}
onMouseOver={this.onMouseOverModal}
>
@ -133,5 +142,5 @@ const mapDispatchToProps = (dispatch) => ({
export const FollowerIndexUnfollowProvider = connect(
undefined,
mapDispatchToProps
)(injectI18n(Provider));
)(FollowerIndexUnfollowProviderUi);

View file

@ -6,7 +6,8 @@
import React, { Fragment, PureComponent } from 'react';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
EuiButtonEmpty,
@ -24,298 +25,298 @@ import { BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants';
const errorMessages = {
noClusterFound: () => (
<FormattedMessage
id="xpack.crossClusterReplication.forms.emptyRemoteClustersCallOutDescription"
id="xpack.crossClusterReplication.remoteClustersFormField.emptyRemoteClustersCallOutDescription"
defaultMessage="You need at least one remote cluster to create a follower index."
/>
),
remoteClusterNotConnectedEditable: (name) => ({
title: (
<FormattedMessage
id="xpack.crossClusterReplication.forms.currentRemoteClusterNotConnectedCallOutTitle"
id="xpack.crossClusterReplication.remoteClustersFormField.currentRemoteClusterNotConnectedCallOutTitle"
defaultMessage="Remote cluster '{name}' is not connected"
values={{ name }}
/>
),
description: (
<FormattedMessage
id="xpack.crossClusterReplication.forms.currentRemoteClusterNotConnectedCallOutDescription"
id="xpack.crossClusterReplication.remoteClustersFormField.currentRemoteClusterNotConnectedCallOutDescription"
defaultMessage="Edit the remote cluster or select a cluster that is connected."
/>
),
}),
};
export const RemoteClustersFormField = injectI18n(
class extends PureComponent {
errorMessages = {
...errorMessages,
...this.props.errorMessages
}
export class RemoteClustersFormField extends PureComponent {
errorMessages = {
...errorMessages,
...this.props.errorMessages
}
componentDidMount() {
const { selected, onError } = this.props;
const { error } = this.validateRemoteCluster(selected);
componentDidMount() {
const { selected, onError } = this.props;
const { error } = this.validateRemoteCluster(selected);
onError(error);
}
onError(error);
}
validateRemoteCluster(clusterName) {
const { remoteClusters } = this.props;
const remoteCluster = remoteClusters.find(c => c.name === clusterName);
validateRemoteCluster(clusterName) {
const { remoteClusters } = this.props;
const remoteCluster = remoteClusters.find(c => c.name === clusterName);
return remoteCluster && remoteCluster.isConnected
? { error: null }
: { error: { message: (
<FormattedMessage
id="xpack.crossClusterReplication.forms.invalidRemoteClusterError"
defaultMessage="Invalid remote cluster"
/>
) } };
}
onRemoteClusterChange = (cluster) => {
const { onChange, onError } = this.props;
const { error } = this.validateRemoteCluster(cluster);
onChange(cluster);
onError(error);
};
renderNotEditable = () => {
const { areErrorsVisible } = this.props;
const errorMessage = this.renderErrorMessage();
return (
<Fragment>
<EuiFieldText
value={this.props.selected}
fullWidth
disabled
isInvalid={areErrorsVisible && Boolean(errorMessage)}
/>
{ areErrorsVisible && Boolean(errorMessage) ? this.renderValidRemoteClusterRequired() : null }
{ errorMessage }
</Fragment>
);
};
renderValidRemoteClusterRequired = () => (
<EuiFormErrorText>
return remoteCluster && remoteCluster.isConnected
? { error: null }
: { error: { message: (
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternForm.remoteCluster.validRemoteClusterRequired"
defaultMessage="A connected remote cluster is required."
id="xpack.crossClusterReplication.remoteClustersFormField.invalidRemoteClusterError"
defaultMessage="Invalid remote cluster"
/>
</EuiFormErrorText>
) } };
}
onRemoteClusterChange = (cluster) => {
const { onChange, onError } = this.props;
const { error } = this.validateRemoteCluster(cluster);
onChange(cluster);
onError(error);
};
renderNotEditable = () => {
const { areErrorsVisible } = this.props;
const errorMessage = this.renderErrorMessage();
return (
<Fragment>
<EuiFieldText
value={this.props.selected}
fullWidth
disabled
isInvalid={areErrorsVisible && Boolean(errorMessage)}
/>
{ areErrorsVisible && Boolean(errorMessage) ? this.renderValidRemoteClusterRequired() : null }
{ errorMessage }
</Fragment>
);
};
renderDropdown = () => {
const { remoteClusters, selected, currentUrl, areErrorsVisible } = this.props;
const hasClusters = Boolean(remoteClusters.length);
const remoteClustersOptions = hasClusters ? remoteClusters.map(({ name, isConnected }) => ({
value: name,
text: isConnected ? name : this.props.intl.formatMessage({
id: 'xpack.crossClusterReplication.forms.remoteClusterDropdownNotConnected',
renderValidRemoteClusterRequired = () => (
<EuiFormErrorText>
<FormattedMessage
id="xpack.crossClusterReplication.remoteClustersFormField.validRemoteClusterRequired"
defaultMessage="A connected remote cluster is required."
/>
</EuiFormErrorText>
);
renderDropdown = () => {
const { remoteClusters, selected, currentUrl, areErrorsVisible } = this.props;
const hasClusters = Boolean(remoteClusters.length);
const remoteClustersOptions = hasClusters ? remoteClusters.map(({ name, isConnected }) => ({
value: name,
text: isConnected ? name : i18n.translate(
'xpack.crossClusterReplication.remoteClustersFormField.remoteClusterDropdownNotConnected',
{
defaultMessage: '{name} (not connected)',
}, { name }),
'data-test-subj': `option-${name}`
})) : [];
const errorMessage = this.renderErrorMessage();
values: { name },
}
),
'data-test-subj': `option-${name}`
})) : [];
const errorMessage = this.renderErrorMessage();
return (
<Fragment>
<EuiSelect
fullWidth
options={remoteClustersOptions}
value={hasClusters ? selected : ''}
onChange={(e) => { this.onRemoteClusterChange(e.target.value); }}
hasNoInitialSelection={!hasClusters}
isInvalid={areErrorsVisible && Boolean(errorMessage)}
/>
{ areErrorsVisible && Boolean(errorMessage) ? this.renderValidRemoteClusterRequired() : null }
{ errorMessage }
return (
<Fragment>
<EuiSelect
fullWidth
options={remoteClustersOptions}
value={hasClusters ? selected : ''}
onChange={(e) => { this.onRemoteClusterChange(e.target.value); }}
hasNoInitialSelection={!hasClusters}
isInvalid={areErrorsVisible && Boolean(errorMessage)}
/>
{ areErrorsVisible && Boolean(errorMessage) ? this.renderValidRemoteClusterRequired() : null }
{ errorMessage }
<Fragment>
<EuiSpacer size="s" />
<div> {/* Break out of EuiFormRow's flexbox layout */}
<EuiButtonEmpty
{...routing.getRouterLinkProps('/add', BASE_PATH_REMOTE_CLUSTERS, { redirect: currentUrl }, true)}
size="s"
iconType="plusInCircle"
flush="left"
>
<FormattedMessage
id="xpack.crossClusterReplication.forms.addRemoteClusterButtonLabel"
defaultMessage="Add remote cluster"
/>
</EuiButtonEmpty>
</div>
</Fragment>
</Fragment>
);
};
renderNoClusterFound = () => {
const { intl, currentUrl } = this.props;
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.forms.emptyRemoteClustersCallOutTitle',
defaultMessage: `You don't have any remote clusters`,
});
return (
<Fragment>
<EuiCallOut
title={title}
color="danger"
iconType="cross"
>
<p>
{ this.errorMessages.noClusterFound() }
</p>
<EuiButton
<EuiSpacer size="s" />
<div> {/* Break out of EuiFormRow's flexbox layout */}
<EuiButtonEmpty
{...routing.getRouterLinkProps('/add', BASE_PATH_REMOTE_CLUSTERS, { redirect: currentUrl }, true)}
size="s"
iconType="plusInCircle"
color="danger"
flush="left"
>
<FormattedMessage
id="xpack.crossClusterReplication.forms.addRemoteClusterButtonLabel"
id="xpack.crossClusterReplication.remoteClustersFormField.addRemoteClusterButtonLabel"
defaultMessage="Add remote cluster"
/>
</EuiButton>
</EuiCallOut>
</EuiButtonEmpty>
</div>
</Fragment>
);
};
</Fragment>
);
};
renderCurrentRemoteClusterNotConnected = (name, fatal) => {
const { isEditable, currentUrl } = this.props;
const {
remoteClusterNotConnectedEditable,
remoteClusterNotConnectedNotEditable,
} = this.errorMessages;
renderNoClusterFound = () => {
const { currentUrl } = this.props;
const title = i18n.translate('xpack.crossClusterReplication.remoteClustersFormField.emptyRemoteClustersCallOutTitle', {
defaultMessage: `You don't have any remote clusters`
});
const { title, description } = isEditable
? remoteClusterNotConnectedEditable(name)
: remoteClusterNotConnectedNotEditable(name);
return (
<EuiCallOut
title={title}
color={fatal ? 'danger' : 'warning'}
iconType="cross"
>
<p>
{ description }
</p>
<EuiButton
{...routing.getRouterLinkProps(`/edit/${name}`, BASE_PATH_REMOTE_CLUSTERS, { redirect: currentUrl }, true)}
color={fatal ? 'danger' : 'warning'}
>
<FormattedMessage
id="xpack.crossClusterReplication.forms.viewRemoteClusterButtonLabel"
defaultMessage="Edit remote cluster"
/>
</EuiButton>
</EuiCallOut>
);
};
renderRemoteClusterDoesNotExist = (name) => {
const { intl, currentUrl } = this.props;
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.forms.remoteClusterNotFoundTitle',
defaultMessage: `Couldn't find remote cluster '{name}'`,
}, { name });
return (
return (
<Fragment>
<EuiCallOut
title={title}
color="danger"
iconType="cross"
>
<p>
{ this.errorMessages.remoteClusterDoesNotExist(name) }
{ this.errorMessages.noClusterFound() }
</p>
<EuiButton
{...routing.getRouterLinkProps('/add', BASE_PATH_REMOTE_CLUSTERS, { redirect: currentUrl }, true)}
iconType="plusInCircle"
color="danger"
>
<FormattedMessage
id="xpack.crossClusterReplication.forms.addRemoteClusterButtonLabel"
id="xpack.crossClusterReplication.remoteClustersFormField.addRemoteClusterButtonLabel"
defaultMessage="Add remote cluster"
/>
</EuiButton>
</EuiCallOut>
);
}
</Fragment>
);
};
renderErrorMessage = () => {
const { selected, remoteClusters, isEditable } = this.props;
const remoteCluster = remoteClusters.find(c => c.name === selected);
const isSelectedRemoteClusterConnected = remoteCluster && remoteCluster.isConnected;
let error;
renderCurrentRemoteClusterNotConnected = (name, fatal) => {
const { isEditable, currentUrl } = this.props;
const {
remoteClusterNotConnectedEditable,
remoteClusterNotConnectedNotEditable,
} = this.errorMessages;
if (isEditable) {
/* Create */
const hasClusters = Boolean(remoteClusters.length);
if (hasClusters && !isSelectedRemoteClusterConnected) {
error = this.renderCurrentRemoteClusterNotConnected(selected);
} else if (!hasClusters) {
error = this.renderNoClusterFound();
}
} else {
/* Edit */
const doesExists = !!remoteCluster;
if (!doesExists) {
error = this.renderRemoteClusterDoesNotExist(selected);
} else if (!isSelectedRemoteClusterConnected) {
error = this.renderCurrentRemoteClusterNotConnected(selected, true);
}
}
const { title, description } = isEditable
? remoteClusterNotConnectedEditable(name)
: remoteClusterNotConnectedNotEditable(name);
return error ? (
<Fragment>
<EuiSpacer size="s" />
{error}
</Fragment>
) : null;
}
return (
<EuiCallOut
title={title}
color={fatal ? 'danger' : 'warning'}
iconType="cross"
>
<p>
{ description }
</p>
render() {
const { remoteClusters, selected, isEditable, areErrorsVisible } = this.props;
const remoteCluster = remoteClusters.find(c => c.name === selected);
const hasClusters = Boolean(remoteClusters.length);
const isSelectedRemoteClusterConnected = remoteCluster && remoteCluster.isConnected;
const isInvalid = areErrorsVisible && (!hasClusters || !isSelectedRemoteClusterConnected);
let field;
if(isEditable) {
if(hasClusters) {
field = this.renderDropdown();
} else {
field = this.renderErrorMessage();
}
} else {
field = this.renderNotEditable();
}
return (
<EuiFormRow
label={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternForm.remoteCluster.fieldClusterLabel"
defaultMessage="Remote cluster"
/>
)}
isInvalid={isInvalid}
fullWidth
<EuiButton
{...routing.getRouterLinkProps(`/edit/${name}`, BASE_PATH_REMOTE_CLUSTERS, { redirect: currentUrl }, true)}
color={fatal ? 'danger' : 'warning'}
>
<Fragment>
{field}
</Fragment>
</EuiFormRow>
);
}
<FormattedMessage
id="xpack.crossClusterReplication.remoteClustersFormField.viewRemoteClusterButtonLabel"
defaultMessage="Edit remote cluster"
/>
</EuiButton>
</EuiCallOut>
);
};
renderRemoteClusterDoesNotExist = (name) => {
const { currentUrl } = this.props;
const title = i18n.translate('xpack.crossClusterReplication.remoteClustersFormField.remoteClusterNotFoundTitle', {
defaultMessage: `Couldn't find remote cluster '{name}'`,
values: { name }
});
return (
<EuiCallOut
title={title}
color="danger"
iconType="cross"
>
<p>
{ this.errorMessages.remoteClusterDoesNotExist(name) }
</p>
<EuiButton
{...routing.getRouterLinkProps('/add', BASE_PATH_REMOTE_CLUSTERS, { redirect: currentUrl }, true)}
iconType="plusInCircle"
color="danger"
>
<FormattedMessage
id="xpack.crossClusterReplication.remoteClustersFormField.addRemoteClusterButtonLabel"
defaultMessage="Add remote cluster"
/>
</EuiButton>
</EuiCallOut>
);
}
);
renderErrorMessage = () => {
const { selected, remoteClusters, isEditable } = this.props;
const remoteCluster = remoteClusters.find(c => c.name === selected);
const isSelectedRemoteClusterConnected = remoteCluster && remoteCluster.isConnected;
let error;
if (isEditable) {
/* Create */
const hasClusters = Boolean(remoteClusters.length);
if (hasClusters && !isSelectedRemoteClusterConnected) {
error = this.renderCurrentRemoteClusterNotConnected(selected);
} else if (!hasClusters) {
error = this.renderNoClusterFound();
}
} else {
/* Edit */
const doesExists = !!remoteCluster;
if (!doesExists) {
error = this.renderRemoteClusterDoesNotExist(selected);
} else if (!isSelectedRemoteClusterConnected) {
error = this.renderCurrentRemoteClusterNotConnected(selected, true);
}
}
return error ? (
<Fragment>
<EuiSpacer size="s" />
{error}
</Fragment>
) : null;
}
render() {
const { remoteClusters, selected, isEditable, areErrorsVisible } = this.props;
const remoteCluster = remoteClusters.find(c => c.name === selected);
const hasClusters = Boolean(remoteClusters.length);
const isSelectedRemoteClusterConnected = remoteCluster && remoteCluster.isConnected;
const isInvalid = areErrorsVisible && (!hasClusters || !isSelectedRemoteClusterConnected);
let field;
if(isEditable) {
if(hasClusters) {
field = this.renderDropdown();
} else {
field = this.renderErrorMessage();
}
} else {
field = this.renderNotEditable();
}
return (
<EuiFormRow
label={(
<FormattedMessage
id="xpack.crossClusterReplication.remoteClustersFormField.fieldClusterLabel"
defaultMessage="Remote cluster"
/>
)}
isInvalid={isInvalid}
fullWidth
>
<Fragment>
{field}
</Fragment>
</EuiFormRow>
);
}
}

View file

@ -6,7 +6,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
@ -22,69 +22,67 @@ import {
SectionLoading,
} from '../../components';
export const AutoFollowPatternAdd = injectI18n(
class extends PureComponent {
static propTypes = {
saveAutoFollowPattern: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object,
apiStatus: PropTypes.string.isRequired,
}
componentDidMount() {
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, addBreadcrumb ]);
}
componentWillUnmount() {
this.props.clearApiError();
}
render() {
const { saveAutoFollowPattern, apiStatus, apiError, match: { url: currentUrl } } = this.props;
return (
<EuiPageContent>
<AutoFollowPatternPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPattern.addTitle"
defaultMessage="Add auto-follow pattern"
/>
)}
/>
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClusters"
defaultMessage="Loading remote clusters..."
/>
</SectionLoading>
);
}
return (
<AutoFollowPatternForm
apiStatus={apiStatus}
apiError={apiError}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
saveAutoFollowPattern={saveAutoFollowPattern}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternCreateForm.saveButtonLabel"
defaultMessage="Create"
/>
)}
/>
);
}}
</RemoteClustersProvider>
</EuiPageContent>
);
}
export class AutoFollowPatternAdd extends PureComponent {
static propTypes = {
saveAutoFollowPattern: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object,
apiStatus: PropTypes.string.isRequired,
}
);
componentDidMount() {
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, addBreadcrumb ]);
}
componentWillUnmount() {
this.props.clearApiError();
}
render() {
const { saveAutoFollowPattern, apiStatus, apiError, match: { url: currentUrl } } = this.props;
return (
<EuiPageContent>
<AutoFollowPatternPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPattern.addTitle"
defaultMessage="Add auto-follow pattern"
/>
)}
/>
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClustersMessage"
defaultMessage="Loading remote clusters…"
/>
</SectionLoading>
);
}
return (
<AutoFollowPatternForm
apiStatus={apiStatus}
apiError={apiError}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
saveAutoFollowPattern={saveAutoFollowPattern}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternCreateForm.saveButtonLabel"
defaultMessage="Create"
/>
)}
/>
);
}}
</RemoteClustersProvider>
</EuiPageContent>
);
}
}

View file

@ -6,7 +6,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
@ -29,156 +30,159 @@ import {
} from '../../components';
import { API_STATUS } from '../../constants';
export const AutoFollowPatternEdit = injectI18n(
class extends PureComponent {
static propTypes = {
getAutoFollowPattern: PropTypes.func.isRequired,
selectAutoFollowPattern: PropTypes.func.isRequired,
saveAutoFollowPattern: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object.isRequired,
apiStatus: PropTypes.object.isRequired,
autoFollowPattern: PropTypes.object,
autoFollowPatternId: PropTypes.string,
export class AutoFollowPatternEdit extends PureComponent {
static propTypes = {
getAutoFollowPattern: PropTypes.func.isRequired,
selectAutoFollowPattern: PropTypes.func.isRequired,
saveAutoFollowPattern: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object.isRequired,
apiStatus: PropTypes.object.isRequired,
autoFollowPattern: PropTypes.object,
autoFollowPatternId: PropTypes.string,
}
static getDerivedStateFromProps({ autoFollowPatternId }, { lastAutoFollowPatternId }) {
if (lastAutoFollowPatternId !== autoFollowPatternId) {
return { lastAutoFollowPatternId: autoFollowPatternId };
}
return null;
}
static getDerivedStateFromProps({ autoFollowPatternId }, { lastAutoFollowPatternId }) {
if (lastAutoFollowPatternId !== autoFollowPatternId) {
return { lastAutoFollowPatternId: autoFollowPatternId };
}
return null;
}
state = { lastAutoFollowPatternId: undefined }
state = { lastAutoFollowPatternId: undefined }
componentDidMount() {
const { match: { params: { id } }, selectAutoFollowPattern } = this.props;
const decodedId = decodeURIComponent(id);
componentDidMount() {
const { match: { params: { id } }, selectAutoFollowPattern } = this.props;
const decodedId = decodeURIComponent(id);
selectAutoFollowPattern(decodedId);
selectAutoFollowPattern(decodedId);
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, editBreadcrumb ]);
}
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, editBreadcrumb ]);
}
componentDidUpdate(prevProps, prevState) {
const { autoFollowPattern, getAutoFollowPattern } = this.props;
// Fetch the auto-follow pattern on the server if we don't have it (i.e. page reload)
if (!autoFollowPattern && prevState.lastAutoFollowPatternId !== this.state.lastAutoFollowPatternId) {
getAutoFollowPattern(this.state.lastAutoFollowPatternId);
}
}
componentWillUnmount() {
this.props.clearApiError();
}
renderGetAutoFollowPatternError(error) {
const { intl, match: { params: { id: name } } } = this.props;
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorTitle',
defaultMessage: 'Error loading auto-follow pattern',
});
const errorMessage = error.status === 404 ? {
data: {
error: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorMessage',
defaultMessage: `The auto-follow pattern '{name}' does not exist.`,
}, { name })
}
} : error;
return (
<Fragment>
<SectionError title={title} error={errorMessage} />
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
{...routing.getRouterLinkProps('/auto_follow_patterns')}
iconType="arrowLeft"
flush="left"
>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.viewAutoFollowPatternsButtonLabel"
defaultMessage="View auto-follow patterns"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
}
renderLoadingAutoFollowPattern() {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.loadingTitle"
defaultMessage="Loading auto-follow pattern..."
/>
</SectionLoading>
);
}
render() {
const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, match: { url: currentUrl } } = this.props;
return (
<EuiPageContent
horizontalPosition="center"
className="ccrPageContent"
>
<AutoFollowPatternPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPattern.editTitle"
defaultMessage="Edit auto-follow pattern"
/>
)}
/>
{apiStatus.get === API_STATUS.LOADING && this.renderLoadingAutoFollowPattern()}
{apiError.get && this.renderGetAutoFollowPatternError(apiError.get)}
{autoFollowPattern && (
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.loadingRemoteClusters"
defaultMessage="Loading remote clusters..."
/>
</SectionLoading>
);
}
return (
<AutoFollowPatternForm
apiStatus={apiStatus.save}
apiError={apiError.save}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
autoFollowPattern={autoFollowPattern}
saveAutoFollowPattern={saveAutoFollowPattern}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.saveButtonLabel"
defaultMessage="Update"
/>
)}
/>
);
}}
</RemoteClustersProvider>
)}
</EuiPageContent>
);
componentDidUpdate(prevProps, prevState) {
const { autoFollowPattern, getAutoFollowPattern } = this.props;
// Fetch the auto-follow pattern on the server if we don't have it (i.e. page reload)
if (!autoFollowPattern && prevState.lastAutoFollowPatternId !== this.state.lastAutoFollowPatternId) {
getAutoFollowPattern(this.state.lastAutoFollowPatternId);
}
}
);
componentWillUnmount() {
this.props.clearApiError();
}
renderGetAutoFollowPatternError(error) {
const { match: { params: { id: name } } } = this.props;
const title = i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorTitle',
{
defaultMessage: 'Error loading auto-follow pattern'
}
);
const errorMessage = error.status === 404 ? {
data: {
error: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorMessage',
{
defaultMessage: `The auto-follow pattern '{name}' does not exist.`,
values: { name }
}
)
}
} : error;
return (
<Fragment>
<SectionError title={title} error={errorMessage} />
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
{...routing.getRouterLinkProps('/auto_follow_patterns')}
iconType="arrowLeft"
flush="left"
>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.viewAutoFollowPatternsButtonLabel"
defaultMessage="View auto-follow patterns"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
}
renderLoadingAutoFollowPattern() {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.loadingTitle"
defaultMessage="Loading auto-follow pattern…"
/>
</SectionLoading>
);
}
render() {
const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, match: { url: currentUrl } } = this.props;
return (
<EuiPageContent
horizontalPosition="center"
className="ccrPageContent"
>
<AutoFollowPatternPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPattern.editTitle"
defaultMessage="Edit auto-follow pattern"
/>
)}
/>
{apiStatus.get === API_STATUS.LOADING && this.renderLoadingAutoFollowPattern()}
{apiError.get && this.renderGetAutoFollowPatternError(apiError.get)}
{autoFollowPattern && (
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.loadingRemoteClustersMessage"
defaultMessage="Loading remote clusters…"
/>
</SectionLoading>
);
}
return (
<AutoFollowPatternForm
apiStatus={apiStatus.save}
apiError={apiError.save}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
autoFollowPattern={autoFollowPattern}
saveAutoFollowPattern={saveAutoFollowPattern}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternEditForm.saveButtonLabel"
defaultMessage="Update"
/>
)}
/>
);
}}
</RemoteClustersProvider>
)}
</EuiPageContent>
);
}
}

View file

@ -6,7 +6,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
@ -22,73 +22,71 @@ import {
SectionLoading,
} from '../../components';
export const FollowerIndexAdd = injectI18n(
class extends PureComponent {
static propTypes = {
saveFollowerIndex: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object,
apiStatus: PropTypes.string.isRequired,
}
componentDidMount() {
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, addBreadcrumb ]);
}
componentWillUnmount() {
this.props.clearApiError();
}
render() {
const { saveFollowerIndex, clearApiError, apiStatus, apiError, match: { url: currentUrl } } = this.props;
return (
<EuiPageContent
horizontalPosition="center"
className="ccrPageContent"
>
<FollowerIndexPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndex.addTitle"
defaultMessage="Add follower index"
/>
)}
/>
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexCreateForm.loadingRemoteClusters"
defaultMessage="Loading remote clusters..."
/>
</SectionLoading>
);
}
return (
<FollowerIndexForm
apiStatus={apiStatus}
apiError={apiError}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
saveFollowerIndex={saveFollowerIndex}
clearApiError={clearApiError}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexCreateForm.saveButtonLabel"
defaultMessage="Create"
/>
)}
/>
);
}}
</RemoteClustersProvider>
</EuiPageContent>
);
}
export class FollowerIndexAdd extends PureComponent {
static propTypes = {
saveFollowerIndex: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object,
apiStatus: PropTypes.string.isRequired,
}
);
componentDidMount() {
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, addBreadcrumb ]);
}
componentWillUnmount() {
this.props.clearApiError();
}
render() {
const { saveFollowerIndex, clearApiError, apiStatus, apiError, match: { url: currentUrl } } = this.props;
return (
<EuiPageContent
horizontalPosition="center"
className="ccrPageContent"
>
<FollowerIndexPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndex.addTitle"
defaultMessage="Add follower index"
/>
)}
/>
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexCreateForm.loadingRemoteClustersMessage"
defaultMessage="Loading remote clusters…"
/>
</SectionLoading>
);
}
return (
<FollowerIndexForm
apiStatus={apiStatus}
apiError={apiError}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
saveFollowerIndex={saveFollowerIndex}
clearApiError={clearApiError}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexCreateForm.saveButtonLabel"
defaultMessage="Create"
/>
)}
/>
);
}}
</RemoteClustersProvider>
</EuiPageContent>
);
}
}

View file

@ -6,7 +6,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
@ -31,245 +32,250 @@ import {
} from '../../components';
import { API_STATUS } from '../../constants';
export const FollowerIndexEdit = injectI18n(
class extends PureComponent {
static propTypes = {
getFollowerIndex: PropTypes.func.isRequired,
selectFollowerIndex: PropTypes.func.isRequired,
saveFollowerIndex: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object.isRequired,
apiStatus: PropTypes.object.isRequired,
followerIndex: PropTypes.object,
followerIndexId: PropTypes.string,
export class FollowerIndexEdit extends PureComponent {
static propTypes = {
getFollowerIndex: PropTypes.func.isRequired,
selectFollowerIndex: PropTypes.func.isRequired,
saveFollowerIndex: PropTypes.func.isRequired,
clearApiError: PropTypes.func.isRequired,
apiError: PropTypes.object.isRequired,
apiStatus: PropTypes.object.isRequired,
followerIndex: PropTypes.object,
followerIndexId: PropTypes.string,
}
static getDerivedStateFromProps({ followerIndexId }, { lastFollowerIndexId }) {
if (lastFollowerIndexId !== followerIndexId) {
return { lastFollowerIndexId: followerIndexId };
}
return null;
}
state = {
lastFollowerIndexId: undefined,
showConfirmModal: false,
}
componentDidMount() {
const { match: { params: { id } }, selectFollowerIndex } = this.props;
let decodedId;
try {
// When we navigate through the router (history.push) we need to decode both the uri and the id
decodedId = decodeURI(id);
decodedId = decodeURIComponent(decodedId);
} catch (e) {
// This is a page load. I guess that AngularJS router does already a decodeURI so it is not
// necessary in this case.
decodedId = decodeURIComponent(id);
}
static getDerivedStateFromProps({ followerIndexId }, { lastFollowerIndexId }) {
if (lastFollowerIndexId !== followerIndexId) {
return { lastFollowerIndexId: followerIndexId };
}
return null;
selectFollowerIndex(decodedId);
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, editBreadcrumb ]);
}
componentDidUpdate(prevProps, prevState) {
const { followerIndex, getFollowerIndex } = this.props;
// Fetch the follower index on the server if we don't have it (i.e. page reload)
if (!followerIndex && prevState.lastFollowerIndexId !== this.state.lastFollowerIndexId) {
getFollowerIndex(this.state.lastFollowerIndexId);
}
}
state = {
lastFollowerIndexId: undefined,
showConfirmModal: false,
}
componentWillUnmount() {
this.props.clearApiError();
}
componentDidMount() {
const { match: { params: { id } }, selectFollowerIndex } = this.props;
let decodedId;
try {
// When we navigate through the router (history.push) we need to decode both the uri and the id
decodedId = decodeURI(id);
decodedId = decodeURIComponent(decodedId);
} catch (e) {
// This is a page load. I guess that AngularJS router does already a decodeURI so it is not
// necessary in this case.
decodedId = decodeURIComponent(id);
}
saveFollowerIndex = (name, followerIndex) => {
this.editedFollowerIndexPayload = { name, followerIndex };
this.showConfirmModal();
}
selectFollowerIndex(decodedId);
confirmSaveFollowerIhdex = () => {
const { name, followerIndex } = this.editedFollowerIndexPayload;
this.props.saveFollowerIndex(name, followerIndex);
this.closeConfirmModal();
}
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, editBreadcrumb ]);
}
showConfirmModal = () => this.setState({ showConfirmModal: true });
componentDidUpdate(prevProps, prevState) {
const { followerIndex, getFollowerIndex } = this.props;
// Fetch the follower index on the server if we don't have it (i.e. page reload)
if (!followerIndex && prevState.lastFollowerIndexId !== this.state.lastFollowerIndexId) {
getFollowerIndex(this.state.lastFollowerIndexId);
}
}
closeConfirmModal = () => this.setState({ showConfirmModal: false });
componentWillUnmount() {
this.props.clearApiError();
}
renderLoadingFollowerIndex() {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.loadingFollowerIndexTitle"
defaultMessage="Loading follower index…"
/>
</SectionLoading>
);
}
saveFollowerIndex = (name, followerIndex) => {
this.editedFollowerIndexPayload = { name, followerIndex };
this.showConfirmModal();
}
confirmSaveFollowerIhdex = () => {
const { name, followerIndex } = this.editedFollowerIndexPayload;
this.props.saveFollowerIndex(name, followerIndex);
this.closeConfirmModal();
}
showConfirmModal = () => this.setState({ showConfirmModal: true });
closeConfirmModal = () => this.setState({ showConfirmModal: false });
renderLoadingFollowerIndex() {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.loadingFollowerIndexTitle"
defaultMessage="Loading follower index..."
/>
</SectionLoading>
);
}
renderGetFollowerIndexError(error) {
const { intl, match: { params: { id: name } } } = this.props;
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexEditForm.loadingErrorTitle',
defaultMessage: 'Error loading follower index',
});
const errorMessage = error.status === 404 ? {
data: {
error: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexEditForm.loadingErrorMessage',
renderGetFollowerIndexError(error) {
const { match: { params: { id: name } } } = this.props;
const title = i18n.translate('xpack.crossClusterReplication.followerIndexEditForm.loadingErrorTitle', {
defaultMessage: 'Error loading follower index'
});
const errorMessage = error.status === 404 ? {
data: {
error: i18n.translate(
'xpack.crossClusterReplication.followerIndexEditForm.loadingErrorMessage',
{
defaultMessage: `The follower index '{name}' does not exist.`,
}, { name })
}
} : error;
values: { name }
}
)
}
} : error;
return (
<Fragment>
<SectionError title={title} error={errorMessage} />
return (
<Fragment>
<SectionError title={title} error={errorMessage} />
<EuiSpacer size="m" />
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
{...routing.getRouterLinkProps('/follower_indices')}
iconType="arrowLeft"
flush="left"
>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.viewFollowerIndicesButtonLabel"
defaultMessage="View follower indices"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
}
renderConfirmModal = () => {
const { followerIndexId, intl, followerIndex: { isPaused } } = this.props;
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexEditForm.confirmModal.title',
defaultMessage: 'Update follower index \'{id}\'?',
}, { id: followerIndexId });
return (
<EuiOverlayMask>
<EuiConfirmModal
title={title}
onCancel={this.closeConfirmModal}
onConfirm={this.confirmSaveFollowerIhdex}
cancelButtonText={
intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexEditForm.confirmModal.cancelButtonText',
defaultMessage: 'Cancel',
})
}
confirmButtonText={isPaused ? (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
{...routing.getRouterLinkProps('/follower_indices')}
iconType="arrowLeft"
flush="left"
>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.confirmAndResumeButtonText"
defaultMessage="Update and resume"
id="xpack.crossClusterReplication.followerIndexEditForm.viewFollowerIndicesButtonLabel"
defaultMessage="View follower indices"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
}
renderConfirmModal = () => {
const { followerIndexId, followerIndex: { isPaused } } = this.props;
const title = i18n.translate(
'xpack.crossClusterReplication.followerIndexEditForm.confirmModal.title',
{
defaultMessage: `Update follower index '{id}'?`,
values: { id: followerIndexId }
}
);
return (
<EuiOverlayMask>
<EuiConfirmModal
title={title}
onCancel={this.closeConfirmModal}
onConfirm={this.confirmSaveFollowerIhdex}
cancelButtonText={
i18n.translate(
'xpack.crossClusterReplication.followerIndexEditForm.confirmModal.cancelButtonText',
{
defaultMessage: 'Cancel'
}
)
}
confirmButtonText={isPaused ? (
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.confirmAndResumeButtonText"
defaultMessage="Update and resume"
/>
) : (
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.confirmButtonText"
defaultMessage="Update"
/>
)}
>
<p>
{isPaused ? (
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.resumeDescription"
defaultMessage="Updating a follower index resumes replication of its leader index."
/>
) : (
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.confirmButtonText"
defaultMessage="Update"
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.description"
defaultMessage="The follower index is paused, then resumed. If the update fails,
try manually resuming replication."
/>
)}
>
<p>
{isPaused ? (
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.resumeDescription"
defaultMessage="Updating a follower index resumes replication of its leader index."
/>
) : (
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.confirmModal.description"
defaultMessage="The follower index is paused, then resumed. If the update fails,
try manually resuming replication."
/>
)}
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);
}
render() {
const {
clearApiError,
apiStatus,
apiError,
followerIndex,
match: { url: currentUrl }
} = this.props;
const { showConfirmModal } = this.state;
/* remove non-editable properties */
const { shards, ...rest } = followerIndex || {}; // eslint-disable-line no-unused-vars
return (
<EuiPageContent
horizontalPosition="center"
className="ccrPageContent"
>
<FollowerIndexPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndex.editTitle"
defaultMessage="Edit follower index"
/>
)}
/>
{apiStatus.get === API_STATUS.LOADING && this.renderLoadingFollowerIndex()}
{apiError.get && this.renderGetFollowerIndexError(apiError.get)}
{ followerIndex && (
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexCreateForm.loadingRemoteClusters"
defaultMessage="Loading remote clusters..."
/>
</SectionLoading>
);
}
return (
<FollowerIndexForm
followerIndex={rest}
apiStatus={apiStatus.save}
apiError={apiError.save}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
saveFollowerIndex={this.saveFollowerIndex}
clearApiError={clearApiError}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.saveButtonLabel"
defaultMessage="Update"
/>
)}
/>
);
}}
</RemoteClustersProvider>
) }
{ showConfirmModal && this.renderConfirmModal() }
</EuiPageContent>
);
}
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);
}
);
render() {
const {
clearApiError,
apiStatus,
apiError,
followerIndex,
match: { url: currentUrl }
} = this.props;
const { showConfirmModal } = this.state;
/* remove non-editable properties */
const { shards, ...rest } = followerIndex || {}; // eslint-disable-line no-unused-vars
return (
<EuiPageContent
horizontalPosition="center"
className="ccrPageContent"
>
<FollowerIndexPageTitle
title={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndex.editTitle"
defaultMessage="Edit follower index"
/>
)}
/>
{apiStatus.get === API_STATUS.LOADING && this.renderLoadingFollowerIndex()}
{apiError.get && this.renderGetFollowerIndexError(apiError.get)}
{ followerIndex && (
<RemoteClustersProvider>
{({ isLoading, error, remoteClusters }) => {
if (isLoading) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.loadingRemoteClustersMessage"
defaultMessage="Loading remote clusters…"
/>
</SectionLoading>
);
}
return (
<FollowerIndexForm
followerIndex={rest}
apiStatus={apiStatus.save}
apiError={apiError.save}
currentUrl={currentUrl}
remoteClusters={error ? [] : remoteClusters}
saveFollowerIndex={this.saveFollowerIndex}
clearApiError={clearApiError}
saveButtonLabel={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexEditForm.saveButtonLabel"
defaultMessage="Update"
/>
)}
/>
);
}}
</RemoteClustersProvider>
) }
{ showConfirmModal && this.renderConfirmModal() }
</EuiPageContent>
);
}
}

View file

@ -6,7 +6,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
EuiEmptyPrompt,
@ -29,228 +30,226 @@ const getQueryParamPattern = ({ location: { search } }) => {
return pattern ? decodeURIComponent(pattern) : null;
};
export const AutoFollowPatternList = injectI18n(
class extends PureComponent {
static propTypes = {
loadAutoFollowPatterns: PropTypes.func,
selectAutoFollowPattern: PropTypes.func,
loadAutoFollowStats: PropTypes.func,
autoFollowPatterns: PropTypes.array,
apiStatus: PropTypes.string,
apiError: PropTypes.object,
export class AutoFollowPatternList extends PureComponent {
static propTypes = {
loadAutoFollowPatterns: PropTypes.func,
selectAutoFollowPattern: PropTypes.func,
loadAutoFollowStats: PropTypes.func,
autoFollowPatterns: PropTypes.array,
apiStatus: PropTypes.string,
apiError: PropTypes.object,
}
static getDerivedStateFromProps({ autoFollowPatternId }, { lastAutoFollowPatternId }) {
if (autoFollowPatternId !== lastAutoFollowPatternId) {
return {
lastAutoFollowPatternId: autoFollowPatternId,
isDetailPanelOpen: !!autoFollowPatternId,
};
}
return null;
}
static getDerivedStateFromProps({ autoFollowPatternId }, { lastAutoFollowPatternId }) {
if (autoFollowPatternId !== lastAutoFollowPatternId) {
return {
lastAutoFollowPatternId: autoFollowPatternId,
isDetailPanelOpen: !!autoFollowPatternId,
};
}
return null;
}
state = {
lastAutoFollowPatternId: null,
isDetailPanelOpen: false,
};
state = {
lastAutoFollowPatternId: null,
isDetailPanelOpen: false,
};
componentDidMount() {
const { loadAutoFollowPatterns, loadAutoFollowStats, selectAutoFollowPattern, history } = this.props;
componentDidMount() {
const { loadAutoFollowPatterns, loadAutoFollowStats, selectAutoFollowPattern, history } = this.props;
loadAutoFollowPatterns();
loadAutoFollowStats();
loadAutoFollowPatterns();
loadAutoFollowStats();
// Select the pattern in the URL query params
selectAutoFollowPattern(getQueryParamPattern(history));
// Select the pattern in the URL query params
selectAutoFollowPattern(getQueryParamPattern(history));
// Interval to load auto-follow patterns in the background passing "true" to the fetch method
this.interval = setInterval(() => loadAutoFollowPatterns(true), REFRESH_RATE_MS);
}
// Interval to load auto-follow patterns in the background passing "true" to the fetch method
this.interval = setInterval(() => loadAutoFollowPatterns(true), REFRESH_RATE_MS);
}
componentDidUpdate(prevProps, prevState) {
const { history, loadAutoFollowStats } = this.props;
const { lastAutoFollowPatternId } = this.state;
componentDidUpdate(prevProps, prevState) {
const { history, loadAutoFollowStats } = this.props;
const { lastAutoFollowPatternId } = this.state;
/**
* Each time our state is updated (through getDerivedStateFromProps())
* we persist the auto-follow pattern id to query params for deep linking
*/
if (lastAutoFollowPatternId !== prevState.lastAutoFollowPatternId) {
if(!lastAutoFollowPatternId) {
history.replace({
search: '',
});
} else {
history.replace({
search: `?pattern=${encodeURIComponent(lastAutoFollowPatternId)}`,
});
/**
* Each time our state is updated (through getDerivedStateFromProps())
* we persist the auto-follow pattern id to query params for deep linking
*/
if (lastAutoFollowPatternId !== prevState.lastAutoFollowPatternId) {
if(!lastAutoFollowPatternId) {
history.replace({
search: '',
});
} else {
history.replace({
search: `?pattern=${encodeURIComponent(lastAutoFollowPatternId)}`,
});
loadAutoFollowStats();
}
loadAutoFollowStats();
}
}
}
componentWillUnmount() {
clearInterval(this.interval);
componentWillUnmount() {
clearInterval(this.interval);
}
renderHeader() {
const { isAuthorized } = this.props;
return (
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
<EuiFlexItem grow={false}>
<EuiText>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsDescription"
defaultMessage="An auto-follow pattern replicates leader indices from a remote
cluster and copies them to follower indices on the local cluster."
/>
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{isAuthorized && (
<EuiButton
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.addAutoFollowPatternButtonLabel"
defaultMessage="Create an auto-follow pattern"
/>
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
</Fragment>
);
}
renderContent(isEmpty) {
const { apiError, isAuthorized } = this.props;
if (!isAuthorized) {
return (
<SectionUnauthorized
title={(
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.permissionErrorTitle"
defaultMessage="Permission error"
/>
)}
>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.noPermissionText"
defaultMessage="You do not have permission to view or add auto-follow patterns."
/>
</SectionUnauthorized>
);
}
renderHeader() {
const { isAuthorized } = this.props;
if (apiError) {
const title = i18n.translate('xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle', {
defaultMessage: 'Error loading auto-follow patterns'
});
return (
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
<EuiFlexItem grow={false}>
<EuiText>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsDescription"
defaultMessage="An auto-follow pattern replicates leader indices from a remote
cluster and copies them to follower indices on the local cluster."
/>
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{isAuthorized && (
<EuiButton
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.addAutoFollowPatternButtonLabel"
defaultMessage="Create an auto-follow pattern"
/>
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
<SectionError title={title} error={apiError} />
<EuiSpacer size="m" />
</Fragment>
);
}
renderContent(isEmpty) {
const { apiError, isAuthorized, intl } = this.props;
if (!isAuthorized) {
return (
<SectionUnauthorized
title={(
if (isEmpty) {
return this.renderEmpty();
}
return this.renderList();
}
renderEmpty() {
return (
<EuiEmptyPrompt
iconType="managementApp"
title={(
<h1>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.emptyPromptTitle"
defaultMessage="Create your first auto-follow pattern"
/>
</h1>
)}
body={
<Fragment>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.permissionErrorTitle"
defaultMessage="Permission error"
id="xpack.crossClusterReplication.autoFollowPatternList.emptyPromptDescription"
defaultMessage="Use an auto-follow pattern to automatically replicate indices from
a remote cluster."
/>
)}
</p>
</Fragment>
}
actions={
<EuiButton
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.noPermissionText"
defaultMessage="You do not have permission to view or add auto-follow patterns."
id="xpack.crossClusterReplication.addAutoFollowPatternButtonLabel"
defaultMessage="Create auto-follow pattern"
/>
</SectionUnauthorized>
);
}
if (apiError) {
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle',
defaultMessage: 'Error loading auto-follow patterns',
});
return (
<Fragment>
<SectionError title={title} error={apiError} />
<EuiSpacer size="m" />
</Fragment>
);
}
if (isEmpty) {
return this.renderEmpty();
}
return this.renderList();
}
renderEmpty() {
return (
<EuiEmptyPrompt
iconType="managementApp"
title={(
<h1>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.emptyPromptTitle"
defaultMessage="Create your first auto-follow pattern"
/>
</h1>
)}
body={
<Fragment>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.emptyPromptDescription"
defaultMessage="Use an auto-follow pattern to automatically replicate indices from
a remote cluster."
/>
</p>
</Fragment>
}
actions={
<EuiButton
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.addAutoFollowPatternButtonLabel"
defaultMessage="Create auto-follow pattern"
/>
</EuiButton>
}
/>
);
}
renderList() {
const {
selectAutoFollowPattern,
autoFollowPatterns,
apiStatus,
} = this.props;
const { isDetailPanelOpen } = this.state;
if (apiStatus === API_STATUS.LOADING) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.loadingTitle"
defaultMessage="Loading auto-follow patterns..."
/>
</SectionLoading>
);
}
return (
<Fragment>
<AutoFollowPatternTable autoFollowPatterns={autoFollowPatterns} />
{isDetailPanelOpen && <DetailPanel closeDetailPanel={() => selectAutoFollowPattern(null)} />}
</Fragment>
);
}
render() {
const { autoFollowPatterns, apiStatus, } = this.props;
const isEmpty = apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length;
return (
<Fragment>
{!isEmpty && this.renderHeader()}
{this.renderContent(isEmpty)}
</Fragment>
);
}
</EuiButton>
}
/>
);
}
);
renderList() {
const {
selectAutoFollowPattern,
autoFollowPatterns,
apiStatus,
} = this.props;
const { isDetailPanelOpen } = this.state;
if (apiStatus === API_STATUS.LOADING) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.loadingTitle"
defaultMessage="Loading auto-follow patterns..."
/>
</SectionLoading>
);
}
return (
<Fragment>
<AutoFollowPatternTable autoFollowPatterns={autoFollowPatterns} />
{isDetailPanelOpen && <DetailPanel closeDetailPanel={() => selectAutoFollowPattern(null)} />}
</Fragment>
);
}
render() {
const { autoFollowPatterns, apiStatus, } = this.props;
const isEmpty = apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length;
return (
<Fragment>
{!isEmpty && this.renderHeader()}
{this.renderContent(isEmpty)}
</Fragment>
);
}
}

View file

@ -7,7 +7,7 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
EuiButtonIcon,
@ -21,227 +21,237 @@ import { API_STATUS } from '../../../../../constants';
import { AutoFollowPatternDeleteProvider } from '../../../../../components';
import routing from '../../../../../services/routing';
export const AutoFollowPatternTable = injectI18n(
class extends PureComponent {
static propTypes = {
autoFollowPatterns: PropTypes.array,
selectAutoFollowPattern: PropTypes.func.isRequired,
}
export class AutoFollowPatternTable extends PureComponent {
static propTypes = {
autoFollowPatterns: PropTypes.array,
selectAutoFollowPattern: PropTypes.func.isRequired,
}
state = {
selectedItems: [],
}
state = {
selectedItems: [],
}
onSearch = ({ query }) => {
const { text } = query;
const normalizedSearchText = text.toLowerCase();
this.setState({
queryText: normalizedSearchText,
onSearch = ({ query }) => {
const { text } = query;
const normalizedSearchText = text.toLowerCase();
this.setState({
queryText: normalizedSearchText,
});
};
getFilteredPatterns = () => {
const { autoFollowPatterns } = this.props;
const { queryText } = this.state;
if(queryText) {
return autoFollowPatterns.filter(autoFollowPattern => {
const { name, remoteCluster, followIndexPatternPrefix, followIndexPatternSuffix } = autoFollowPattern;
const inName = name.toLowerCase().includes(queryText);
const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText);
const inPrefix = followIndexPatternPrefix.toLowerCase().includes(queryText);
const inSuffix = followIndexPatternSuffix.toLowerCase().includes(queryText);
return inName || inRemoteCluster || inPrefix || inSuffix;
});
};
getFilteredPatterns = () => {
const { autoFollowPatterns } = this.props;
const { queryText } = this.state;
if(queryText) {
return autoFollowPatterns.filter(autoFollowPattern => {
const { name, remoteCluster, followIndexPatternPrefix, followIndexPatternSuffix } = autoFollowPattern;
const inName = name.toLowerCase().includes(queryText);
const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText);
const inPrefix = followIndexPatternPrefix.toLowerCase().includes(queryText);
const inSuffix = followIndexPatternSuffix.toLowerCase().includes(queryText);
return inName || inRemoteCluster || inPrefix || inSuffix;
});
}
return autoFollowPatterns.slice(0);
};
getTableColumns() {
const { intl, selectAutoFollowPattern } = this.props;
return [{
field: 'name',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.table.nameColumnTitle',
defaultMessage: 'Name',
}),
sortable: true,
truncateText: false,
render: (name) => {
return (
<EuiLink onClick={() => selectAutoFollowPattern(name)}>
{name}
</EuiLink>
);
}
}, {
field: 'remoteCluster',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.table.clusterColumnTitle',
defaultMessage: 'Remote cluster',
}),
truncateText: true,
sortable: true,
}, {
field: 'leaderIndexPatterns',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.table.leaderPatternsColumnTitle',
defaultMessage: 'Leader patterns',
}),
render: (leaderPatterns) => leaderPatterns.join(', '),
}, {
field: 'followIndexPatternPrefix',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.table.prefixColumnTitle',
defaultMessage: 'Follower index prefix',
}),
sortable: true,
}, {
field: 'followIndexPatternSuffix',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.table.suffixColumnTitle',
defaultMessage: 'Follower index suffix',
}),
sortable: true,
}, {
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.autoFollowPatternList.table.actionsColumnTitle',
defaultMessage: 'Actions',
}),
actions: [
{
render: ({ name }) => {
const label = i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription',
{
defaultMessage: 'Delete auto-follow pattern',
}
);
return (
<EuiToolTip
content={label}
delay="long"
>
<AutoFollowPatternDeleteProvider>
{(deleteAutoFollowPattern) => (
<EuiButtonIcon
aria-label={label}
iconType="trash"
color="danger"
onClick={() => deleteAutoFollowPattern(name)}
/>
)}
</AutoFollowPatternDeleteProvider>
</EuiToolTip>
);
},
},
{
render: ({ name }) => {
const label = i18n.translate('xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', {
defaultMessage: 'Edit auto-follow pattern',
});
return (
<EuiToolTip
content={label}
delay="long"
>
<EuiButtonIcon
aria-label={label}
iconType="pencil"
color="primary"
href={routing.getAutoFollowPatternPath(name)}
/>
</EuiToolTip>
);
},
},
],
width: '100px',
}];
}
renderLoading = () => {
const { apiStatusDelete } = this.props;
return autoFollowPatterns.slice(0);
};
if (apiStatusDelete === API_STATUS.DELETING) {
getTableColumns() {
const { selectAutoFollowPattern } = this.props;
return [{
field: 'name',
name: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.nameColumnTitle',
{
defaultMessage: 'Name'
}
),
sortable: true,
truncateText: false,
render: (name) => {
return (
<EuiOverlayMask>
<EuiLoadingKibana size="xl"/>
</EuiOverlayMask>
<EuiLink onClick={() => selectAutoFollowPattern(name)}>
{name}
</EuiLink>
);
}
return null;
};
render() {
const {
selectedItems,
} = this.state;
const sorting = {
sort: {
field: 'name',
direction: 'asc',
}, {
field: 'remoteCluster',
name: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.clusterColumnTitle',
{
defaultMessage: 'Remote cluster'
}
};
),
truncateText: true,
sortable: true,
}, {
field: 'leaderIndexPatterns',
name: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.leaderPatternsColumnTitle',
{
defaultMessage: 'Leader patterns'
}
),
render: (leaderPatterns) => leaderPatterns.join(', '),
}, {
field: 'followIndexPatternPrefix',
name: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.prefixColumnTitle',
{
defaultMessage: 'Follower index prefix'
}
),
sortable: true,
}, {
field: 'followIndexPatternSuffix',
name: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.suffixColumnTitle',
{
defaultMessage: 'Follower index suffix'
}
),
sortable: true,
}, {
name: i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.actionsColumnTitle',
{
defaultMessage: 'Actions'
}
),
actions: [
{
render: ({ name }) => {
const label = i18n.translate(
'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription',
{
defaultMessage: 'Delete auto-follow pattern',
}
);
const pagination = {
initialPageSize: 20,
pageSizeOptions: [10, 20, 50]
};
const selection = {
onSelectionChange: (selectedItems) => this.setState({ selectedItems })
};
const search = {
toolsLeft: selectedItems.length ? (
<AutoFollowPatternDeleteProvider>
{(deleteAutoFollowPattern) => (
<EuiButton
iconType="trash"
color="danger"
onClick={() => deleteAutoFollowPattern(selectedItems.map(({ name }) => name))}
return (
<EuiToolTip
content={label}
delay="long"
>
<FormattedMessage
id="xpack.crossClusterReplication.deleteAutoFollowPatternButtonLabel"
defaultMessage="Delete auto-follow {total, plural, one {pattern} other {patterns}}"
values={{
total: selectedItems.length
}}
/>
</EuiButton>
)}
</AutoFollowPatternDeleteProvider>
) : undefined,
onChange: this.onSearch,
box: {
incremental: true,
<AutoFollowPatternDeleteProvider>
{(deleteAutoFollowPattern) => (
<EuiButtonIcon
aria-label={label}
iconType="trash"
color="danger"
onClick={() => deleteAutoFollowPattern(name)}
/>
)}
</AutoFollowPatternDeleteProvider>
</EuiToolTip>
);
},
},
};
{
render: ({ name }) => {
const label = i18n.translate('xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', {
defaultMessage: 'Edit auto-follow pattern',
});
return (
<EuiToolTip
content={label}
delay="long"
>
<EuiButtonIcon
aria-label={label}
iconType="pencil"
color="primary"
href={routing.getAutoFollowPatternPath(name)}
/>
</EuiToolTip>
);
},
},
],
width: '100px',
}];
}
renderLoading = () => {
const { apiStatusDelete } = this.props;
if (apiStatusDelete === API_STATUS.DELETING) {
return (
<Fragment>
<EuiInMemoryTable
items={this.getFilteredPatterns()}
itemId="name"
columns={this.getTableColumns()}
search={search}
pagination={pagination}
sorting={sorting}
selection={selection}
isSelectable={true}
/>
{this.renderLoading()}
</Fragment>
<EuiOverlayMask>
<EuiLoadingKibana size="xl"/>
</EuiOverlayMask>
);
}
return null;
};
render() {
const {
selectedItems,
} = this.state;
const sorting = {
sort: {
field: 'name',
direction: 'asc',
}
};
const pagination = {
initialPageSize: 20,
pageSizeOptions: [10, 20, 50]
};
const selection = {
onSelectionChange: (selectedItems) => this.setState({ selectedItems })
};
const search = {
toolsLeft: selectedItems.length ? (
<AutoFollowPatternDeleteProvider>
{(deleteAutoFollowPattern) => (
<EuiButton
iconType="trash"
color="danger"
onClick={() => deleteAutoFollowPattern(selectedItems.map(({ name }) => name))}
>
<FormattedMessage
id="xpack.crossClusterReplication.deleteAutoFollowPatternButtonLabel"
defaultMessage="Delete auto-follow {total, plural, one {pattern} other {patterns}}"
values={{
total: selectedItems.length
}}
/>
</EuiButton>
)}
</AutoFollowPatternDeleteProvider>
) : undefined,
onChange: this.onSearch,
box: {
incremental: true,
},
};
return (
<Fragment>
<EuiInMemoryTable
items={this.getFilteredPatterns()}
itemId="name"
columns={this.getTableColumns()}
search={search}
pagination={pagination}
sorting={sorting}
selection={selection}
isSelectable={true}
/>
{this.renderLoading()}
</Fragment>
);
}
);
}

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import { getIndexListUri } from '../../../../../../../../index_management/public/services/navigation';
@ -39,7 +39,7 @@ import {
import { API_STATUS } from '../../../../../constants';
import routing from '../../../../../services/routing';
export class DetailPanelUi extends Component {
export class DetailPanel extends Component {
static propTypes = {
apiStatus: PropTypes.string,
autoFollowPatternId: PropTypes.string,
@ -250,7 +250,7 @@ export class DetailPanelUi extends Component {
<EuiTextColor color="subdued">
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternDetailPanel.loadingLabel"
defaultMessage="Loading auto-follow pattern..."
defaultMessage="Loading auto-follow pattern"
/>
</EuiTextColor>
</EuiText>
@ -375,5 +375,3 @@ export class DetailPanelUi extends Component {
);
}
}
export const DetailPanel = injectI18n(DetailPanelUi);

View file

@ -5,7 +5,7 @@
*/
import React, { PureComponent, Fragment } from 'react';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import PropTypes from 'prop-types';
import {
EuiButton,
@ -22,8 +22,7 @@ import {
FollowerIndexUnfollowProvider
} from '../../../../../components';
export class ContextMenuUi extends PureComponent {
export class ContextMenu extends PureComponent {
static propTypes = {
iconSide: PropTypes.string,
iconType: PropTypes.string,
@ -174,5 +173,3 @@ export class ContextMenuUi extends PureComponent {
);
}
}
export const ContextMenu = injectI18n(ContextMenuUi);

View file

@ -6,7 +6,7 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import { getIndexListUri } from '../../../../../../../../index_management/public/services/navigation';
import {
@ -38,7 +38,7 @@ import { ContextMenu } from '../context_menu';
import { API_STATUS } from '../../../../../constants';
export class DetailPanelUi extends Component {
export class DetailPanel extends Component {
static propTypes = {
apiStatus: PropTypes.string,
followerIndexId: PropTypes.string,
@ -391,7 +391,7 @@ export class DetailPanelUi extends Component {
<EuiTextColor color="subdued">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexDetailPanel.loadingLabel"
defaultMessage="Loading follower index..."
defaultMessage="Loading follower index"
/>
</EuiTextColor>
</EuiText>
@ -518,5 +518,3 @@ export class DetailPanelUi extends Component {
);
}
}
export const DetailPanel = injectI18n(DetailPanelUi);

View file

@ -6,7 +6,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiHealth,
EuiIcon,
@ -24,269 +25,281 @@ import {
import routing from '../../../../../services/routing';
import { ContextMenu } from '../context_menu';
export const FollowerIndicesTable = injectI18n(
class extends PureComponent {
static propTypes = {
followerIndices: PropTypes.array,
selectFollowerIndex: PropTypes.func.isRequired,
}
export class FollowerIndicesTable extends PureComponent {
static propTypes = {
followerIndices: PropTypes.array,
selectFollowerIndex: PropTypes.func.isRequired,
}
state = {
selectedItems: [],
}
state = {
selectedItems: [],
}
onSearch = ({ query }) => {
const { text } = query;
const normalizedSearchText = text.toLowerCase();
this.setState({
queryText: normalizedSearchText,
onSearch = ({ query }) => {
const { text } = query;
const normalizedSearchText = text.toLowerCase();
this.setState({
queryText: normalizedSearchText,
});
};
editFollowerIndex = (id) => {
const uri = routing.getFollowerIndexPath(id, '/edit', false);
routing.navigate(uri);
}
getFilteredIndices = () => {
const { followerIndices } = this.props;
const { queryText } = this.state;
if(queryText) {
return followerIndices.filter(followerIndex => {
const { name, shards } = followerIndex;
const inName = name.toLowerCase().includes(queryText);
const inRemoteCluster = shards[0].remoteCluster.toLowerCase().includes(queryText);
const inLeaderIndex = shards[0].leaderIndex.toLowerCase().includes(queryText);
return inName || inRemoteCluster || inLeaderIndex;
});
};
editFollowerIndex = (id) => {
const uri = routing.getFollowerIndexPath(id, '/edit', false);
routing.navigate(uri);
}
getFilteredIndices = () => {
const { followerIndices } = this.props;
const { queryText } = this.state;
return followerIndices.slice(0);
};
if(queryText) {
return followerIndices.filter(followerIndex => {
const { name, shards } = followerIndex;
getTableColumns() {
const { selectFollowerIndex } = this.props;
const inName = name.toLowerCase().includes(queryText);
const inRemoteCluster = shards[0].remoteCluster.toLowerCase().includes(queryText);
const inLeaderIndex = shards[0].leaderIndex.toLowerCase().includes(queryText);
return inName || inRemoteCluster || inLeaderIndex;
});
}
return followerIndices.slice(0);
};
getTableColumns() {
const { intl, selectFollowerIndex } = this.props;
const actions = [
/* Pause or resume follower index */
{
render: (followerIndex) => {
const { name, isPaused } = followerIndex;
const label = isPaused
? intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription',
defaultMessage: 'Resume replication',
})
: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription',
defaultMessage: 'Pause replication',
});
return isPaused ? (
<FollowerIndexResumeProvider>
{(resumeFollowerIndex) => (
<span onClick={() => resumeFollowerIndex(name)}>
<EuiIcon
aria-label={label}
type="play"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
)}
</FollowerIndexResumeProvider>
) : (
<FollowerIndexPauseProvider>
{(pauseFollowerIndex) => (
<span onClick={() => pauseFollowerIndex(followerIndex)}>
<EuiIcon
aria-label={label}
type="pause"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
)}
</FollowerIndexPauseProvider>
const actions = [
/* Pause or resume follower index */
{
render: (followerIndex) => {
const { name, isPaused } = followerIndex;
const label = isPaused
? i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription',
{
defaultMessage: 'Resume replication'
}
) : i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription',
{
defaultMessage: 'Pause replication'
}
);
},
},
/* Edit follower index */
{
render: ({ name }) => {
const label = intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription',
defaultMessage: 'Edit follower index',
});
return (
<span onClick={() => this.editFollowerIndex(name)}>
<EuiIcon
aria-label={label}
type="pencil"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
);
},
},
/* Unfollow leader index */
{
render: ({ name }) => {
const label = intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription',
defaultMessage: 'Unfollow leader index',
});
return (
<FollowerIndexUnfollowProvider>
{(unfollowLeaderIndex) => (
<span onClick={() => unfollowLeaderIndex(name)}>
<EuiIcon
aria-label={label}
type="indexFlush"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
)}
</FollowerIndexUnfollowProvider>
);
},
},
];
return [{
field: 'name',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.nameColumnTitle',
defaultMessage: 'Name',
}),
sortable: true,
truncateText: false,
render: (name) => {
return (
<EuiLink onClick={() => selectFollowerIndex(name)}>
{name}
</EuiLink>
);
}
}, {
field: 'isPaused',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.statusColumnTitle',
defaultMessage: 'Status',
}),
truncateText: true,
sortable: true,
render: (isPaused) => {
return isPaused ? (
<EuiHealth color="subdued">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.table.pausedStatus"
defaultMessage="Paused"
/>
</EuiHealth>
<FollowerIndexResumeProvider>
{(resumeFollowerIndex) => (
<span onClick={() => resumeFollowerIndex(name)}>
<EuiIcon
aria-label={label}
type="play"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
)}
</FollowerIndexResumeProvider>
) : (
<EuiHealth color="success">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.table.activeStatus"
defaultMessage="Active"
/>
</EuiHealth>
<FollowerIndexPauseProvider>
{(pauseFollowerIndex) => (
<span onClick={() => pauseFollowerIndex(followerIndex)}>
<EuiIcon
aria-label={label}
type="pause"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
)}
</FollowerIndexPauseProvider>
);
},
},
/* Edit follower index */
{
render: ({ name }) => {
const label = i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription',
{
defaultMessage: 'Edit follower index'
}
);
}
}, {
field: 'remoteCluster',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.clusterColumnTitle',
defaultMessage: 'Remote cluster',
}),
truncateText: true,
sortable: true,
}, {
field: 'leaderIndex',
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.leaderIndexColumnTitle',
defaultMessage: 'Leader index',
}),
truncateText: true,
sortable: true,
}, {
name: intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.table.actionsColumnTitle',
defaultMessage: 'Actions',
}),
actions,
width: '100px',
}];
}
renderLoading = () => {
const { apiStatusDelete } = this.props;
return (
<span onClick={() => this.editFollowerIndex(name)}>
<EuiIcon
aria-label={label}
type="pencil"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
);
},
},
/* Unfollow leader index */
{
render: ({ name }) => {
const label = i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription',
{
defaultMessage: 'Unfollow leader index'
}
);
if (apiStatusDelete === API_STATUS.DELETING) {
return (
<FollowerIndexUnfollowProvider>
{(unfollowLeaderIndex) => (
<span onClick={() => unfollowLeaderIndex(name)}>
<EuiIcon
aria-label={label}
type="indexFlush"
className="euiContextMenu__icon"
/>
<span>{label}</span>
</span>
)}
</FollowerIndexUnfollowProvider>
);
},
},
];
return [{
field: 'name',
name: i18n.translate('xpack.crossClusterReplication.followerIndexList.table.nameColumnTitle', {
defaultMessage: 'Name'
}),
sortable: true,
truncateText: false,
render: (name) => {
return (
<EuiOverlayMask>
<EuiLoadingKibana size="xl"/>
</EuiOverlayMask>
<EuiLink onClick={() => selectFollowerIndex(name)}>
{name}
</EuiLink>
);
}
return null;
};
render() {
const {
selectedItems,
} = this.state;
const sorting = {
sort: {
field: 'name',
direction: 'asc',
}, {
field: 'isPaused',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.statusColumnTitle',
{
defaultMessage: 'Status'
}
};
),
truncateText: true,
sortable: true,
render: (isPaused) => {
return isPaused ? (
<EuiHealth color="subdued">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.table.statusColumn.pausedLabel"
defaultMessage="Paused"
/>
</EuiHealth>
) : (
<EuiHealth color="success">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.table.statusColumn.activeLabel"
defaultMessage="Active"
/>
</EuiHealth>
);
}
}, {
field: 'remoteCluster',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.clusterColumnTitle',
{
defaultMessage: 'Remote cluster'
}
),
truncateText: true,
sortable: true,
}, {
field: 'leaderIndex',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.leaderIndexColumnTitle',
{
defaultMessage: 'Leader index'
}
),
truncateText: true,
sortable: true,
}, {
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionsColumnTitle',
{
defaultMessage: 'Actions'
}
),
actions,
width: '100px',
}];
}
const pagination = {
initialPageSize: 20,
pageSizeOptions: [10, 20, 50]
};
const selection = {
onSelectionChange: (selectedItems) => this.setState({ selectedItems })
};
const search = {
toolsLeft: selectedItems.length ? (
<ContextMenu
followerIndices={selectedItems}
/>
) : undefined,
onChange: this.onSearch,
box: {
incremental: true,
},
};
renderLoading = () => {
const { apiStatusDelete } = this.props;
if (apiStatusDelete === API_STATUS.DELETING) {
return (
<Fragment>
<EuiInMemoryTable
items={this.getFilteredIndices()}
itemId="name"
columns={this.getTableColumns()}
search={search}
pagination={pagination}
sorting={sorting}
selection={selection}
isSelectable={true}
/>
{this.renderLoading()}
</Fragment>
<EuiOverlayMask>
<EuiLoadingKibana size="xl"/>
</EuiOverlayMask>
);
}
return null;
};
render() {
const {
selectedItems,
} = this.state;
const sorting = {
sort: {
field: 'name',
direction: 'asc',
}
};
const pagination = {
initialPageSize: 20,
pageSizeOptions: [10, 20, 50]
};
const selection = {
onSelectionChange: (selectedItems) => this.setState({ selectedItems })
};
const search = {
toolsLeft: selectedItems.length ? (
<ContextMenu
followerIndices={selectedItems}
/>
) : undefined,
onChange: this.onSearch,
box: {
incremental: true,
},
};
return (
<Fragment>
<EuiInMemoryTable
items={this.getFilteredIndices()}
itemId="name"
columns={this.getTableColumns()}
search={search}
pagination={pagination}
sorting={sorting}
selection={selection}
isSelectable={true}
/>
{this.renderLoading()}
</Fragment>
);
}
);
}

View file

@ -6,7 +6,8 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButton,
EuiEmptyPrompt,
@ -29,223 +30,220 @@ const getQueryParamName = ({ location: { search } }) => {
return name ? decodeURIComponent(name) : null;
};
export const FollowerIndicesList = injectI18n(
class extends PureComponent {
static propTypes = {
loadFollowerIndices: PropTypes.func,
selectFollowerIndex: PropTypes.func,
followerIndices: PropTypes.array,
apiStatus: PropTypes.string,
apiError: PropTypes.object,
export class FollowerIndicesList extends PureComponent {
static propTypes = {
loadFollowerIndices: PropTypes.func,
selectFollowerIndex: PropTypes.func,
followerIndices: PropTypes.array,
apiStatus: PropTypes.string,
apiError: PropTypes.object,
}
static getDerivedStateFromProps({ followerIndexId }, { lastFollowerIndexId }) {
if (followerIndexId !== lastFollowerIndexId) {
return {
lastFollowerIndexId: followerIndexId,
isDetailPanelOpen: !!followerIndexId,
};
}
return null;
}
static getDerivedStateFromProps({ followerIndexId }, { lastFollowerIndexId }) {
if (followerIndexId !== lastFollowerIndexId) {
return {
lastFollowerIndexId: followerIndexId,
isDetailPanelOpen: !!followerIndexId,
};
}
return null;
}
state = {
lastFollowerIndexId: null,
isDetailPanelOpen: false,
};
state = {
lastFollowerIndexId: null,
isDetailPanelOpen: false,
};
componentDidMount() {
const { loadFollowerIndices, selectFollowerIndex, history } = this.props;
componentDidMount() {
const { loadFollowerIndices, selectFollowerIndex, history } = this.props;
loadFollowerIndices();
loadFollowerIndices();
// Select the pattern in the URL query params
selectFollowerIndex(getQueryParamName(history));
// Select the pattern in the URL query params
selectFollowerIndex(getQueryParamName(history));
// Interval to load follower indices in the background passing "true" to the fetch method
this.interval = setInterval(() => loadFollowerIndices(true), REFRESH_RATE_MS);
}
// Interval to load follower indices in the background passing "true" to the fetch method
this.interval = setInterval(() => loadFollowerIndices(true), REFRESH_RATE_MS);
}
componentDidUpdate(prevProps, prevState) {
const { history } = this.props;
const { lastFollowerIndexId } = this.state;
componentDidUpdate(prevProps, prevState) {
const { history } = this.props;
const { lastFollowerIndexId } = this.state;
/**
* Each time our state is updated (through getDerivedStateFromProps())
* we persist the follower index id to query params for deep linking
*/
if (lastFollowerIndexId !== prevState.lastFollowerIndexId) {
if(!lastFollowerIndexId) {
history.replace({
search: '',
});
} else {
history.replace({
search: `?name=${encodeURIComponent(lastFollowerIndexId)}`,
});
}
/**
* Each time our state is updated (through getDerivedStateFromProps())
* we persist the follower index id to query params for deep linking
*/
if (lastFollowerIndexId !== prevState.lastFollowerIndexId) {
if(!lastFollowerIndexId) {
history.replace({
search: '',
});
} else {
history.replace({
search: `?name=${encodeURIComponent(lastFollowerIndexId)}`,
});
}
}
}
componentWillUnmount() {
clearInterval(this.interval);
componentWillUnmount() {
clearInterval(this.interval);
}
renderHeader() {
const { isAuthorized } = this.props;
return (
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
<EuiFlexItem grow={false}>
<EuiText>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.followerIndicesDescription"
defaultMessage="A follower index replicates a leader index on a remote cluster."
/>
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{isAuthorized && (
<EuiButton
{...routing.getRouterLinkProps('/follower_indices/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.addFollowerButtonLabel"
defaultMessage="Create a follower index"
/>
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
</Fragment>
);
}
renderContent(isEmpty) {
const { apiError, isAuthorized } = this.props;
if (!isAuthorized) {
return (
<SectionUnauthorized
title={(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.permissionErrorTitle"
defaultMessage="Permission error"
/>
)}
>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.noPermissionText"
defaultMessage="You do not have permission to view or add follower indices."
/>
</SectionUnauthorized>
);
}
renderHeader() {
const { isAuthorized } = this.props;
if (apiError) {
const title = i18n.translate('xpack.crossClusterReplication.followerIndexList.loadingErrorTitle', {
defaultMessage: 'Error loading follower indices'
});
return (
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
<EuiFlexItem grow={false}>
<EuiText>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.followerIndicesDescription"
defaultMessage="A follower index replicates a leader index on a remote cluster."
/>
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{isAuthorized && (
<EuiButton
{...routing.getRouterLinkProps('/follower_indices/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.addFollowerButtonLabel"
defaultMessage="Create a follower index"
/>
</EuiButton>
)}
</EuiFlexItem>
</EuiFlexGroup>
<SectionError title={title} error={apiError} />
<EuiSpacer size="m" />
</Fragment>
);
}
renderContent(isEmpty) {
const { apiError, isAuthorized, intl } = this.props;
if (isEmpty) {
return this.renderEmpty();
}
if (!isAuthorized) {
return (
<SectionUnauthorized
title={(
return this.renderList();
}
renderEmpty() {
return (
<EuiEmptyPrompt
iconType="managementApp"
title={(
<h1>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.emptyPromptTitle"
defaultMessage="Create your first follower index"
/>
</h1>
)}
body={
<Fragment>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.permissionErrorTitle"
defaultMessage="Permission error"
id="xpack.crossClusterReplication.followerIndexList.emptyPromptDescription"
defaultMessage="Use a follower index to replicate a leader index on a remote cluster."
/>
)}
</p>
</Fragment>
}
actions={
<EuiButton
{...routing.getRouterLinkProps('/follower_indices/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.noPermissionText"
defaultMessage="You do not have permission to view or add follower indices."
id="xpack.crossClusterReplication.addFollowerButtonLabel"
defaultMessage="Create a follower index"
/>
</SectionUnauthorized>
);
}
if (apiError) {
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.followerIndexList.loadingErrorTitle',
defaultMessage: 'Error loading follower indices',
});
return (
<Fragment>
<SectionError title={title} error={apiError} />
<EuiSpacer size="m" />
</Fragment>
);
}
if (isEmpty) {
return this.renderEmpty();
}
return this.renderList();
}
renderEmpty() {
return (
<EuiEmptyPrompt
iconType="managementApp"
title={(
<h1>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.emptyPromptTitle"
defaultMessage="Create your first follower index"
/>
</h1>
)}
body={
<Fragment>
<p>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.emptyPromptDescription"
defaultMessage="Use a follower index to replicate a leader index on a remote cluster."
/>
</p>
</Fragment>
}
actions={
<EuiButton
{...routing.getRouterLinkProps('/follower_indices/add')}
fill
iconType="plusInCircle"
>
<FormattedMessage
id="xpack.crossClusterReplication.addFollowerButtonLabel"
defaultMessage="Create a follower index"
/>
</EuiButton>
}
/>
);
}
renderList() {
const {
selectFollowerIndex,
followerIndices,
apiStatus,
} = this.props;
const { isDetailPanelOpen } = this.state;
if (apiStatus === API_STATUS.LOADING) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.loadingTitle"
defaultMessage="Loading follower indices..."
/>
</SectionLoading>
);
}
return (
<Fragment>
<FollowerIndicesTable followerIndices={followerIndices} />
{isDetailPanelOpen && <DetailPanel closeDetailPanel={() => selectFollowerIndex(null)} />}
</Fragment>
);
}
render() {
const { followerIndices, apiStatus } = this.props;
const isEmpty = apiStatus === API_STATUS.IDLE && !followerIndices.length;
return (
<Fragment>
{!isEmpty && this.renderHeader()}
{this.renderContent(isEmpty)}
</Fragment>
);
}
</EuiButton>
}
/>
);
}
);
renderList() {
const {
selectFollowerIndex,
followerIndices,
apiStatus,
} = this.props;
const { isDetailPanelOpen } = this.state;
if (apiStatus === API_STATUS.LOADING) {
return (
<SectionLoading>
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.loadingTitle"
defaultMessage="Loading follower indices..."
/>
</SectionLoading>
);
}
return (
<Fragment>
<FollowerIndicesTable followerIndices={followerIndices} />
{isDetailPanelOpen && <DetailPanel closeDetailPanel={() => selectFollowerIndex(null)} />}
</Fragment>
);
}
render() {
const { followerIndices, apiStatus } = this.props;
const isEmpty = apiStatus === API_STATUS.IDLE && !followerIndices.length;
return (
<Fragment>
{!isEmpty && this.renderHeader()}
{this.renderContent(isEmpty)}
</Fragment>
);
}
}

View file

@ -6,7 +6,7 @@
import React, { PureComponent } from 'react';
import { Route, Switch } from 'react-router-dom';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { FormattedMessage } from '@kbn/i18n/react';
import chrome from 'ui/chrome';
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
@ -25,81 +25,79 @@ import routing from '../../services/routing';
import { AutoFollowPatternList } from './auto_follow_pattern_list';
import { FollowerIndicesList } from './follower_indices_list';
export const CrossClusterReplicationHome = injectI18n(
class extends PureComponent {
state = {
activeSection: 'follower_indices'
}
tabs = [{
id: 'follower_indices',
name: (
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.followerIndicesTitle"
defaultMessage="Follower indices"
/>
)
}, {
id: 'auto_follow_patterns',
name: (
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsTitle"
defaultMessage="Auto-follow patterns"
/>
)
}]
componentDidMount() {
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]);
}
static getDerivedStateFromProps(props) {
const { match: { params: { section } } } = props;
return {
activeSection: section
};
}
onSectionChange = (section) => {
routing.navigate(`/${section}`);
}
render() {
return (
<EuiPageBody>
<EuiPageContent>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.crossClusterReplicationTitle"
defaultMessage="Cross Cluster Replication"
/>
</h1>
</EuiTitle>
<EuiSpacer size="s" />
<EuiTabs>
{this.tabs.map(tab => (
<EuiTab
onClick={() => this.onSectionChange(tab.id)}
isSelected={tab.id === this.state.activeSection}
key={tab.id}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
<EuiSpacer size="m" />
<Switch>
<Route exact path={`${BASE_PATH}/follower_indices`} component={FollowerIndicesList} />
<Route exact path={`${BASE_PATH}/auto_follow_patterns`} component={AutoFollowPatternList} />
</Switch>
</EuiPageContent>
</EuiPageBody>
);
}
export class CrossClusterReplicationHome extends PureComponent {
state = {
activeSection: 'follower_indices'
}
);
tabs = [{
id: 'follower_indices',
name: (
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.followerIndicesTitle"
defaultMessage="Follower indices"
/>
)
}, {
id: 'auto_follow_patterns',
name: (
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsTitle"
defaultMessage="Auto-follow patterns"
/>
)
}]
componentDidMount() {
chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]);
}
static getDerivedStateFromProps(props) {
const { match: { params: { section } } } = props;
return {
activeSection: section
};
}
onSectionChange = (section) => {
routing.navigate(`/${section}`);
}
render() {
return (
<EuiPageBody>
<EuiPageContent>
<EuiTitle size="l">
<h1>
<FormattedMessage
id="xpack.crossClusterReplication.autoFollowPatternList.crossClusterReplicationTitle"
defaultMessage="Cross Cluster Replication"
/>
</h1>
</EuiTitle>
<EuiSpacer size="s" />
<EuiTabs>
{this.tabs.map(tab => (
<EuiTab
onClick={() => this.onSectionChange(tab.id)}
isSelected={tab.id === this.state.activeSection}
key={tab.id}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
<EuiSpacer size="m" />
<Switch>
<Route exact path={`${BASE_PATH}/follower_indices`} component={FollowerIndicesList} />
<Route exact path={`${BASE_PATH}/auto_follow_patterns`} component={AutoFollowPatternList} />
</Switch>
</EuiPageContent>
</EuiPageBody>
);
}
}

View file

@ -30,7 +30,7 @@ export const indexNameValidator = (value) => {
if (isEmpty(value)) {
return [(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexForm.errors.nameMissing"
id="xpack.crossClusterReplication.followerIndexForm.errors.nameMissingMessage"
defaultMessage="Name is required."
/>
)];
@ -39,7 +39,7 @@ export const indexNameValidator = (value) => {
if (beginsWithPeriod(value)) {
return [(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexForm.errors.nameBeginsWithPeriod"
id="xpack.crossClusterReplication.followerIndexForm.errors.nameBeginsWithPeriodMessage"
defaultMessage="Name can't begin with a period."
/>
)];
@ -50,7 +50,7 @@ export const indexNameValidator = (value) => {
if (illegalCharacters.length) {
return [(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexForm.errors.nameIllegalCharacters"
id="xpack.crossClusterReplication.followerIndexForm.errors.nameIllegalCharactersMessage"
defaultMessage="Remove the characters {characterList} from your name."
values={{ characterList: <strong>{illegalCharacters.join(' ')}</strong> }}
/>
@ -64,7 +64,7 @@ export const leaderIndexValidator = (value) => {
if (isEmpty(value)) {
return [(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexForm.errors.leaderIndexMissing"
id="xpack.crossClusterReplication.followerIndexForm.errors.leaderIndexMissingMessage"
defaultMessage="Leader index is required."
/>
)];
@ -75,7 +75,7 @@ export const leaderIndexValidator = (value) => {
if (illegalCharacters.length) {
return [(
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexForm.errors.leaderIndexIllegalCharacters"
id="xpack.crossClusterReplication.followerIndexForm.errors.leaderIndexIllegalCharactersMessage"
defaultMessage="Remove the characters {characterList} from your leader index."
values={{ characterList: <strong>{illegalCharacters.join(' ')}</strong> }}
/>

View file

@ -24,8 +24,8 @@ export function validateSeed(seed) {
errors.push(i18n.translate(
'xpack.remoteClusters.remoteClusterForm.localSeedError.invalidCharactersMessage',
{
defaultMessage: `Seed node must use host:port format. Example: 127.0.0.1:9400, localhost:9400.
Hosts can only consist of letters, numbers, and dashes.`,
defaultMessage: 'Seed node must use host:port format. Example: 127.0.0.1:9400, localhost:9400. ' +
'Hosts can only consist of letters, numbers, and dashes.',
},
));
}

View file

@ -3720,7 +3720,6 @@
"xpack.crossClusterReplication.autoFollowPattern.removeAction.successSingleNotificationTitle": "自动跟随模式 “{name}” 已删除",
"xpack.crossClusterReplication.autoFollowPattern.suffixValidation.illegalCharacters": "从后缀中删除{characterListLength, plural, one {字符} other {字符}} {characterList}。",
"xpack.crossClusterReplication.autoFollowPattern.suffixValidation.noEmptySpace": "后缀中不能使用空格。",
"xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClusters": "正在加载远程集群……",
"xpack.crossClusterReplication.autoFollowPatternDetailPanel.closeButtonLabel": "关闭",
"xpack.crossClusterReplication.autoFollowPatternDetailPanel.deleteButtonLabel": "删除",
"xpack.crossClusterReplication.autoFollowPatternDetailPanel.editButtonLabel": "编辑",
@ -3736,7 +3735,6 @@
"xpack.crossClusterReplication.autoFollowPatternDetailPanel.suffixLabel": "后缀",
"xpack.crossClusterReplication.autoFollowPatternDetailPanel.viewIndicesLink": "在“索引管理”中查看您的 Follower 索引",
"xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorTitle": "加载自动跟随模式时出错",
"xpack.crossClusterReplication.autoFollowPatternEditForm.loadingRemoteClusters": "正在加载远程集群……",
"xpack.crossClusterReplication.autoFollowPatternEditForm.loadingTitle": "正在加载自动跟随模式……",
"xpack.crossClusterReplication.autoFollowPatternEditForm.viewAutoFollowPatternsButtonLabel": "查看自动跟随模式",
"xpack.crossClusterReplication.autoFollowPatternForm.actions.savingText": "正在保存",
@ -3751,7 +3749,6 @@
"xpack.crossClusterReplication.autoFollowPatternForm.indicesPreviewDescription": "上述设置将生成类似下面的索引名称:",
"xpack.crossClusterReplication.autoFollowPatternForm.indicesPreviewTitle": "索引名称示例",
"xpack.crossClusterReplication.autoFollowPatternForm.leaderIndexPatternError.duplicateMessage": "不允许重复的 Leader 索引模式。",
"xpack.crossClusterReplication.autoFollowPatternForm.remoteCluster.fieldClusterLabel": "远程集群",
"xpack.crossClusterReplication.autoFollowPatternForm.savingErrorTitle": "创建自动跟随模式时出错",
"xpack.crossClusterReplication.autoFollowPatternForm.sectionAutoFollowPatternDescription": "应用于 Follower 索引名称的定制前缀或后缀以便您可以更容易辨识复制的索引。默认情况下Follower 索引与 Leader 索引有相同的名称。",
"xpack.crossClusterReplication.autoFollowPatternForm.sectionAutoFollowPatternNameDescription": "自动跟随模式的唯一名称。",