Skip to content
Merged
226 changes: 188 additions & 38 deletions x-pack/plugins/cross_cluster_replication/public/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,25 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Route, Switch, Redirect } from 'react-router-dom';
import chrome from 'ui/chrome';
import { fatalError } from 'ui/notify';
import { i18n } from '@kbn/i18n';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';

import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPageContent,
EuiTitle,
} from '@elastic/eui';

import routing from './services/routing';
import { BASE_PATH } from '../../common/constants';
import { SectionUnauthorized, SectionError } from './components';
import routing from './services/routing';
import { isAvailable, isActive, getReason } from './services/license';
import { loadPermissions } from './services/api';

import {
CrossClusterReplicationHome,
Expand All @@ -19,47 +35,181 @@ import {
FollowerIndexEdit,
} from './sections';

export class App extends Component {
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
createHref: PropTypes.func.isRequired
export const App = injectI18n(
class extends Component {
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
createHref: PropTypes.func.isRequired
}).isRequired
}).isRequired
}).isRequired
}
}

constructor(...args) {
super(...args);
this.registerRouter();
}
constructor(...args) {
super(...args);
this.registerRouter();

componentWillMount() {
routing.userHasLeftApp = false;
}
this.state = {
isFetchingPermissions: false,
fetchPermissionError: undefined,
hasPermission: false,
missingPermissions: [],
};
}

componentWillUnmount() {
routing.userHasLeftApp = true;
}
componentWillMount() {
routing.userHasLeftApp = false;
}

registerRouter() {
const { router } = this.context;
routing.reactRouter = router;
}
componentDidMount() {
this.checkPermissions();
}

render() {
return (
<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>
);
}
}
componentWillUnmount() {
routing.userHasLeftApp = true;
}

async checkPermissions() {
this.setState({
isFetchingPermissions: true,
});

try {
const { hasPermission, missingPermissions } = await loadPermissions();

this.setState({
isFetchingPermissions: false,
hasPermission,
missingPermissions,
});
} catch (error) {
// Expect an error in the shape provided by Angular's $http service.
if (error && error.data) {
return this.setState({
isFetchingPermissions: false,
fetchPermissionError: error,
});
}

// This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened.
fatalError(error, i18n.translate('xpack.crossClusterReplication.app.checkPermissionsFatalErrorTitle', {
defaultMessage: 'Cross Cluster Replication app',
}));
}
}

registerRouter() {
const { router } = this.context;
routing.reactRouter = router;
}

render() {
const {
isFetchingPermissions,
fetchPermissionError,
hasPermission,
missingPermissions,
} = this.state;

if (!isAvailable() || !isActive()) {
return (
<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>
<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 (
<SectionError
title={(
<FormattedMessage
id="xpack.crossClusterReplication.app.permissionCheckErrorTitle"
defaultMessage="Error checking permissions"
/>
)}
error={fetchPermissionError}
/>
);
}

if (!hasPermission) {
return (
<EuiPageContent horizontalPosition="center">
<EuiEmptyPrompt
iconType="securityApp"
iconColor={null}
title={
<h2>
<FormattedMessage
id="xpack.crossClusterReplication.app.deniedPermissionTitle"
defaultMessage="Permission denied"
/>
</h2>}
body={
<p>
<FormattedMessage
id="xpack.crossClusterReplication.app.deniedPermissionDescription"
defaultMessage="You do not have required permissions ({permissions}) for Cross Cluster Replication."
values={{ permissions: missingPermissions.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>
);
}
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -190,15 +190,15 @@ export const FollowerIndexForm = injectI18n(
if (error && error.data) {
// All validation does is check for a name collision, so we can just let the user attempt
// to save the follower index and get an error back from the API.
this.setState({
return this.setState({
isValidatingIndexName: false,
});
}

// This error isn't an HTTP error, so let the fatal error screen tell the user something
// unexpected happened.
fatalError(error, i18n.translate('xpack.crossClusterReplication.followerIndexForm.indexNameValidationFatalErrorTitle', {
defaultMessage: 'Follower Index Forn index name validation',
defaultMessage: 'Follower Index Form index name validation',
}));
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@
*/

import React, { Fragment } from 'react';
import { injectI18n } from '@kbn/i18n/react';

import { EuiCallOut } from '@elastic/eui';

export function SectionUnauthorizedUI({ intl, children }) {
const title = intl.formatMessage({
id: 'xpack.crossClusterReplication.remoteClusterList.noPermissionTitle',
defaultMessage: 'Permission error',
});
export function SectionUnauthorized({ title, children }) {
return (
<Fragment>
<EuiCallOut
Expand All @@ -26,5 +21,3 @@ export function SectionUnauthorizedUI({ intl, children }) {
</Fragment>
);
}

export const SectionUnauthorized = injectI18n(SectionUnauthorizedUI);
Loading