mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
parent
cc6c31cd1c
commit
1d2a6fafd1
25 changed files with 2580 additions and 2541 deletions
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> }}
|
||||
/>
|
||||
|
|
|
@ -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.',
|
||||
},
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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": "自动跟随模式的唯一名称。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue