diff --git a/.i18nrc.json b/.i18nrc.json index 3ced22fa9cd7c..415002de09078 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -17,6 +17,7 @@ "tagCloud": "src/legacy/core_plugins/tagcloud", "tsvb": "src/legacy/core_plugins/metrics", "xpack.beatsManagement": "x-pack/plugins/beats_management", + "xpack.crossClusterReplication": "x-pack/plugins/cross_cluster_replication", "xpack.graph": "x-pack/plugins/graph", "xpack.grokDebugger": "x-pack/plugins/grokdebugger", "xpack.idxMgmt": "x-pack/plugins/index_management", @@ -25,6 +26,7 @@ "xpack.ml": "x-pack/plugins/ml", "xpack.logstash": "x-pack/plugins/logstash", "xpack.monitoring": "x-pack/plugins/monitoring", + "xpack.remoteClusters": "x-pack/plugins/remote_clusters", "xpack.reporting": "x-pack/plugins/reporting", "xpack.rollupJobs": "x-pack/plugins/rollup", "xpack.searchProfiler": "x-pack/plugins/searchprofiler", diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js index fe43c1bb85cbb..af9f37cb16256 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.js @@ -65,618 +65,618 @@ export const updateFormErrors = (errors, existingErrors) => ({ } }); -export class AutoFollowPatternFormUI extends PureComponent { - static propTypes = { - saveAutoFollowPattern: PropTypes.func.isRequired, - autoFollowPattern: PropTypes.object, - apiError: PropTypes.object, - apiStatus: PropTypes.string.isRequired, - remoteClusters: PropTypes.array.isRequired, - } +export const AutoFollowPatternForm = injectI18n( + class extends PureComponent { + static propTypes = { + saveAutoFollowPattern: PropTypes.func.isRequired, + autoFollowPattern: PropTypes.object, + apiError: PropTypes.object, + apiStatus: PropTypes.string.isRequired, + remoteClusters: PropTypes.array.isRequired, + } - constructor(props) { - super(props); + constructor(props) { + super(props); - const isNew = this.props.autoFollowPattern === undefined; + const isNew = this.props.autoFollowPattern === undefined; - const autoFollowPattern = isNew - ? getEmptyAutoFollowPattern(this.props.remoteClusters) - : { - ...this.props.autoFollowPattern, - ...getPrefixSuffixFromFollowPattern(this.props.autoFollowPattern.followIndexPattern) - }; + const autoFollowPattern = isNew + ? getEmptyAutoFollowPattern(this.props.remoteClusters) + : { + ...this.props.autoFollowPattern, + ...getPrefixSuffixFromFollowPattern(this.props.autoFollowPattern.followIndexPattern) + }; - this.state = { - autoFollowPattern, - fieldsErrors: validateAutoFollowPattern(autoFollowPattern), - areErrorsVisible: false, - isNew, - }; - } + this.state = { + autoFollowPattern, + fieldsErrors: validateAutoFollowPattern(autoFollowPattern), + areErrorsVisible: false, + isNew, + }; + } - onFieldsChange = (fields) => { - this.setState(({ autoFollowPattern }) => ({ - autoFollowPattern: { - ...autoFollowPattern, - ...fields, - }, - })); - - const errors = validateAutoFollowPattern(fields); - this.setState(({ fieldsErrors }) => updateFormErrors(errors, fieldsErrors)); - }; - - onClusterChange = (remoteCluster) => { - this.onFieldsChange({ remoteCluster }); - }; - - onCreateLeaderIndexPattern = (indexPattern) => { - const error = validateLeaderIndexPattern(indexPattern); - - if (error) { - const errors = { - leaderIndexPatterns: - { - ...error, - alwaysVisible: true, + onFieldsChange = (fields) => { + this.setState(({ autoFollowPattern }) => ({ + autoFollowPattern: { + ...autoFollowPattern, + ...fields, }, - }; + })); + const errors = validateAutoFollowPattern(fields); this.setState(({ fieldsErrors }) => updateFormErrors(errors, fieldsErrors)); + }; - // Return false to explicitly reject the user's input. - return false; - } + onClusterChange = (remoteCluster) => { + this.onFieldsChange({ remoteCluster }); + }; - const { - autoFollowPattern: { - leaderIndexPatterns, - }, - } = this.state; - - const newLeaderIndexPatterns = [ - ...leaderIndexPatterns, - indexPattern, - ]; - - this.onFieldsChange({ leaderIndexPatterns: newLeaderIndexPatterns }); - }; - - onLeaderIndexPatternChange = (indexPatterns) => { - this.onFieldsChange({ - leaderIndexPatterns: indexPatterns.map(({ label }) => label) - }); - }; - - onLeaderIndexPatternInputChange = (leaderIndexPattern) => { - if (!leaderIndexPattern || !leaderIndexPattern.trim()) { - return; - } + onCreateLeaderIndexPattern = (indexPattern) => { + const error = validateLeaderIndexPattern(indexPattern); - const { autoFollowPattern: { leaderIndexPatterns } } = this.state; + if (error) { + const errors = { + leaderIndexPatterns: + { + ...error, + alwaysVisible: true, + }, + }; - 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.`, - }); + this.setState(({ fieldsErrors }) => updateFormErrors(errors, fieldsErrors)); - const errors = { - leaderIndexPatterns: { - message: errorMsg, - alwaysVisible: true, + // Return false to explicitly reject the user's input. + return false; + } + + const { + autoFollowPattern: { + leaderIndexPatterns, }, - }; + } = this.state; - this.setState(({ fieldsErrors }) => updateFormErrors(errors, fieldsErrors)); - } - }; + const newLeaderIndexPatterns = [ + ...leaderIndexPatterns, + indexPattern, + ]; - getFields = () => { - const { autoFollowPattern: stateFields } = this.state; - const { followIndexPatternPrefix, followIndexPatternSuffix, ...rest } = stateFields; + this.onFieldsChange({ leaderIndexPatterns: newLeaderIndexPatterns }); + }; - return { - ...rest, - followIndexPattern: `${followIndexPatternPrefix}{{leader_index}}${followIndexPatternSuffix}` + onLeaderIndexPatternChange = (indexPatterns) => { + this.onFieldsChange({ + leaderIndexPatterns: indexPatterns.map(({ label }) => label) + }); }; - }; - isFormValid() { - return Object.values(this.state.fieldsErrors).every(error => error === null); - } + onLeaderIndexPatternInputChange = (leaderIndexPattern) => { + if (!leaderIndexPattern || !leaderIndexPattern.trim()) { + return; + } - sendForm = () => { - const isFormValid = this.isFormValid(); + const { autoFollowPattern: { leaderIndexPatterns } } = this.state; - if (!isFormValid) { - this.setState({ areErrorsVisible: true }); - return; - } + 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.`, + }); - this.setState({ areErrorsVisible: false }); + const errors = { + leaderIndexPatterns: { + message: errorMsg, + alwaysVisible: true, + }, + }; - const { name, ...autoFollowPattern } = this.getFields(); - this.props.saveAutoFollowPattern(name, autoFollowPattern); - }; + this.setState(({ fieldsErrors }) => updateFormErrors(errors, fieldsErrors)); + } + }; - cancelForm = () => { - routing.navigate('/auto_follow_patterns'); - }; + getFields = () => { + const { autoFollowPattern: stateFields } = this.state; + const { followIndexPatternPrefix, followIndexPatternSuffix, ...rest } = stateFields; - /** - * Secctions Renders - */ - renderApiErrors() { - const { apiError, intl } = this.props; + return { + ...rest, + followIndexPattern: `${followIndexPatternPrefix}{{leader_index}}${followIndexPatternSuffix}` + }; + }; - if (apiError) { - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternForm.savingErrorTitle', - defaultMessage: 'Error creating auto-follow pattern', - }); - return ; + isFormValid() { + return Object.values(this.state.fieldsErrors).every(error => error === null); } - return null; - } + sendForm = () => { + const isFormValid = this.isFormValid(); - renderForm = () => { - const { intl } = this.props; - const { - autoFollowPattern: { - name, - remoteCluster, - leaderIndexPatterns, - followIndexPatternPrefix, - followIndexPatternSuffix, - }, - isNew, - areErrorsVisible, - fieldsErrors, - } = this.state; + if (!isFormValid) { + this.setState({ areErrorsVisible: true }); + return; + } + + this.setState({ areErrorsVisible: false }); + + const { name, ...autoFollowPattern } = this.getFields(); + this.props.saveAutoFollowPattern(name, autoFollowPattern); + }; + + cancelForm = () => { + routing.navigate('/auto_follow_patterns'); + }; /** - * Auto-follow pattern Name + * Secctions Renders */ - const renderAutoFollowPatternName = () => { - const isInvalid = areErrorsVisible && !!fieldsErrors.name; + renderApiErrors() { + const { apiError, intl } = this.props; - return ( - -

- -

- - )} - description={( - - )} - fullWidth - > - ; + } + + return null; + } + + renderForm = () => { + const { intl } = this.props; + const { + autoFollowPattern: { + name, + remoteCluster, + leaderIndexPatterns, + followIndexPatternPrefix, + followIndexPatternSuffix, + }, + isNew, + areErrorsVisible, + fieldsErrors, + } = this.state; + + /** + * Auto-follow pattern Name + */ + const renderAutoFollowPatternName = () => { + const isInvalid = areErrorsVisible && !!fieldsErrors.name; + + return ( + +

+ +

+ + )} + description={( )} - error={fieldsErrors.name} - isInvalid={isInvalid} fullWidth > - + )} + error={fieldsErrors.name} isInvalid={isInvalid} - value={name} - onChange={e => this.onFieldsChange({ name: e.target.value })} fullWidth - disabled={!isNew} - /> -
-
- ); - }; + > + this.onFieldsChange({ name: e.target.value })} + fullWidth + disabled={!isNew} + /> + + + ); + }; - /** - * Remote Cluster - */ - const renderRemoteClusterField = () => { - const remoteClustersOptions = this.props.remoteClusters.map(({ name, isConnected }) => ({ - value: name, - inputDisplay: isConnected ? name : `${name} (not connected)`, - disabled: !isConnected, - 'data-test-subj': `option-${name}` - })); + /** + * Remote Cluster + */ + const renderRemoteClusterField = () => { + const remoteClustersOptions = this.props.remoteClusters.map(({ name, isConnected }) => ({ + value: name, + inputDisplay: isConnected ? name : `${name} (not connected)`, + disabled: !isConnected, + 'data-test-subj': `option-${name}` + })); - return ( - -

- -

- - )} - description={( - - )} - fullWidth - > - +

+ +

+ + )} + description={( )} fullWidth > - - { isNew && ( - - )} - { !isNew && ( - )} - -
-
- ); - }; + fullWidth + > + + { isNew && ( + + )} + { !isNew && ( + + )} + + + + ); + }; - /** - * Leader index pattern(s) - */ - const renderLeaderIndexPatterns = () => { - const hasError = !!fieldsErrors.leaderIndexPatterns; - const isInvalid = hasError && (fieldsErrors.leaderIndexPatterns.alwaysVisible || areErrorsVisible); - const formattedLeaderIndexPatterns = leaderIndexPatterns.map(pattern => ({ label: pattern })); + /** + * Leader index pattern(s) + */ + const renderLeaderIndexPatterns = () => { + const hasError = !!fieldsErrors.leaderIndexPatterns; + const isInvalid = hasError && (fieldsErrors.leaderIndexPatterns.alwaysVisible || areErrorsVisible); + const formattedLeaderIndexPatterns = leaderIndexPatterns.map(pattern => ({ label: pattern })); - return ( - -

+ return ( + +

+ +

+ + )} + description={( + +

+ +

+ +

+ + + + ) }} + /> +

+
+ )} + fullWidth + > + -

- - )} - description={( - -

+ )} + helpText={( {indexPatternIllegalCharacters} }} /> -

+ )} + isInvalid={isInvalid} + error={fieldsErrors.leaderIndexPatterns} + fullWidth + > + + +
+ ); + }; -

- - - - ) }} - /> -

- - )} - fullWidth - > - { + const isPrefixInvalid = areErrorsVisible && !!fieldsErrors.followIndexPatternPrefix; + const isSuffixInvalid = areErrorsVisible && !!fieldsErrors.followIndexPatternSuffix; + + const renderFollowIndicesPreview = () => { + const { indicesPreview } = getPreviewIndicesFromAutoFollowPattern({ + prefix: followIndexPatternPrefix, + suffix: followIndexPatternSuffix, + leaderIndexPatterns + }); + + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternForm.indicesPreviewTitle', + defaultMessage: 'Index name examples', + }); + + return ( + +
    + {indicesPreview.map((followerIndex, i) =>
  • {followerIndex}
  • )} +
+
+ ); + }; + + return ( + +

+ +

+ )} - helpText={( + description={( {indexPatternIllegalCharacters} }} + id="xpack.crossClusterReplication.autoFollowPatternForm.sectionAutoFollowPatternDescription" + defaultMessage="A custom prefix or suffix to apply to the names of the follower + indices so you can more easily identify replicated indices. By default, a follower + index has the same name as the leader index." /> )} - isInvalid={isInvalid} - error={fieldsErrors.leaderIndexPatterns} fullWidth > - -
- - ); - }; - - /** - * Auto-follow pattern - */ - const renderAutoFollowPattern = () => { - const isPrefixInvalid = areErrorsVisible && !!fieldsErrors.followIndexPatternPrefix; - const isSuffixInvalid = areErrorsVisible && !!fieldsErrors.followIndexPatternSuffix; - - const renderFollowIndicesPreview = () => { - const { indicesPreview } = getPreviewIndicesFromAutoFollowPattern({ - prefix: followIndexPatternPrefix, - suffix: followIndexPatternSuffix, - leaderIndexPatterns - }); + + + + )} + error={fieldsErrors.followIndexPatternPrefix} + isInvalid={isPrefixInvalid} + fullWidth + > + this.onFieldsChange({ followIndexPatternPrefix: e.target.value })} + fullWidth + /> + + + + + + )} + error={fieldsErrors.followIndexPatternSuffix} + isInvalid={isSuffixInvalid} + fullWidth + > + this.onFieldsChange({ followIndexPatternSuffix: e.target.value })} + fullWidth + /> + + + - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternForm.indicesPreviewTitle', - defaultMessage: 'Index name examples', - }); + + {indexNameIllegalCharacters} }} + /> + - return ( - - -
    - {indicesPreview.map((followerIndex, i) =>
  • {followerIndex}
  • )} -
-
+ {!!leaderIndexPatterns.length && ( + + + {renderFollowIndicesPreview()} + + )} + ); }; - return ( - -

+ /** + * Form Error warning message + */ + const renderFormErrorWarning = () => { + const { areErrorsVisible } = this.state; + const isFormValid = this.isFormValid(); + + if (!areErrorsVisible || isFormValid) { + return null; + } + + return ( + + + -

- - )} - description={( - - )} - fullWidth - > - - - + ); + }; + + /** + * Form Actions + */ + const renderActions = () => { + const { apiStatus } = this.props; + const { areErrorsVisible } = this.state; + + if (apiStatus === API_STATUS.SAVING) { + return ( + + + + + + + - )} - error={fieldsErrors.followIndexPatternPrefix} - isInvalid={isPrefixInvalid} - fullWidth + + + + ); + } + + const isSaveDisabled = areErrorsVisible && !this.isFormValid(); + + return ( + + + - this.onFieldsChange({ followIndexPatternPrefix: e.target.value })} - fullWidth + - + - - - )} - error={fieldsErrors.followIndexPatternSuffix} - isInvalid={isSuffixInvalid} - fullWidth + + - this.onFieldsChange({ followIndexPatternSuffix: e.target.value })} - fullWidth + - + - - - {indexNameIllegalCharacters} }} - /> - - - {!!leaderIndexPatterns.length && ( - - - {renderFollowIndicesPreview()} - - )} -
- ); - }; - - /** - * Form Error warning message - */ - const renderFormErrorWarning = () => { - const { areErrorsVisible } = this.state; - const isFormValid = this.isFormValid(); - - if (!areErrorsVisible || isFormValid) { - return null; - } + ); + }; return ( - - - )} - color="danger" - iconType="cross" - /> + + {renderAutoFollowPatternName()} + {renderRemoteClusterField()} + {renderLeaderIndexPatterns()} + {renderAutoFollowPattern()} + + {renderFormErrorWarning()} + + {renderActions()} ); - }; + } - /** - * Form Actions - */ - const renderActions = () => { + renderLoading = () => { const { apiStatus } = this.props; - const { areErrorsVisible } = this.state; if (apiStatus === API_STATUS.SAVING) { return ( - - - - - - - - - - - + + + ); } + return null; + } - const isSaveDisabled = areErrorsVisible && !this.isFormValid(); - - return ( - - - - - - - - - - - - - - ); - }; - - return ( - - - {renderAutoFollowPatternName()} - {renderRemoteClusterField()} - {renderLeaderIndexPatterns()} - {renderAutoFollowPattern()} - - {renderFormErrorWarning()} - - {renderActions()} - - ); - } - - renderLoading = () => { - const { apiStatus } = this.props; - - if (apiStatus === API_STATUS.SAVING) { + render() { return ( - - - + + {this.renderApiErrors()} + {this.renderForm()} + {this.renderLoading()} + ); } - return null; } - - render() { - return ( - - {this.renderApiErrors()} - {this.renderForm()} - {this.renderLoading()} - - ); - } -} - -export const AutoFollowPatternForm = injectI18n(AutoFollowPatternFormUI); +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js b/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js index ac77a36b4d47b..958065e87424e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js @@ -11,7 +11,7 @@ import { EuiCallOut } from '@elastic/eui'; export function SectionUnauthorizedUI({ intl, children }) { const title = intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.noPermissionTitle', + id: 'xpack.crossClusterReplication.remoteClusterList.noPermissionTitle', defaultMessage: 'Permission error', }); return ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js index 95e39161efa04..74090862a007f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js @@ -29,156 +29,157 @@ import { SectionError, } from '../../components'; -class AutoFollowPatternAddUi 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(); - } - - renderEmptyClusters() { - const { intl, match: { url: currentUrl } } = this.props; - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.emptyRemoteClustersCallOutTitle', - defaultMessage: 'No remote cluster found' - }); - - return ( - - -

- -

- - + - - -
-
- ); - } - - renderNoConnectedCluster() { - const { intl } = this.props; - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.noRemoteClustersConnectedCallOutTitle', - defaultMessage: 'Remote cluster connection error' - }); - - return ( - - -

- -

- + +

+ + + + +
+
+ ); + } + + renderNoConnectedCluster() { + const { intl } = this.props; + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.noRemoteClustersConnectedCallOutTitle', + defaultMessage: 'Remote cluster connection error' + }); + + return ( + + - - - - - ); - } - - render() { - const { saveAutoFollowPattern, apiStatus, apiError, intl } = this.props; +

+ +

+ + + + + + ); + } + + render() { + const { saveAutoFollowPattern, apiStatus, apiError, intl } = this.props; + + return ( + + + + + )} + /> + + + {({ isLoading, error, remoteClusters }) => { + if (isLoading) { + return ( + + + + ); + } + + if (error) { + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClustersErrorTitle', + defaultMessage: 'Error loading remote clusters', + }); + return ; + } + + if (!remoteClusters.length) { + return this.renderEmptyClusters(); + } + + if (remoteClusters.every(cluster => cluster.isConnected === false)) { + return this.renderNoConnectedCluster(); + } - return ( - - - - - )} - /> - - - {({ isLoading, error, remoteClusters }) => { - if (isLoading) { return ( - - - + ); - } - - if (error) { - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClustersErrorTitle', - defaultMessage: 'Error loading remote clusters', - }); - return ; - } - - if (!remoteClusters.length) { - return this.renderEmptyClusters(); - } - - if (remoteClusters.every(cluster => cluster.isConnected === false)) { - return this.renderNoConnectedCluster(); - } - - return ( - - ); - }} - - - - - ); + }} + + + + + ); + } } -} - -export const AutoFollowPatternAdd = injectI18n(AutoFollowPatternAddUi); +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 3288a4966c834..46a802e9c228f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -33,174 +33,175 @@ import { } from '../../components'; import { API_STATUS } from '../../constants'; -class AutoFollowPatternEditUi extends PureComponent { - static propTypes = { - getAutoFollowPattern: PropTypes.func.isRequired, - saveAutoFollowPattern: PropTypes.func.isRequired, - clearApiError: PropTypes.func.isRequired, - apiError: PropTypes.object, - apiStatus: PropTypes.string.isRequired, - } +export const AutoFollowPatternEdit = injectI18n( + class extends PureComponent { + static propTypes = { + getAutoFollowPattern: PropTypes.func.isRequired, + saveAutoFollowPattern: PropTypes.func.isRequired, + clearApiError: PropTypes.func.isRequired, + apiError: PropTypes.object, + apiStatus: PropTypes.string.isRequired, + } + + componentDidMount() { + const { autoFollowPattern, match: { params: { id } } } = this.props; + if (!autoFollowPattern) { + const decodedId = decodeURIComponent(id); + this.props.getAutoFollowPattern(decodedId); + } - componentDidMount() { - const { autoFollowPattern, match: { params: { id } } } = this.props; - if (!autoFollowPattern) { - const decodedId = decodeURIComponent(id); - this.props.getAutoFollowPattern(decodedId); + chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, editBreadcrumb ]); } - chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, editBreadcrumb ]); - } + componentWillUnmount() { + this.props.clearApiError(); + } - componentWillUnmount() { - this.props.clearApiError(); - } + renderApiError(error) { + const { intl } = this.props; + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorTitle', + defaultMessage: 'Error loading auto-follow pattern', + }); + + return ( + + + + + + + + + + + + ); + } - renderApiError(error) { - const { intl } = this.props; - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorTitle', - defaultMessage: 'Error loading auto-follow pattern', - }); - - return ( - - - - - - - - - - - - ); - } + renderLoadingAutoFollowPattern() { + return ( + + + + ); + } - renderLoadingAutoFollowPattern() { - return ( - - - - ); - } + renderMissingCluster({ name, remoteCluster }) { + const { intl } = this.props; + + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.emptyRemoteClustersTitle', + defaultMessage: 'Remote cluster missing' + }); - renderMissingCluster({ name, remoteCluster }) { - const { intl } = this.props; - - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.emptyRemoteClustersTitle', - defaultMessage: 'Remote cluster missing' - }); - - return ( - - -

- - -

- + - - -
-
- ); - } +

- render() { - const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, intl } = this.props; + +

+ + + + + + ); + } - return ( - - - - - )} - /> + render() { + const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, intl } = this.props; - {apiStatus === API_STATUS.LOADING && this.renderLoadingAutoFollowPattern()} + return ( + + + + + )} + /> - {apiError && this.renderApiError(apiError)} + {apiStatus === API_STATUS.LOADING && this.renderLoadingAutoFollowPattern()} + + {apiError && this.renderApiError(apiError)} + + {autoFollowPattern && ( + + {({ isLoading, error, remoteClusters }) => { + if (isLoading) { + return ( + + + + ); + } + + if (error) { + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingRemoteClustersErrorTitle', + defaultMessage: 'Error loading remote clusters', + }); + return ; + } + + const autoFollowPatternCluster = remoteClusters.find(cluster => cluster.name === autoFollowPattern.remoteCluster); + + if (!autoFollowPatternCluster || !autoFollowPatternCluster.isConnected) { + return this.renderMissingCluster(autoFollowPattern); + } - {autoFollowPattern && ( - - {({ isLoading, error, remoteClusters }) => { - if (isLoading) { return ( - - - + ); - } - - if (error) { - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingRemoteClustersErrorTitle', - defaultMessage: 'Error loading remote clusters', - }); - return ; - } - - const autoFollowPatternCluster = remoteClusters.find(cluster => cluster.name === autoFollowPattern.remoteCluster); - - if (!autoFollowPatternCluster || !autoFollowPatternCluster.isConnected) { - return this.renderMissingCluster(autoFollowPattern); - } - - return ( - - ); - }} - - )} - - - - ); + }} + + )} + + + + ); + } } -} - -export const AutoFollowPatternEdit = injectI18n(AutoFollowPatternEditUi); +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index ce5e59b6a38ee..e437da78a5d3f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -16,101 +16,102 @@ import { AutoFollowPatternTable } from './components'; const REFRESH_RATE_MS = 30000; -export class AutoFollowPatternListUI extends PureComponent { - static propTypes = { - loadAutoFollowPatterns: PropTypes.func, - autoFollowPatterns: PropTypes.array, - apiStatus: PropTypes.string, - apiError: PropTypes.object, - } +export const AutoFollowPatternList = injectI18n( + class extends PureComponent { + static propTypes = { + loadAutoFollowPatterns: PropTypes.func, + autoFollowPatterns: PropTypes.array, + apiStatus: PropTypes.string, + apiError: PropTypes.object, + } - componentDidMount() { - this.props.loadAutoFollowPatterns(); + componentDidMount() { + this.props.loadAutoFollowPatterns(); - // Interval to load auto-follow patterns in the background passing "true" to the fetch method - this.interval = setInterval(() => this.props.loadAutoFollowPatterns(true), REFRESH_RATE_MS); - } + // Interval to load auto-follow patterns in the background passing "true" to the fetch method + this.interval = setInterval(() => this.props.loadAutoFollowPatterns(true), REFRESH_RATE_MS); + } - componentWillUnmount() { - clearInterval(this.interval); - } + componentWillUnmount() { + clearInterval(this.interval); + } - renderEmpty() { - return ( - - - - )} - body={ - -

+ renderEmpty() { + return ( + -

-
- } - actions={ - + + )} + body={ + +

+ +

+
+ } + actions={ + + + + } + /> + ); + } + + renderList() { + const { autoFollowPatterns, apiStatus } = this.props; + + if (apiStatus === API_STATUS.LOADING) { + return ( + -
- } - /> - ); - } - - renderList() { - const { autoFollowPatterns, apiStatus } = this.props; + + ); + } - if (apiStatus === API_STATUS.LOADING) { - return ( - - - - ); + return ; } - return ; - } + render() { + const { autoFollowPatterns, apiStatus, apiError, isAuthorized, intl } = this.props; - render() { - const { autoFollowPatterns, apiStatus, apiError, isAuthorized, intl } = this.props; + if (!isAuthorized) { + return null; + } - if (!isAuthorized) { - return null; - } + if (apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length) { + return this.renderEmpty(); + } - if (apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length) { - return this.renderEmpty(); - } + if (apiError) { + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle', + defaultMessage: 'Error loading auto-follow patterns', + }); + return ; + } - if (apiError) { - const title = intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle', - defaultMessage: 'Error loading auto-follow patterns', - }); - return ; + return this.renderList(); } - - return this.renderList(); } -} - -export const AutoFollowPatternList = injectI18n(AutoFollowPatternListUI); +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 66f4bd7a05793..74485e52c70ac 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -21,210 +21,218 @@ import { AutoFollowPatternDeleteProvider } from '../../../../../components'; import routing from '../../../../../services/routing'; import { getPrefixSuffixFromFollowPattern } from '../../../../../services/auto_follow_pattern'; -export class AutoFollowPatternTableUI extends PureComponent { - static propTypes = { - autoFollowPatterns: PropTypes.array, - } - - state = { - selectedItems: [], - } +export const AutoFollowPatternTable = injectI18n( + class extends PureComponent { + static propTypes = { + autoFollowPatterns: PropTypes.array, + } - onSearch = ({ query }) => { - const { text } = query; - const normalizedSearchText = text.toLowerCase(); - this.setState({ - queryText: normalizedSearchText, - }); - }; + state = { + selectedItems: [], + } - getFilteredPatterns = () => { - const { autoFollowPatterns } = this.props; - const { queryText } = this.state; + onSearch = ({ query }) => { + const { text } = query; + const normalizedSearchText = text.toLowerCase(); + this.setState({ + queryText: normalizedSearchText, + }); + }; - if(queryText) { - return autoFollowPatterns.filter(autoFollowPattern => { - const { name, remoteCluster } = autoFollowPattern; + getFilteredPatterns = () => { + const { autoFollowPatterns } = this.props; + const { queryText } = this.state; - const inName = name.toLowerCase().includes(queryText); - const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText); + if(queryText) { + return autoFollowPatterns.filter(autoFollowPattern => { + const { name, remoteCluster } = autoFollowPattern; - return inName || inRemoteCluster; - }); - } + const inName = name.toLowerCase().includes(queryText); + const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText); - 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, - }, { - field: 'remoteCluster', - name: intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternList.table.clusterColumnTitle', - defaultMessage: 'Cluster', - }), - truncateText: true, - sortable: true, - }, { - field: 'leaderIndexPatterns', - name: intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternList.table.leaderPatternsColumnTitle', - defaultMessage: 'Leader patterns', - }), - render: (leaderPatterns) => leaderPatterns.join(', '), - }, { - field: 'followIndexPattern', - name: intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternList.table.prefixColumnTitle', - defaultMessage: 'Follower pattern prefix', - }), - render: (followIndexPattern) => { - const { followIndexPatternPrefix } = getPrefixSuffixFromFollowPattern(followIndexPattern); - return followIndexPatternPrefix; + return inName || inRemoteCluster; + }); } - }, { - field: 'followIndexPattern', - name: intl.formatMessage({ - id: 'xpack.crossClusterReplication.autoFollowPatternList.table.suffixColumnTitle', - defaultMessage: 'Follower pattern suffix', - }), - render: (followIndexPattern) => { - const { followIndexPatternSuffix } = getPrefixSuffixFromFollowPattern(followIndexPattern); - return followIndexPatternSuffix; - } - }, { - 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 ( - - - {(deleteAutoFollowPattern) => ( - deleteAutoFollowPattern(name)} - /> - )} - - - ); - }, - }, - { - name: intl.formatMessage({ id: 'kbn.management.editIndexPattern.fields.table.editLabel', defaultMessage: 'Edit' }), - description: intl.formatMessage({ - id: 'kbn.management.editIndexPattern.fields.table.editDescription', defaultMessage: 'Edit' }), - icon: 'pencil', - onClick: ({ name }) => { - selectAutoFollowPattern(name); - routing.navigate(encodeURI(`/auto_follow_patterns/edit/${encodeURIComponent(name)}`)); - }, - type: 'icon', - }, - ], - width: '100px', - }]; - } - renderLoading = () => { - const { apiStatusDelete } = this.props; - - if (apiStatusDelete === API_STATUS.DELETING) { - return ( - - - - ); - } - return null; - } + return autoFollowPatterns.slice(0); + }; - render() { - const { - selectedItems, - } = this.state; + getTableColumns() { + const { intl, selectAutoFollowPattern } = this.props; - const sorting = { - sort: { + return [{ field: 'name', - direction: 'asc', - } - }; + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.table.nameColumnTitle', + defaultMessage: 'Name', + }), + sortable: true, + truncateText: false, + }, { + field: 'remoteCluster', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.table.clusterColumnTitle', + defaultMessage: 'Cluster', + }), + truncateText: true, + sortable: true, + }, { + field: 'leaderIndexPatterns', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.table.leaderPatternsColumnTitle', + defaultMessage: 'Leader patterns', + }), + render: (leaderPatterns) => leaderPatterns.join(', '), + }, { + field: 'followIndexPattern', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.table.prefixColumnTitle', + defaultMessage: 'Follower pattern prefix', + }), + render: (followIndexPattern) => { + const { followIndexPatternPrefix } = getPrefixSuffixFromFollowPattern(followIndexPattern); + return followIndexPatternPrefix; + } + }, { + field: 'followIndexPattern', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.autoFollowPatternList.table.suffixColumnTitle', + defaultMessage: 'Follower pattern suffix', + }), + render: (followIndexPattern) => { + const { followIndexPatternSuffix } = getPrefixSuffixFromFollowPattern(followIndexPattern); + return followIndexPatternSuffix; + } + }, { + 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 ( + + + {(deleteAutoFollowPattern) => ( + deleteAutoFollowPattern(name)} + /> + )} + + + ); + }, + }, + { + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.editIndexPattern.fields.table.actionEditLabel', + defaultMessage: 'Edit', + }), + description: intl.formatMessage({ + id: 'xpack.crossClusterReplication.editIndexPattern.fields.table.actionEditDescription', + defaultMessage: 'Edit', + }), + icon: 'pencil', + onClick: ({ name }) => { + selectAutoFollowPattern(name); + routing.navigate(encodeURI(`/auto_follow_patterns/edit/${encodeURIComponent(name)}`)); + }, + type: 'icon', + }, + ], + width: '100px', + }]; + } - const pagination = { - initialPageSize: 20, - pageSizeOptions: [10, 20, 50] - }; + renderLoading = () => { + const { apiStatusDelete } = this.props; - const selection = { - onSelectionChange: (selectedItems) => this.setState({ selectedItems }) + if (apiStatusDelete === API_STATUS.DELETING) { + return ( + + + + ); + } + return null; }; - const search = { - toolsLeft: selectedItems.length ? ( - - {(deleteAutoFollowPattern) => ( - deleteAutoFollowPattern(selectedItems.map(({ name }) => name))} - > - - - )} - - ) : undefined, - onChange: this.onSearch, - box: { - incremental: true, - }, - }; + 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 ? ( + + {(deleteAutoFollowPattern) => ( + deleteAutoFollowPattern(selectedItems.map(({ name }) => name))} + > + + + )} + + ) : undefined, + onChange: this.onSearch, + box: { + incremental: true, + }, + }; - return ( - - - {this.renderLoading()} - - ); + return ( + + + {this.renderLoading()} + + ); + } } -} - -export const AutoFollowPatternTable = injectI18n(AutoFollowPatternTableUI); +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js index a29ec8d06a6e7..ec05dfba44598 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js @@ -29,107 +29,108 @@ import routing from '../../services/routing'; import { AutoFollowPatternList } from './auto_follow_pattern_list'; import { SectionUnauthorized } from '../../components'; -export class CrossClusterReplicationHomeUI extends PureComponent { - static propTypes = { - autoFollowPatterns: PropTypes.array, - } - - state = { - sectionActive: 'auto-follow' - } - - componentDidMount() { - chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); - } +export const CrossClusterReplicationHome = injectI18n( + class extends PureComponent { + static propTypes = { + autoFollowPatterns: PropTypes.array, + } - getHeaderSection() { - const { autoFollowPatterns } = this.props; + state = { + sectionActive: 'auto-follow' + } - if (!autoFollowPatterns.length) { - return null; + componentDidMount() { + chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); } - return ( - - -

- -

-
+ getHeaderSection() { + const { autoFollowPatterns } = this.props; - + if (!autoFollowPatterns.length) { + return null; + } - - - -

+ return ( + + +

+ +

+
+ + + + + + +

+ +

+
+ + +

+ +

+
+
+ + + -

-
+ +
+
- -

- -

-
- - - - - - - - + +
+ ); + } - - - ); - } + getUnauthorizedSection() { + const { isAutoFollowApiAuthorized } = this.props; + if (!isAutoFollowApiAuthorized) { + return ( + + + + ); + } + } - getUnauthorizedSection() { - const { isAutoFollowApiAuthorized } = this.props; - if (!isAutoFollowApiAuthorized) { + render() { return ( - - - + + + + {this.getHeaderSection()} + {this.getUnauthorizedSection()} + + + + + + ); } } - - render() { - return ( - - - - {this.getHeaderSection()} - {this.getUnauthorizedSection()} - - - - - - - ); - } -} - -export const CrossClusterReplicationHome = injectI18n(CrossClusterReplicationHomeUI); +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap b/x-pack/plugins/cross_cluster_replication/public/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap index b3b2c6db6c131..416cb05aad20a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap @@ -18,7 +18,7 @@ Object { />, "followIndexPatternSuffix": diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js index 5fb3a4fb51596..f7a76edabe52c 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js @@ -37,14 +37,14 @@ export const validateName = (name = '') => { if (name[0] === '_') { errorMsg = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPattern.nameValidation.errorFirstChar', + 'xpack.crossClusterReplication.autoFollowPattern.nameValidation.errorUnderscore', { defaultMessage: "Name can't begin with an underscore." } ); } if (name.includes(',')) { errorMsg = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPattern.nameValidation.errorFirstChar', + 'xpack.crossClusterReplication.autoFollowPattern.nameValidation.errorComma', { defaultMessage: "Commas are not allowed in the name." } ); } @@ -137,7 +137,7 @@ export const validateSuffix = (suffix) => { if (illegalCharacters.length) { return ( - autofollowPatterns.map(deserializeAutoFollowPattern); +export const deserializeListAutoFollowPatterns = autoFollowPatterns => + autoFollowPatterns.map(deserializeAutoFollowPattern); export const serializeAutoFollowPattern = ({ remoteCluster, diff --git a/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/remote_cluster_form.js b/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/remote_cluster_form.js index e357621a0ebb7..791c5ea43f218 100644 --- a/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/remote_cluster_form.js +++ b/x-pack/plugins/remote_clusters/public/sections/components/remote_cluster_form/remote_cluster_form.js @@ -44,616 +44,616 @@ const defaultFields = { skipUnavailable: false, }; -export class RemoteClusterFormUi extends Component { - static propTypes = { - save: PropTypes.func.isRequired, - cancel: PropTypes.func, - isSaving: PropTypes.bool, - saveError: PropTypes.object, - fields: PropTypes.object, - disabledFields: PropTypes.object, - } - - static defaultProps = { - fields: merge({}, defaultFields), - disabledFields: {}, - } - - constructor(props) { - super(props); +export const RemoteClusterForm = injectI18n( + class extends Component { + static propTypes = { + save: PropTypes.func.isRequired, + cancel: PropTypes.func, + isSaving: PropTypes.bool, + saveError: PropTypes.object, + fields: PropTypes.object, + disabledFields: PropTypes.object, + } - const { fields, disabledFields } = props; - const fieldsState = merge({}, defaultFields, fields); + static defaultProps = { + fields: merge({}, defaultFields), + disabledFields: {}, + } - this.state = { - localSeedErrors: [], - seedInput: '', - fields: fieldsState, - disabledFields, - fieldsErrors: this.getFieldsErrors(fieldsState), - areErrorsVisible: false, - }; - } + constructor(props) { + super(props); - getFieldsErrors(fields, seedInput = '') { - const { name, seeds } = fields; - const errors = {}; + const { fields, disabledFields } = props; + const fieldsState = merge({}, defaultFields, fields); - if (!name || !name.trim()) { - errors.name = ( - - ); - } else if (name.match(/[^a-zA-Z\d\-_]/)) { - errors.name = ( - - ); + this.state = { + localSeedErrors: [], + seedInput: '', + fields: fieldsState, + disabledFields, + fieldsErrors: this.getFieldsErrors(fieldsState), + areErrorsVisible: false, + }; } - if (!seeds.some(seed => Boolean(seed.trim()))) { - // If the user hasn't entered any seeds then we only want to prompt them for some if they - // aren't already in the process of entering one in. In this case, we'll just show the - // combobox-specific validation. - if (!seedInput) { - errors.seeds = ( + getFieldsErrors(fields, seedInput = '') { + const { name, seeds } = fields; + const errors = {}; + + if (!name || !name.trim()) { + errors.name = ( + + ); + } else if (name.match(/[^a-zA-Z\d\-_]/)) { + errors.name = ( ); } - } - return errors; - } + if (!seeds.some(seed => Boolean(seed.trim()))) { + // If the user hasn't entered any seeds then we only want to prompt them for some if they + // aren't already in the process of entering one in. In this case, we'll just show the + // combobox-specific validation. + if (!seedInput) { + errors.seeds = ( + + ); + } + } - onFieldsChange = (changedFields) => { - this.setState(({ fields: prevFields, seedInput }) => { - const newFields = { - ...prevFields, - ...changedFields, - }; - return ({ - fields: newFields, - fieldsErrors: this.getFieldsErrors(newFields, seedInput), + return errors; + } + + onFieldsChange = (changedFields) => { + this.setState(({ fields: prevFields, seedInput }) => { + const newFields = { + ...prevFields, + ...changedFields, + }; + return ({ + fields: newFields, + fieldsErrors: this.getFieldsErrors(newFields, seedInput), + }); }); - }); - }; + }; - getAllFields() { - const { - fields: { + getAllFields() { + const { + fields: { + name, + seeds, + skipUnavailable, + }, + } = this.state; + + return { name, seeds, skipUnavailable, - }, - } = this.state; + }; + } + + save = () => { + const { save } = this.props; - return { - name, - seeds, - skipUnavailable, + if (this.hasErrors()) { + this.setState({ + areErrorsVisible: true, + }); + return; + } + + const cluster = this.getAllFields(); + save(cluster); }; - } - save = () => { - const { save } = this.props; + getLocalSeedErrors = (seedNode) => { + const { intl } = this.props; - if (this.hasErrors()) { - this.setState({ - areErrorsVisible: true, - }); - return; - } + const errors = []; + + if (!seedNode) { + return errors; + } - const cluster = this.getAllFields(); - save(cluster); - }; + const isInvalid = !isSeedNodeValid(seedNode); - getLocalSeedErrors = (seedNode) => { - const { intl } = this.props; + if (isInvalid) { + errors.push(intl.formatMessage({ + id: '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.`, + })); + } - const errors = []; + const isPortInvalid = !isSeedNodePortValid(seedNode); + + if (isPortInvalid) { + errors.push(intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterForm.localSeedError.invalidPortMessage', + defaultMessage: 'A port is required.', + })); + } - if (!seedNode) { return errors; - } + }; - const isInvalid = !isSeedNodeValid(seedNode); + onCreateSeed = (newSeed) => { + // If the user just hit enter without typing anything, treat it as a no-op. + if (!newSeed) { + return; + } - if (isInvalid) { - errors.push(intl.formatMessage({ - id: '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.`, - })); - } + const localSeedErrors = this.getLocalSeedErrors(newSeed); - const isPortInvalid = !isSeedNodePortValid(seedNode); + if (localSeedErrors.length !== 0) { + this.setState({ + localSeedErrors, + }); - if (isPortInvalid) { - errors.push(intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterForm.localSeedError.invalidPortMessage', - defaultMessage: 'A port is required.', - })); - } + // Return false to explicitly reject the user's input. + return false; + } - return errors; - }; + const { + fields: { + seeds, + }, + } = this.state; - onCreateSeed = (newSeed) => { - // If the user just hit enter without typing anything, treat it as a no-op. - if (!newSeed) { - return; - } + const newSeeds = seeds.slice(0); + newSeeds.push(newSeed.toLowerCase()); + this.onFieldsChange({ seeds: newSeeds }); + }; + + onSeedsInputChange = (seedInput) => { + if (!seedInput) { + // If empty seedInput ("") don't do anything. This happens + // right after a seed is created. + return; + } - const localSeedErrors = this.getLocalSeedErrors(newSeed); + const { intl } = this.props; - if (localSeedErrors.length !== 0) { - this.setState({ - localSeedErrors, + this.setState(({ fields, localSeedErrors }) => { + const { seeds } = fields; + + // Allow typing to clear the errors, but not to add new ones. + const errors = (!seedInput || this.getLocalSeedErrors(seedInput).length === 0) ? [] : localSeedErrors; + + // EuiComboBox internally checks for duplicates and prevents calling onCreateOption if the + // input is a duplicate. So we need to surface this error here instead. + const isDuplicate = seeds.includes(seedInput); + + if (isDuplicate) { + errors.push(intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterForm.localSeedError.duplicateMessage', + defaultMessage: `Duplicate seed nodes aren't allowed.`, + })); + } + + return ({ + localSeedErrors: errors, + fieldsErrors: this.getFieldsErrors(fields, seedInput), + seedInput, + }); }); + }; - // Return false to explicitly reject the user's input. - return false; - } + onSeedsChange = (seeds) => { + this.onFieldsChange({ seeds: seeds.map(({ label }) => label) }); + }; - const { - fields: { - seeds, - }, - } = this.state; - - const newSeeds = seeds.slice(0); - newSeeds.push(newSeed.toLowerCase()); - this.onFieldsChange({ seeds: newSeeds }); - }; - - onSeedsInputChange = (seedInput) => { - if (!seedInput) { - // If empty seedInput ("") don't do anything. This happens - // right after a seed is created. - return; - } + onSkipUnavailableChange = (e) => { + const skipUnavailable = e.target.checked; + this.onFieldsChange({ skipUnavailable }); + }; - const { intl } = this.props; + resetToDefault = (fieldName) => { + this.onFieldsChange({ + [fieldName]: defaultFields[fieldName], + }); + }; - this.setState(({ fields, localSeedErrors }) => { - const { seeds } = fields; + hasErrors = () => { + const { fieldsErrors, localSeedErrors } = this.state; + const errorValues = Object.values(fieldsErrors); + const hasErrors = errorValues.some(error => error !== undefined) || localSeedErrors.length; + return hasErrors; + }; - // Allow typing to clear the errors, but not to add new ones. - const errors = (!seedInput || this.getLocalSeedErrors(seedInput).length === 0) ? [] : localSeedErrors; + renderSeeds() { + const { + areErrorsVisible, + fields: { + seeds, + }, + fieldsErrors: { + seeds: errorsSeeds, + }, + localSeedErrors, + } = this.state; - // EuiComboBox internally checks for duplicates and prevents calling onCreateOption if the - // input is a duplicate. So we need to surface this error here instead. - const isDuplicate = seeds.includes(seedInput); + const { intl } = this.props; - if (isDuplicate) { - errors.push(intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterForm.localSeedError.duplicateMessage', - defaultMessage: `Duplicate seed nodes aren't allowed.`, - })); - } + // Show errors if there is a general form error or local errors. + const areFormErrorsVisible = Boolean(areErrorsVisible && errorsSeeds); + const showErrors = areFormErrorsVisible || localSeedErrors.length !== 0; + const errors = areFormErrorsVisible ? localSeedErrors.concat(errorsSeeds) : localSeedErrors; - return ({ - localSeedErrors: errors, - fieldsErrors: this.getFieldsErrors(fields, seedInput), - seedInput, - }); - }); - }; - - onSeedsChange = (seeds) => { - this.onFieldsChange({ seeds: seeds.map(({ label }) => label) }); - }; - - onSkipUnavailableChange = (e) => { - const skipUnavailable = e.target.checked; - this.onFieldsChange({ skipUnavailable }); - }; - - resetToDefault = (fieldName) => { - this.onFieldsChange({ - [fieldName]: defaultFields[fieldName], - }); - }; - - hasErrors = () => { - const { fieldsErrors, localSeedErrors } = this.state; - const errorValues = Object.values(fieldsErrors); - const hasErrors = errorValues.some(error => error !== undefined) || localSeedErrors.length; - return hasErrors; - }; - - renderSeeds() { - const { - areErrorsVisible, - fields: { - seeds, - }, - fieldsErrors: { - seeds: errorsSeeds, - }, - localSeedErrors, - } = this.state; - - const { intl } = this.props; - - // Show errors if there is a general form error or local errors. - const areFormErrorsVisible = Boolean(areErrorsVisible && errorsSeeds); - const showErrors = areFormErrorsVisible || localSeedErrors.length !== 0; - const errors = areFormErrorsVisible ? localSeedErrors.concat(errorsSeeds) : localSeedErrors; - - const formattedSeeds = seeds.map(seed => ({ label: seed })); - - return ( - -

+ const formattedSeeds = seeds.map(seed => ({ label: seed })); + + return ( + +

+ +

+ + )} + description={( + +

+ +

+
+ )} + fullWidth + > + -

- - )} - description={( - -

+ )} + helpText={( -

-
- )} - fullWidth - > - + + +
+ ); + } + + renderSkipUnavailable() { + const { + fields: { + skipUnavailable, + }, + } = this.state; + + return ( + +

+ +

+ )} - helpText={( - + description={( + +

+ + + + ), + learnMoreLink: ( + + + + ), + }} + /> +

+
)} - isInvalid={showErrors} - error={errors} fullWidth > - - -
- ); - } + helpText={ + skipUnavailable !== defaultFields.skipUnavailable ? ( + { this.resetToDefault('skipUnavailable'); }}> + + + ) : null + } + > + + + + ); + } - renderSkipUnavailable() { - const { - fields: { - skipUnavailable, - }, - } = this.state; - - return ( - -

- -

- - )} - description={( - -

- - - - ), - learnMoreLink: ( - - - - ), - }} - /> -

-
- )} - fullWidth - > - { this.resetToDefault('skipUnavailable'); }}> + renderActions() { + const { isSaving, cancel } = this.props; + const { areErrorsVisible } = this.state; + + if (isSaving) { + return ( + + + + + + + - - ) : null - } - > - - -
- ); - } + + + + ); + } - renderActions() { - const { isSaving, cancel } = this.props; - const { areErrorsVisible } = this.state; + let cancelButton; - if (isSaving) { - return ( - + if (cancel) { + cancelButton = ( - + + + + ); + } + + const isSaveDisabled = areErrorsVisible && this.hasErrors(); + return ( + - + - + + + {cancelButton} ); } - let cancelButton; + renderSavingFeedback() { + if (this.props.isSaving) { + return ( + + + + ); + } - if (cancel) { - cancelButton = ( - - - - - - ); + return null; } - const isSaveDisabled = areErrorsVisible && this.hasErrors(); - - return ( - - - - - - - - {cancelButton} - - ); - } - - renderSavingFeedback() { - if (this.props.isSaving) { - return ( - - - - ); - } + renderSaveErrorFeedback() { + const { saveError } = this.props; + + if (saveError) { + const { message, cause } = saveError; + + let errorBody; + + if (cause) { + if (cause.length === 1) { + errorBody = ( +

{cause[0]}

+ ); + } else { + errorBody = ( +
    + {cause.map(causeValue =>
  • {causeValue}
  • )} +
+ ); + } + } - return null; - } + return ( + + + {errorBody} + - renderSaveErrorFeedback() { - const { saveError } = this.props; + + + ); + } - if (saveError) { - const { message, cause } = saveError; + return null; + } - let errorBody; + renderErrors = () => { + const { areErrorsVisible } = this.state; + const hasErrors = this.hasErrors(); - if (cause) { - if (cause.length === 1) { - errorBody = ( -

{cause[0]}

- ); - } else { - errorBody = ( -
    - {cause.map(causeValue =>
  • {causeValue}
  • )} -
- ); - } + if (!areErrorsVisible || !hasErrors) { + return null; } return ( + + )} color="danger" - > - {errorBody} - - - + iconType="cross" + /> ); } - return null; - } - - renderErrors = () => { - const { areErrorsVisible } = this.state; - const hasErrors = this.hasErrors(); - - if (!areErrorsVisible || !hasErrors) { - return null; - } - - return ( - - - - )} - color="danger" - iconType="cross" - /> - - ); - } - - render() { - const { - disabledFields: { - name: disabledName, - }, - } = this.props; + render() { + const { + disabledFields: { + name: disabledName, + }, + } = this.props; + + const { + areErrorsVisible, + fields: { + name, + }, + fieldsErrors: { + name: errorClusterName, + }, + } = this.state; - const { - areErrorsVisible, - fields: { - name, - }, - fieldsErrors: { - name: errorClusterName, - }, - } = this.state; - - return ( - - {this.renderSaveErrorFeedback()} - - - -

- -

- - )} - description={( - - )} - fullWidth - > - + return ( + + {this.renderSaveErrorFeedback()} + + + +

+ +

+ )} - helpText={( + description={( )} - error={errorClusterName} - isInvalid={Boolean(areErrorsVisible && errorClusterName)} fullWidth > - + )} + helpText={( + + )} + error={errorClusterName} isInvalid={Boolean(areErrorsVisible && errorClusterName)} - value={name} - onChange={e => this.onFieldsChange({ name: e.target.value })} fullWidth - disabled={disabledName} - /> -
-
+ > + this.onFieldsChange({ name: e.target.value })} + fullWidth + disabled={disabledName} + /> + + - {this.renderSeeds()} + {this.renderSeeds()} - {this.renderSkipUnavailable()} -
+ {this.renderSkipUnavailable()} + - {this.renderErrors()} + {this.renderErrors()} - + - {this.renderActions()} + {this.renderActions()} - {this.renderSavingFeedback()} -
- ); + {this.renderSavingFeedback()} + + ); + } } -} - -export const RemoteClusterForm = injectI18n(RemoteClusterFormUi); +); diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_add/remote_cluster_add.js index 9b9989705578b..4187bfdd82fba 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_add/remote_cluster_add.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_add/remote_cluster_add.js @@ -20,65 +20,65 @@ import { CRUD_APP_BASE_PATH } from '../../constants'; import { listBreadcrumb, addBreadcrumb } from '../../services'; import { RemoteClusterPageTitle, RemoteClusterForm } from '../components'; -export class RemoteClusterAddUi extends Component { - static propTypes = { - addCluster: PropTypes.func, - isAddingCluster: PropTypes.bool, - addClusterError: PropTypes.object, - clearAddClusterErrors: PropTypes.func, - } +export const RemoteClusterAdd = injectI18n( + class extends Component { + static propTypes = { + addCluster: PropTypes.func, + isAddingCluster: PropTypes.bool, + addClusterError: PropTypes.object, + clearAddClusterErrors: PropTypes.func, + } - constructor(props) { - super(props); - chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, addBreadcrumb ]); - } + constructor(props) { + super(props); + chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb, addBreadcrumb ]); + } - componentWillUnmount() { - // Clean up after ourselves. - this.props.clearAddClusterErrors(); - } + componentWillUnmount() { + // Clean up after ourselves. + this.props.clearAddClusterErrors(); + } - save = (clusterConfig) => { - this.props.addCluster(clusterConfig); - }; + save = (clusterConfig) => { + this.props.addCluster(clusterConfig); + }; - cancel = () => { - const { history } = this.props; - history.push(CRUD_APP_BASE_PATH); - }; + cancel = () => { + const { history } = this.props; + history.push(CRUD_APP_BASE_PATH); + }; - render() { - const { isAddingCluster, addClusterError } = this.props; + render() { + const { isAddingCluster, addClusterError } = this.props; - return ( - - - - - - )} - /> + return ( + + + + + + )} + /> - - - - - - ); + + + + + + ); + } } -} - -export const RemoteClusterAdd = injectI18n(RemoteClusterAddUi); +); diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js index ff1a4275241f6..62233c12fae0b 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js @@ -30,162 +30,163 @@ const disabledFields = { name: true, }; -export class RemoteClusterEditUi extends Component { - static propTypes = { - isLoading: PropTypes.bool, - cluster: PropTypes.object, - startEditingCluster: PropTypes.func, - stopEditingCluster: PropTypes.func, - editCluster: PropTypes.func, - isEditingCluster: PropTypes.bool, - getEditClusterError: PropTypes.string, - clearEditClusterErrors: PropTypes.func, - openDetailPanel: PropTypes.func, - } - constructor(props) { - super(props); +export const RemoteClusterEdit = injectI18n( + class extends Component { + static propTypes = { + isLoading: PropTypes.bool, + cluster: PropTypes.object, + startEditingCluster: PropTypes.func, + stopEditingCluster: PropTypes.func, + editCluster: PropTypes.func, + isEditingCluster: PropTypes.bool, + getEditClusterError: PropTypes.string, + clearEditClusterErrors: PropTypes.func, + openDetailPanel: PropTypes.func, + } + + constructor(props) { + super(props); - const { - match: { - params: { - name, + const { + match: { + params: { + name, + }, }, - }, - } = props; + } = props; + + chrome.breadcrumbs.set([ + MANAGEMENT_BREADCRUMB, + buildListBreadcrumb(`?cluster=${name}`), + editBreadcrumb, + ]); + + this.state = { + clusterName: name, + }; + } + + componentDidMount() { + const { startEditingCluster } = this.props; + const { clusterName } = this.state; + startEditingCluster(clusterName); + } - chrome.breadcrumbs.set([ - MANAGEMENT_BREADCRUMB, - buildListBreadcrumb(`?cluster=${name}`), - editBreadcrumb, - ]); + componentWillUnmount() { + // Clean up after ourselves. + this.props.clearEditClusterErrors(); + this.props.stopEditingCluster(); + } - this.state = { - clusterName: name, + save = (clusterConfig) => { + this.props.editCluster(clusterConfig); }; - } - componentDidMount() { - const { startEditingCluster } = this.props; - const { clusterName } = this.state; - startEditingCluster(clusterName); - } + cancel = () => { + const { history, openDetailPanel } = this.props; + const { clusterName } = this.state; + history.push(CRUD_APP_BASE_PATH); + openDetailPanel(clusterName); + }; - componentWillUnmount() { - // Clean up after ourselves. - this.props.clearEditClusterErrors(); - this.props.stopEditingCluster(); - } + renderContent() { + const { + isLoading, + cluster, + isEditingCluster, + getEditClusterError, + } = this.props; + + if (isLoading) { + return ( + + + + + + + + + + + + + + ); + } else if (!cluster) { + return ( + + + + + + + + + + + + + + ); + } - save = (clusterConfig) => { - this.props.editCluster(clusterConfig); - }; - - cancel = () => { - const { history, openDetailPanel } = this.props; - const { clusterName } = this.state; - history.push(CRUD_APP_BASE_PATH); - openDetailPanel(clusterName); - }; - - renderContent() { - const { - isLoading, - cluster, - isEditingCluster, - getEditClusterError, - } = this.props; - - if (isLoading) { return ( - - - - - - - - - - - - - + ); - } else if (!cluster) { + } + + render() { + const { + clusterName, + } = this.state; + return ( - - - - - - - - - + + + + + )} /> - - - - + + {this.renderContent()} + + + + ); } - - return ( - - ); } - - render() { - const { - clusterName, - } = this.state; - - return ( - - - - - - )} - /> - - {this.renderContent()} - - - - - ); - } -} - -export const RemoteClusterEdit = injectI18n(RemoteClusterEditUi); +); diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/components/remove_cluster_button/remove_cluster_button.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/components/remove_cluster_button/remove_cluster_button.js index 15ad3ed81a253..e44b87945d46c 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/components/remove_cluster_button/remove_cluster_button.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/components/remove_cluster_button/remove_cluster_button.js @@ -14,148 +14,148 @@ import { EuiOverlayMask, } from '@elastic/eui'; -export class RemoveClusterButtonUi extends Component { - static propTypes = { - removeClusters: PropTypes.func.isRequired, - clusterNames: PropTypes.array.isRequired, - isSmallButton: PropTypes.bool, - children: PropTypes.node, - }; - - static defaultProps = { - children: ( - - ), - }; - - state = { - isModalOpen: false, - }; - - onMouseOverModal = (event) => { - // This component can sometimes be used inside of an EuiToolTip, in which case mousing over - // the modal can trigger the tooltip. Stopping propagation prevents this. - event.stopPropagation(); - }; - - showConfirmModal = () => { - this.setState({ - isModalOpen: true, - }); - }; - - closeConfirmModal = () => { - this.setState({ +export const RemoveClusterButton = injectI18n( + class extends Component { + static propTypes = { + removeClusters: PropTypes.func.isRequired, + clusterNames: PropTypes.array.isRequired, + isSmallButton: PropTypes.bool, + children: PropTypes.node, + }; + + static defaultProps = { + children: ( + + ), + }; + + state = { isModalOpen: false, - }); - }; - - onConfirm = () => { - const { removeClusters, clusterNames } = this.props; - removeClusters(clusterNames); - this.closeConfirmModal(); - } - - renderButtonText() { - const { clusterNames, isSmallButton } = this.props; - const isSingleCluster = clusterNames.length === 1; - - if (isSmallButton) { - return ( - - ); + }; + + onMouseOverModal = (event) => { + // This component can sometimes be used inside of an EuiToolTip, in which case mousing over + // the modal can trigger the tooltip. Stopping propagation prevents this. + event.stopPropagation(); + }; + + showConfirmModal = () => { + this.setState({ + isModalOpen: true, + }); + }; + + closeConfirmModal = () => { + this.setState({ + isModalOpen: false, + }); + }; + + onConfirm = () => { + const { removeClusters, clusterNames } = this.props; + removeClusters(clusterNames); + this.closeConfirmModal(); } - if (isSingleCluster) { + renderButtonText() { + const { clusterNames, isSmallButton } = this.props; + const isSingleCluster = clusterNames.length === 1; + + if (isSmallButton) { + return ( + + ); + } + + if (isSingleCluster) { + return ( + + ); + } + return ( ); } - return ( - - ); - } + render() { + const { intl, clusterNames, children } = this.props; + const { isModalOpen } = this.state; + const isSingleCluster = clusterNames.length === 1; + + const button = cloneElement(children, { + onClick: this.showConfirmModal, + }, this.renderButtonText()); + + let modal; + + if (isModalOpen) { + const title = isSingleCluster ? intl.formatMessage({ + id: 'xpack.remoteClusters.removeButton.confirmModal.deleteSingleClusterTitle', + defaultMessage: 'Remove remote cluster \'{name}\'?', + }, { name: clusterNames[0] }) : intl.formatMessage({ + id: 'xpack.remoteClusters.removeButton.confirmModal.multipleDeletionTitle', + defaultMessage: 'Remove {count} remote clusters?', + }, { count: clusterNames.length }); + + const content = ( + +

+ +

+ { isSingleCluster ? null : (
    {clusterNames.map(name =>
  • {name}
  • )}
)} +
+ ); + + modal = ( + + { /* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */ } + + {content} + + + ); + } - render() { - const { intl, clusterNames, children } = this.props; - const { isModalOpen } = this.state; - const isSingleCluster = clusterNames.length === 1; - - const button = cloneElement(children, { - onClick: this.showConfirmModal, - }, this.renderButtonText()); - - let modal; - - if (isModalOpen) { - const title = isSingleCluster ? intl.formatMessage({ - id: 'xpack.remoteClusters.removeButton.confirmModal.deleteSingleClusterTitle', - defaultMessage: 'Remove remote cluster \'{name}\'?', - }, { name: clusterNames[0] }) : intl.formatMessage({ - id: 'xpack.remoteClusters.removeButton.confirmModal.multipleDeletionTitle', - defaultMessage: 'Remove {count} remote clusters?', - }, { count: clusterNames.length }); - - const content = ( + return ( -

- -

- { isSingleCluster ? null : (
    {clusterNames.map(name =>
  • {name}
  • )}
)} + {button} + {modal}
); - - modal = ( - - { /* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */ } - - {content} - - - ); } - - return ( - - {button} - {modal} - - ); } -} - -export const RemoveClusterButton = injectI18n(RemoveClusterButtonUi); +); diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/detail_panel/detail_panel.js index 1265d072efd5a..92321557db315 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/detail_panel/detail_panel.js @@ -32,373 +32,373 @@ import { CRUD_APP_BASE_PATH } from '../../../constants'; import { ConnectionStatus, RemoveClusterButton } from '../components'; -export class DetailPanelUi extends Component { - static propTypes = { - isOpen: PropTypes.bool.isRequired, - isLoading: PropTypes.bool, - cluster: PropTypes.object, - closeDetailPanel: PropTypes.func.isRequired, - clusterName: PropTypes.string, - copyCluster: PropTypes.func.isRequired, - } - - copyCluster = () => { - const { copyCluster, cluster } = this.props; - const { - name, - seeds, - skipUnavailable, - } = cluster; - - const clusterConfig = { - name, - seeds, - skipUnavailable, - }; - - copyCluster(clusterConfig); - } +export const DetailPanel = injectI18n( + class extends Component { + static propTypes = { + isOpen: PropTypes.bool.isRequired, + isLoading: PropTypes.bool, + cluster: PropTypes.object, + closeDetailPanel: PropTypes.func.isRequired, + clusterName: PropTypes.string, + copyCluster: PropTypes.func.isRequired, + } - renderSkipUnavailableValue(skipUnavailable) { - if(skipUnavailable === true) { - return ( - - ); + copyCluster = () => { + const { copyCluster, cluster } = this.props; + const { + name, + seeds, + skipUnavailable, + } = cluster; + + const clusterConfig = { + name, + seeds, + skipUnavailable, + }; + + copyCluster(clusterConfig); } - if(skipUnavailable === false) { + renderSkipUnavailableValue(skipUnavailable) { + if(skipUnavailable === true) { + return ( + + ); + } + + if(skipUnavailable === false) { + return ( + + ); + } + return ( ); } - return ( - - ); - } - - renderCluster() { - const { - cluster, - } = this.props; - - const { - isConnected, - connectedNodesCount, - skipUnavailable, - seeds, - isConfiguredByNode, - maxConnectionsPerCluster, - initialConnectTimeout, - } = cluster; - - let configuredByNodeWarning; - - if (isConfiguredByNode) { - configuredByNodeWarning = ( - - - } - color="warning" - iconType="help" - > - -

+ renderCluster() { + const { + cluster, + } = this.props; + + const { + isConnected, + connectedNodesCount, + skipUnavailable, + seeds, + isConfiguredByNode, + maxConnectionsPerCluster, + initialConnectTimeout, + } = cluster; + + let configuredByNodeWarning; + + if (isConfiguredByNode) { + configuredByNodeWarning = ( + + -

- - - - -
-
- - -
- ); - } - - return ( - - - {configuredByNodeWarning} - - -

- -

-
+ } + color="warning" + iconType="help" + > + +

+ +

- + + + +
+ - - - - - - - - + +
+ ); + } - - - - - - - - - - - + return ( + + + {configuredByNodeWarning} - - {connectedNodesCount} - - -
+ +

+ +

+
- - - - - - - + + + + + + + + + + + + + + + + + + + + + + + {connectedNodesCount} + + + + + + + + + + + + + + + + {seeds.map(seed => {seed})} + + + + + + + + + + + + {this.renderSkipUnavailableValue(skipUnavailable)} + + + + + + + + + + + + + + + + {maxConnectionsPerCluster} + + + + + + + + + + + + {initialConnectTimeout} + + + + + + + ); + } - - {seeds.map(seed => {seed})} - + renderContent() { + const { + isLoading, + cluster, + } = this.props; + + if (isLoading) { + return ( + + + + - - - + + + - - - - - {this.renderSkipUnavailableValue(skipUnavailable)} - + + - - - - - - - - - - - - - {maxConnectionsPerCluster} - + + ); + } + + if (!cluster) { + return ( + + + + - - - + + + - - - - - {initialConnectTimeout} - + + - - - - ); - } + + ); + } - renderContent() { - const { - isLoading, - cluster, - } = this.props; + return this.renderCluster(); + } - if (isLoading) { - return ( - - - - - + renderFooter() { + const { + cluster, + clusterName, + } = this.props; - - - - - - - - - - ); - } + // Remote clusters configured by a node's elasticsearch.yml file can't be edited or removeed. + if (!cluster || cluster.isConfiguredByNode) { + return null; + } - if (!cluster) { return ( - - + + - + - - - - - + + + - + ); } - return this.renderCluster(); - } + render() { + const { + isOpen, + closeDetailPanel, + clusterName, + } = this.props; - renderFooter() { - const { - cluster, - clusterName, - } = this.props; + if (!isOpen) { + return null; + } - // Remote clusters configured by a node's elasticsearch.yml file can't be edited or removeed. - if (!cluster || cluster.isConfiguredByNode) { - return null; - } - - return ( - - - - - - - - - - - - - - ); - } - - render() { - const { - isOpen, - closeDetailPanel, - clusterName, - } = this.props; - - if (!isOpen) { - return null; + return ( + + + +

{clusterName}

+
+
+ + {this.renderContent()} + + {this.renderFooter()} +
+ ); } - - return ( - - - -

{clusterName}

-
-
- - {this.renderContent()} - - {this.renderFooter()} -
- ); } -} - -export const DetailPanel = injectI18n(DetailPanelUi); +); diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_list.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_list.js index c6fab4f78b40f..f0a988b0ad65d 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_list.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_list.js @@ -43,264 +43,264 @@ import { const REFRESH_RATE_MS = 30000; -export class RemoteClusterListUi extends Component { - static propTypes = { - loadClusters: PropTypes.func.isRequired, - refreshClusters: PropTypes.func.isRequired, - openDetailPanel: PropTypes.func.isRequired, - closeDetailPanel: PropTypes.func.isRequired, - isDetailPanelOpen: PropTypes.bool, - clusters: PropTypes.array, - isLoading: PropTypes.bool, - isCopyingCluster: PropTypes.bool, - isRemovingCluster: PropTypes.bool, - } +export const RemoteClusterList = injectI18n( + class extends Component { + static propTypes = { + loadClusters: PropTypes.func.isRequired, + refreshClusters: PropTypes.func.isRequired, + openDetailPanel: PropTypes.func.isRequired, + closeDetailPanel: PropTypes.func.isRequired, + isDetailPanelOpen: PropTypes.bool, + clusters: PropTypes.array, + isLoading: PropTypes.bool, + isCopyingCluster: PropTypes.bool, + isRemovingCluster: PropTypes.bool, + } - static getDerivedStateFromProps(props) { - const { - openDetailPanel, - closeDetailPanel, - isDetailPanelOpen, - history: { - location: { - search, + static getDerivedStateFromProps(props) { + const { + openDetailPanel, + closeDetailPanel, + isDetailPanelOpen, + history: { + location: { + search, + }, }, - }, - } = props; - - const { cluster: clusterName } = extractQueryParams(search); - - // Show deeplinked remoteCluster whenever remoteClusters get loaded or the URL changes. - if (clusterName != null) { - openDetailPanel(clusterName); - } else if (isDetailPanelOpen) { - closeDetailPanel(); - } + } = props; - return null; - } + const { cluster: clusterName } = extractQueryParams(search); - state = {}; + // Show deeplinked remoteCluster whenever remoteClusters get loaded or the URL changes. + if (clusterName != null) { + openDetailPanel(clusterName); + } else if (isDetailPanelOpen) { + closeDetailPanel(); + } - componentDidMount() { - this.props.loadClusters(); - this.interval = setInterval(this.props.refreshClusters, REFRESH_RATE_MS); - chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); - } + return null; + } - componentWillUnmount() { - clearInterval(this.interval); + state = {}; - // Close the panel, otherwise it will default to already being open when we navigate back to - // this page. - this.props.closeDetailPanel(); - } + componentDidMount() { + this.props.loadClusters(); + this.interval = setInterval(this.props.refreshClusters, REFRESH_RATE_MS); + chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); + } - getHeaderSection() { - return ( - - -

- -

-
-
- ); - } + componentWillUnmount() { + clearInterval(this.interval); - renderBlockingAction() { - const { isCopyingCluster, isRemovingCluster } = this.props; + // Close the panel, otherwise it will default to already being open when we navigate back to + // this page. + this.props.closeDetailPanel(); + } - if (isCopyingCluster || isRemovingCluster) { + getHeaderSection() { return ( - - - + + +

+ +

+
+
); } - return null; - } + renderBlockingAction() { + const { isCopyingCluster, isRemovingCluster } = this.props; - renderNoPermission() { - const { intl } = this.props; - const title = intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.noPermissionTitle', - defaultMessage: 'Permission error', - }); - return ( - - {this.getHeaderSection()} - - - - - - ); - } + if (isCopyingCluster || isRemovingCluster) { + return ( + + + + ); + } - renderError(error) { - // We can safely depend upon the shape of this error coming from Angular $http, because we - // handle unexpected error shapes in the API action. - const { - statusCode, - error: errorString, - } = error.data; - - const { intl } = this.props; - const title = intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.loadingErrorTitle', - defaultMessage: 'Error loading remote clusters', - }); - return ( - - {this.getHeaderSection()} - - - {statusCode} {errorString} - - - ); - } + return null; + } - renderEmpty() { - return ( - - - - )} - body={ - -

- -

-
- } - actions={ - + {this.getHeaderSection()} + + - - } - /> - ); - } - - renderList() { - const { isLoading, clusters } = this.props; - - let table; - - if (isLoading) { - table = ( - - - - - - - - - - - - - + + ); - } else { - table = ; } - return ( - - + renderError(error) { + // We can safely depend upon the shape of this error coming from Angular $http, because we + // handle unexpected error shapes in the API action. + const { + statusCode, + error: errorString, + } = error.data; + + const { intl } = this.props; + const title = intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterList.loadingErrorTitle', + defaultMessage: 'Error loading remote clusters', + }); + return ( + {this.getHeaderSection()} + + + {statusCode} {errorString} + + + ); + } - + renderEmpty() { + return ( + + + + )} + body={ + +

+ +

+
+ } + actions={ -
-
+ } + /> + ); + } - {table} + renderList() { + const { isLoading, clusters } = this.props; - -
- ); - } + let table; + + if (isLoading) { + table = ( + + + + + + + + + + + + + + ); + } else { + table = ; + } + + return ( + + + {this.getHeaderSection()} + + + + + + + + + {table} + + + + ); + } - render() { - const { isLoading, clusters, clusterLoadError } = this.props; + render() { + const { isLoading, clusters, clusterLoadError } = this.props; - let content; + let content; - if (clusterLoadError) { - if (clusterLoadError.status === 403) { - content = this.renderNoPermission(); + if (clusterLoadError) { + if (clusterLoadError.status === 403) { + content = this.renderNoPermission(); + } else { + content = this.renderError(clusterLoadError); + } + } else if (!isLoading && !clusters.length) { + content = this.renderEmpty(); } else { - content = this.renderError(clusterLoadError); + content = this.renderList(); } - } else if (!isLoading && !clusters.length) { - content = this.renderEmpty(); - } else { - content = this.renderList(); - } - return ( - - - - {content} - - {this.renderBlockingAction()} - - - - ); + return ( + + + + {content} + + {this.renderBlockingAction()} + + + + ); + } } -} - -export const RemoteClusterList = injectI18n(RemoteClusterListUi); +); diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index 8ee616f17e482..f01bff36f5126 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -22,229 +22,229 @@ import { import { CRUD_APP_BASE_PATH } from '../../../constants'; import { ConnectionStatus, RemoveClusterButton } from '../components'; -export class RemoteClusterTableUi extends Component { - static propTypes = { - clusters: PropTypes.array, - openDetailPanel: PropTypes.func.isRequired, - } +export const RemoteClusterTable = injectI18n( + class extends Component { + static propTypes = { + clusters: PropTypes.array, + openDetailPanel: PropTypes.func.isRequired, + } - static defaultProps = { - clusters: [], - } + static defaultProps = { + clusters: [], + } + + constructor(props) { + super(props); - constructor(props) { - super(props); + this.state = { + queryText: undefined, + selectedItems: [], + }; + } - this.state = { - queryText: undefined, - 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, - }); - }; - - getFilteredClusters = () => { - const { clusters } = this.props; - const { queryText } = this.state; - - if (queryText) { - return clusters.filter(cluster => { - const { name, seeds } = cluster; - const normalizedName = name.toLowerCase(); - if (normalizedName.toLowerCase().includes(queryText)) { - return true; - } + getFilteredClusters = () => { + const { clusters } = this.props; + const { queryText } = this.state; + + if (queryText) { + return clusters.filter(cluster => { + const { name, seeds } = cluster; + const normalizedName = name.toLowerCase(); + if (normalizedName.toLowerCase().includes(queryText)) { + return true; + } + + return seeds.some(seed => seed.includes(queryText)); + }); + } else { + return clusters.slice(0); + } + }; - return seeds.some(seed => seed.includes(queryText)); - }); - } else { - return clusters.slice(0); - } - }; - - render() { - const { - openDetailPanel, - intl, - } = this.props; - - const { - selectedItems, - } = this.state; - - const columns = [{ - field: 'name', - name: intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.table.nameColumnTitle', - defaultMessage: 'Name', - }), - sortable: true, - truncateText: false, - render: (name, { isConfiguredByNode }) => { - const link = ( - openDetailPanel(name)}> - {name} - - ); - - if (isConfiguredByNode) { - return ( - - - {link} - - - - - )} - /> - - + render() { + const { + openDetailPanel, + intl, + } = this.props; + + const { + selectedItems, + } = this.state; + + const columns = [{ + field: 'name', + name: intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterList.table.nameColumnTitle', + defaultMessage: 'Name', + }), + sortable: true, + truncateText: false, + render: (name, { isConfiguredByNode }) => { + const link = ( + openDetailPanel(name)}> + {name} + ); - } - return link; - } - }, { - field: 'seeds', - name: intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.table.seedsColumnTitle', - defaultMessage: 'Seeds', - }), - truncateText: true, - render: (seeds) => seeds.join(', '), - }, { - field: 'isConnected', - name: intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.table.connectedColumnTitle', - defaultMessage: 'Connection', - }), - sortable: true, - render: (isConnected) => , - width: '160px', - }, { - field: 'connectedNodesCount', - name: intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.table.connectedNodesColumnTitle', - defaultMessage: 'Connected nodes', - }), - sortable: true, - width: '160px', - }, { - name: intl.formatMessage({ - id: 'xpack.remoteClusters.remoteClusterList.table.actionsColumnTitle', - defaultMessage: 'Actions', - }), - width: '100px', - actions: [{ - render: ({ name, isConfiguredByNode }) => { - const label = isConfiguredByNode - ? i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionBlockedDeleteDescription', { - defaultMessage: `Remote clusters defined in elasticsearch.yml can't be deleted`, - }) : i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionDeleteDescription', { - defaultMessage: 'Delete remote cluster', - }); - - return ( - - + if (isConfiguredByNode) { + return ( + + + {link} + + + + + )} + /> + + + ); + } + + return link; + } + }, { + field: 'seeds', + name: intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterList.table.seedsColumnTitle', + defaultMessage: 'Seeds', + }), + truncateText: true, + render: (seeds) => seeds.join(', '), + }, { + field: 'isConnected', + name: intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterList.table.connectedColumnTitle', + defaultMessage: 'Connection', + }), + sortable: true, + render: (isConnected) => , + width: '160px', + }, { + field: 'connectedNodesCount', + name: intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterList.table.connectedNodesColumnTitle', + defaultMessage: 'Connected nodes', + }), + sortable: true, + width: '160px', + }, { + name: intl.formatMessage({ + id: 'xpack.remoteClusters.remoteClusterList.table.actionsColumnTitle', + defaultMessage: 'Actions', + }), + width: '100px', + actions: [{ + render: ({ name, isConfiguredByNode }) => { + const label = isConfiguredByNode + ? i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionBlockedDeleteDescription', { + defaultMessage: `Remote clusters defined in elasticsearch.yml can't be deleted`, + }) : i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionDeleteDescription', { + defaultMessage: 'Delete remote cluster', + }); + + return ( + + + + + + ); + }, + }, { + render: ({ name, isConfiguredByNode }) => { + const label = isConfiguredByNode + ? i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionBlockedEditDescription', { + defaultMessage: `Remote clusters defined in elasticsearch.yml can't be edited`, + }) : i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionEditDescription', { + defaultMessage: 'Edit remote cluster', + }); + + return ( + - - - ); - }, - }, { - render: ({ name, isConfiguredByNode }) => { - const label = isConfiguredByNode - ? i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionBlockedEditDescription', { - defaultMessage: `Remote clusters defined in elasticsearch.yml can't be edited`, - }) : i18n.translate('xpack.remoteClusters.remoteClusterList.table.actionEditDescription', { - defaultMessage: 'Edit remote cluster', - }); - - return ( - - - - ); + + ); + }, + }], + }]; + + const sorting = { + sort: { + field: 'name', + direction: 'asc', + } + }; + + const search = { + toolsLeft: selectedItems.length ? ( + name)} + /> + ) : undefined, + onChange: this.onSearch, + box: { + incremental: true, }, - }], - }]; - - const sorting = { - sort: { - field: 'name', - direction: 'asc', - } - }; - - const search = { - toolsLeft: selectedItems.length ? ( - name)} + }; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50] + }; + + const selection = { + onSelectionChange: (selectedItems) => this.setState({ selectedItems }), + }; + + const filteredClusters = this.getFilteredClusters(); + + return ( + - ) : undefined, - onChange: this.onSearch, - box: { - incremental: true, - }, - }; - - const pagination = { - initialPageSize: 20, - pageSizeOptions: [10, 20, 50] - }; - - const selection = { - onSelectionChange: (selectedItems) => this.setState({ selectedItems }), - }; - - const filteredClusters = this.getFilteredClusters(); - - return ( - - ); + ); + } } -} - -export const RemoteClusterTable = injectI18n(RemoteClusterTableUi); +); diff --git a/x-pack/plugins/remote_clusters/public/services/breadcrumbs.js b/x-pack/plugins/remote_clusters/public/services/breadcrumbs.js index d92ecaef68b99..e8cdea4930efa 100644 --- a/x-pack/plugins/remote_clusters/public/services/breadcrumbs.js +++ b/x-pack/plugins/remote_clusters/public/services/breadcrumbs.js @@ -30,7 +30,7 @@ export const addBreadcrumb = { }; export const editBreadcrumb = { - text: i18n.translate('xpack.remoteClusters.addBreadcrumbTitle', { + text: i18n.translate('xpack.remoteClusters.editBreadcrumbTitle', { defaultMessage: 'Edit', }), }; diff --git a/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js b/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js index b7bf1cd5ccfe6..2b872a061c53f 100644 --- a/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js +++ b/x-pack/plugins/remote_clusters/server/lib/check_license/check_license.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; + export function checkLicense(xpackLicenseInfo) { const pluginName = 'Remote Clusters'; @@ -14,7 +16,13 @@ export function checkLicense(xpackLicenseInfo) { isAvailable: false, showLinks: true, enableLinks: false, - message: `You cannot use ${pluginName} because license information is not available at this time.` + message: i18n.translate( + 'xpack.remoteClusters.checkLicense.errorUnavailableMessage', + { + defaultMessage: 'You cannot use {pluginName} because license information is not available at this time.', + values: { pluginName }, + }, + ), }; } @@ -35,7 +43,13 @@ export function checkLicense(xpackLicenseInfo) { return { isAvailable: false, showLinks: false, - message: `Your ${licenseType} license does not support ${pluginName}. Please upgrade your license.` + message: i18n.translate( + 'xpack.remoteClusters.checkLicense.errorUnsupportedMessage', + { + defaultMessage: 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', + values: { licenseType, pluginName }, + }, + ), }; } @@ -45,7 +59,14 @@ export function checkLicense(xpackLicenseInfo) { isAvailable: false, showLinks: true, enableLinks: false, - message: `You cannot use ${pluginName} because your ${licenseType} license has expired.` + message: i18n.translate( + 'xpack.remoteClusters.checkLicense.errorExpiredMessage', + { + defaultMessage: 'You cannot use {pluginName} because your {licenseType} license has expired', + values: { licenseType, pluginName }, + }, + ), + message: `.`, }; }