Translate security/roles component (#23984)

Translate security/roles component
This commit is contained in:
tibmt 2018-11-20 10:22:10 +03:00 committed by pavel06081991
parent 7baea1d737
commit cf64825ff2
38 changed files with 1495 additions and 487 deletions

View file

@ -43,7 +43,11 @@ exports[`it renders without blowing up 1`] = `
onClick={[Function]}
type="button"
>
hide
<FormattedMessage
defaultMessage="hide"
id="xpack.security.management.editRole.collapsiblePanel.hideLinkText"
values={Object {}}
/>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -5,12 +5,12 @@
*/
import { EuiLink } from '@elastic/eui';
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { CollapsiblePanel } from './collapsible_panel';
test('it renders without blowing up', () => {
const wrapper = shallow(
const wrapper = shallowWithIntl(
<CollapsiblePanel iconType="logoElasticsearch" title="Elasticsearch">
<p>child</p>
</CollapsiblePanel>
@ -20,7 +20,7 @@ test('it renders without blowing up', () => {
});
test('it renders children by default', () => {
const wrapper = mount(
const wrapper = mountWithIntl(
<CollapsiblePanel iconType="logoElasticsearch" title="Elasticsearch">
<p className="child">child 1</p>
<p className="child">child 2</p>
@ -32,7 +32,7 @@ test('it renders children by default', () => {
});
test('it hides children when the "hide" link is clicked', () => {
const wrapper = mount(
const wrapper = mountWithIntl(
<CollapsiblePanel iconType="logoElasticsearch" title="Elasticsearch">
<p className="child">child 1</p>
<p className="child">child 2</p>

View file

@ -13,6 +13,7 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
interface Props {
@ -55,7 +56,19 @@ export class CollapsiblePanel extends Component<Props, State> {
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink onClick={this.toggleCollapsed}>{this.state.collapsed ? 'show' : 'hide'}</EuiLink>
<EuiLink onClick={this.toggleCollapsed}>
{this.state.collapsed ? (
<FormattedMessage
id="xpack.security.management.editRole.collapsiblePanel.showLinkText"
defaultMessage="show"
/>
) : (
<FormattedMessage
id="xpack.security.management.editRole.collapsiblePanel.hideLinkText"
defaultMessage="hide"
/>
)}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -9,20 +9,20 @@ import {
// @ts-ignore
EuiConfirmModal,
} from '@elastic/eui';
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { DeleteRoleButton } from './delete_role_button';
test('it renders without crashing', () => {
const deleteHandler = jest.fn();
const wrapper = shallow(<DeleteRoleButton canDelete={true} onDelete={deleteHandler} />);
const wrapper = shallowWithIntl(<DeleteRoleButton canDelete={true} onDelete={deleteHandler} />);
expect(wrapper.find(EuiButtonEmpty)).toHaveLength(1);
expect(deleteHandler).toHaveBeenCalledTimes(0);
});
test('it shows a confirmation dialog when clicked', () => {
const deleteHandler = jest.fn();
const wrapper = mount(<DeleteRoleButton canDelete={true} onDelete={deleteHandler} />);
const wrapper = mountWithIntl(<DeleteRoleButton canDelete={true} onDelete={deleteHandler} />);
wrapper.find(EuiButtonEmpty).simulate('click');
@ -33,7 +33,7 @@ test('it shows a confirmation dialog when clicked', () => {
test('it renders nothing when canDelete is false', () => {
const deleteHandler = jest.fn();
const wrapper = shallow(<DeleteRoleButton canDelete={false} onDelete={deleteHandler} />);
const wrapper = shallowWithIntl(<DeleteRoleButton canDelete={false} onDelete={deleteHandler} />);
expect(wrapper.find('*')).toHaveLength(0);
expect(deleteHandler).toHaveBeenCalledTimes(0);
});

View file

@ -11,6 +11,7 @@ import {
// @ts-ignore
EuiOverlayMask,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
interface Props {
@ -35,7 +36,10 @@ export class DeleteRoleButton extends Component<Props, State> {
return (
<Fragment>
<EuiButtonEmpty color={'danger'} onClick={this.showModal}>
Delete role
<FormattedMessage
id="xpack.security.management.editRoles.deleteRoleButton.deleteRoleButtonLabel"
defaultMessage="Delete role"
/>
</EuiButtonEmpty>
{this.maybeShowModal()}
</Fragment>
@ -49,15 +53,40 @@ export class DeleteRoleButton extends Component<Props, State> {
return (
<EuiOverlayMask>
<EuiConfirmModal
title={'Delete Role'}
title={
<FormattedMessage
id="xpack.security.management.editRoles.deleteRoleButton.deleteRoleTitle"
defaultMessage="Delete Role"
/>
}
onCancel={this.closeModal}
onConfirm={this.onConfirmDelete}
cancelButtonText={"No, don't delete"}
confirmButtonText={'Yes, delete role'}
cancelButtonText={
<FormattedMessage
id="xpack.security.management.editRoles.deleteRoleButton.cancelButtonLabel"
defaultMessage="No, don't delete"
/>
}
confirmButtonText={
<FormattedMessage
id="xpack.security.management.editRoles.deleteRoleButton.confirmButtonLabel"
defaultMessage="Yes, delete role"
/>
}
buttonColor={'danger'}
>
<p>Are you sure you want to delete this role?</p>
<p>This action cannot be undone!</p>
<p>
<FormattedMessage
id="xpack.security.management.editRoles.deleteRoleButton.deletingRoleConfirmationText"
defaultMessage="Are you sure you want to delete this role?"
/>
</p>
<p>
<FormattedMessage
id="xpack.security.management.editRoles.deleteRoleButton.deletingRoleWarningText"
defaultMessage="This action cannot be undone!"
/>
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
EuiButton,
EuiButtonEmpty,
@ -19,6 +20,7 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { get } from 'lodash';
import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react';
import { toastNotifications } from 'ui/notify';
@ -47,6 +49,7 @@ interface Props {
spaces?: Space[];
spacesEnabled: boolean;
userProfile: UserProfile;
intl: InjectedIntl;
}
interface State {
@ -54,7 +57,7 @@ interface State {
formError: RoleValidationResult | null;
}
export class EditRolePage extends Component<Props, State> {
class EditRolePageUI extends Component<Props, State> {
private validator: RoleValidator;
constructor(props: Props) {
@ -67,9 +70,17 @@ export class EditRolePage extends Component<Props, State> {
}
public render() {
const description = this.props.spacesEnabled
? `Set privileges on your Elasticsearch data and control access to your Kibana spaces.`
: `Set privileges on your Elasticsearch data and control access to Kibana.`;
const description = this.props.spacesEnabled ? (
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to your Kibana spaces."
/>
) : (
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to Kibana."
/>
);
return (
<EuiPage className="editRolePage" restrictWidth>
@ -86,7 +97,10 @@ export class EditRolePage extends Component<Props, State> {
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p id="reservedRoleDescription" tabIndex={1}>
Reserved roles are built-in and cannot be removed or modified.
<FormattedMessage
id="xpack.security.management.editRole.modifyingReversedRolesDescription"
defaultMessage="Reserved roles are built-in and cannot be removed or modified."
/>
</p>
</EuiText>
</Fragment>
@ -115,12 +129,27 @@ export class EditRolePage extends Component<Props, State> {
tabIndex: 0,
};
if (isReservedRole(this.props.role)) {
titleText = 'Viewing role';
titleText = (
<FormattedMessage
id="xpack.security.management.editRole.viewingRoleTitle"
defaultMessage="Viewing role"
/>
);
props['aria-describedby'] = 'reservedRoleDescription';
} else if (this.editingExistingRole()) {
titleText = 'Edit role';
titleText = (
<FormattedMessage
id="xpack.security.management.editRole.editRoleTitle"
defaultMessage="Edit role"
/>
);
} else {
titleText = 'Create role';
titleText = (
<FormattedMessage
id="xpack.security.management.editRole.createRoleTitle"
defaultMessage="Create role"
/>
);
}
return (
@ -148,11 +177,21 @@ export class EditRolePage extends Component<Props, State> {
return (
<EuiPanel>
<EuiFormRow
label={'Role name'}
label={
<FormattedMessage
id="xpack.security.management.editRole.roleNameFormRowTitle"
defaultMessage="Role name"
/>
}
helpText={
!isReservedRole(this.props.role) && this.editingExistingRole()
? "A role's name cannot be changed once it has been created."
: undefined
!isReservedRole(this.props.role) && this.editingExistingRole() ? (
<FormattedMessage
id="xpack.security.management.editRole.roleNameFormRowHelpText"
defaultMessage="A role's name cannot be changed once it has been created."
/>
) : (
undefined
)
}
{...this.validator.validateRoleName(this.state.role)}
>
@ -225,10 +264,27 @@ export class EditRolePage extends Component<Props, State> {
public getFormButtons = () => {
if (isReservedRole(this.props.role)) {
return <EuiButton onClick={this.backToRoleList}>Return to role list</EuiButton>;
return (
<EuiButton onClick={this.backToRoleList}>
<FormattedMessage
id="xpack.security.management.editRole.returnToRoleListButtonLabel"
defaultMessage="Return to role list"
/>
</EuiButton>
);
}
const saveText = this.editingExistingRole() ? 'Update role' : 'Create role';
const saveText = this.editingExistingRole() ? (
<FormattedMessage
id="xpack.security.management.editRole.updateRoleText"
defaultMessage="Update role"
/>
) : (
<FormattedMessage
id="xpack.security.management.editRole.createRoleText"
defaultMessage="Create role"
/>
);
return (
<EuiFlexGroup responsive={false}>
@ -244,7 +300,10 @@ export class EditRolePage extends Component<Props, State> {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty data-test-subj={`roleFormCancelButton`} onClick={this.backToRoleList}>
Cancel
<FormattedMessage
id="xpack.security.management.editRole.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={true} />
@ -274,7 +333,7 @@ export class EditRolePage extends Component<Props, State> {
formError: null,
});
const { httpClient } = this.props;
const { httpClient, intl } = this.props;
const role = {
...this.state.role,
@ -287,7 +346,12 @@ export class EditRolePage extends Component<Props, State> {
saveRole(httpClient, role)
.then(() => {
toastNotifications.addSuccess('Saved role');
toastNotifications.addSuccess(
intl.formatMessage({
id: 'xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage',
defaultMessage: 'Saved role',
})
);
this.backToRoleList();
})
.catch((error: any) => {
@ -297,11 +361,16 @@ export class EditRolePage extends Component<Props, State> {
};
public handleDeleteRole = () => {
const { httpClient, role } = this.props;
const { httpClient, role, intl } = this.props;
deleteRole(httpClient, role.name)
.then(() => {
toastNotifications.addSuccess('Deleted role');
toastNotifications.addSuccess(
intl.formatMessage({
id: 'xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage',
defaultMessage: 'Deleted role',
})
);
this.backToRoleList();
})
.catch((error: any) => {
@ -313,3 +382,5 @@ export class EditRolePage extends Component<Props, State> {
window.location.hash = ROLES_PATH;
};
}
export const EditRolePage = injectI18n(EditRolePageUI);

View file

@ -1,181 +1,220 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`it renders without crashing 1`] = `
<CollapsiblePanel
iconType="logoElasticsearch"
title="Elasticsearch"
>
<React.Fragment>
<EuiDescribedFormGroup
description={
<p>
Manage the actions this role can perform against your cluster.
<EuiLink
className="editRole__learnMore"
color="primary"
href="undefinedguide/en/x-pack/undefined/security-privileges.html#security-privileges"
target="_blank"
type="button"
>
Learn more
</EuiLink>
</p>
}
fullWidth={false}
gutterSize="l"
title={
<h3>
Cluster privileges
</h3>
}
titleSize="xs"
>
<EuiFormRow
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={true}
>
<ClusterPrivileges
onChange={[Function]}
role={
Object {
"elasticsearch": Object {
"cluster": Array [],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
}
}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiSpacer
size="l"
/>
<EuiDescribedFormGroup
description={
<p>
Allow requests to be submitted on the behalf of other users.
<EuiLink
className="editRole__learnMore"
color="primary"
href="undefinedguide/en/x-pack/undefined/security-privileges.html#_run_as_privilege"
target="_blank"
type="button"
>
Learn more
</EuiLink>
</p>
}
fullWidth={false}
gutterSize="l"
title={
<h3>
Run As privileges
</h3>
}
titleSize="xs"
>
<EuiFormRow
describedByIds={Array []}
<I18nProvider>
<CollapsiblePanel
iconType="logoElasticsearch"
title="Elasticsearch"
>
<React.Fragment>
<EuiDescribedFormGroup
description={
<p>
<FormattedMessage
defaultMessage="Manage the actions this role can perform against your cluster. "
id="xpack.security.management.editRoles.elasticSearchPrivileges.manageRoleActionsDescription"
values={Object {}}
/>
<EuiLink
className="editRole__learnMore"
color="primary"
href="undefinedguide/en/x-pack/undefined/security-privileges.html#security-privileges"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="Learn more"
id="xpack.security.management.editRoles.elasticSearchPrivileges.learnMoreLinkText"
values={Object {}}
/>
</EuiLink>
</p>
}
fullWidth={false}
hasEmptyLabelSpace={true}
gutterSize="l"
title={
<h3>
<FormattedMessage
defaultMessage="Cluster privileges"
id="xpack.security.management.editRoles.elasticSearchPrivileges.clusterPrivilegesTitle"
values={Object {}}
/>
</h3>
}
titleSize="xs"
>
<EuiComboBox
compressed={false}
fullWidth={false}
isClearable={true}
isDisabled={false}
onChange={[Function]}
options={Array []}
placeholder="Add a user..."
selectedOptions={Array []}
singleSelection={false}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiSpacer
size="l"
/>
<EuiTitle
size="xs"
textTransform="none"
>
<h3>
Index privileges
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<EuiText
color="subdued"
grow={true}
size="s"
>
<p>
Control access to the data in your cluster.
<EuiLink
className="editRole__learnMore"
color="primary"
href="undefinedguide/en/x-pack/undefined/security-privileges.html#privileges-list-indices"
target="_blank"
type="button"
<EuiFormRow
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={true}
>
Learn more
</EuiLink>
</p>
</EuiText>
<IndexPrivileges
allowDocumentLevelSecurity={true}
allowFieldLevelSecurity={true}
httpClient={[MockFunction]}
indexPatterns={Array []}
onChange={[MockFunction]}
role={
Object {
"elasticsearch": Object {
"cluster": Array [],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
<ClusterPrivileges
onChange={[Function]}
role={
Object {
"elasticsearch": Object {
"cluster": Array [],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
}
}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiSpacer
size="l"
/>
<EuiDescribedFormGroup
description={
<p>
<FormattedMessage
defaultMessage="Allow requests to be submitted on the behalf of other users. "
id="xpack.security.management.editRoles.elasticSearchPrivileges.howToBeSubmittedOnBehalfOfOtherUsersDescription"
values={Object {}}
/>
<EuiLink
className="editRole__learnMore"
color="primary"
href="undefinedguide/en/x-pack/undefined/security-privileges.html#_run_as_privilege"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="Learn more"
id="xpack.security.management.editRoles.elasticSearchPrivileges.learnMoreLinkText"
values={Object {}}
/>
</EuiLink>
</p>
}
}
validator={
RoleValidator {
"inProgressSpacePrivileges": Array [],
"shouldValidate": undefined,
fullWidth={false}
gutterSize="l"
title={
<h3>
<FormattedMessage
defaultMessage="Run As privileges"
id="xpack.security.management.editRoles.elasticSearchPrivileges.runAsPrivilegesTitle"
values={Object {}}
/>
</h3>
}
}
/>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiButton
color="primary"
fill={false}
iconSide="left"
iconType="plusInCircle"
onClick={[Function]}
size="s"
type="button"
>
Add index privilege
</EuiButton>
</React.Fragment>
</CollapsiblePanel>
titleSize="xs"
>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={true}
>
<EuiComboBox
compressed={false}
fullWidth={false}
isClearable={true}
isDisabled={false}
onChange={[Function]}
options={Array []}
placeholder="Add a user..."
selectedOptions={Array []}
singleSelection={false}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiSpacer
size="l"
/>
<EuiTitle
size="xs"
textTransform="none"
>
<h3>
<FormattedMessage
defaultMessage="Index privileges"
id="xpack.security.management.editRoles.elasticSearchPrivileges.indexPrivilegesTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
size="s"
/>
<EuiText
color="subdued"
grow={true}
size="s"
>
<p>
<FormattedMessage
defaultMessage="Control access to the data in your cluster. "
id="xpack.security.management.editRoles.elasticSearchPrivileges.controlAccessToClusterDataDescription"
values={Object {}}
/>
<EuiLink
className="editRole__learnMore"
color="primary"
href="undefinedguide/en/x-pack/undefined/security-privileges.html#privileges-list-indices"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="Learn more"
id="xpack.security.management.editRoles.elasticSearchPrivileges.learnMoreLinkText"
values={Object {}}
/>
</EuiLink>
</p>
</EuiText>
<IndexPrivileges
allowDocumentLevelSecurity={true}
allowFieldLevelSecurity={true}
httpClient={[MockFunction]}
indexPatterns={Array []}
onChange={[MockFunction]}
role={
Object {
"elasticsearch": Object {
"cluster": Array [],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
}
}
validator={
RoleValidator {
"inProgressSpacePrivileges": Array [],
"shouldValidate": undefined,
}
}
/>
<EuiHorizontalRule
margin="l"
size="full"
/>
<EuiButton
color="primary"
fill={false}
iconSide="left"
iconType="plusInCircle"
onClick={[Function]}
size="s"
type="button"
>
<FormattedMessage
defaultMessage="Add index privilege"
id="xpack.security.management.editRoles.elasticSearchPrivileges.addIndexPrivilegesButtonLabel"
values={Object {}}
/>
</EuiButton>
</React.Fragment>
</CollapsiblePanel>
</I18nProvider>
`;

View file

@ -39,7 +39,13 @@ exports[`it renders without crashing 1`] = `
fullWidth={true}
hasEmptyLabelSpace={false}
isInvalid={false}
label="Indices"
label={
<FormattedMessage
defaultMessage="Indices"
id="xpack.security.management.editRoles.indexPrivilegeForm.indicesFormRowLabel"
values={Object {}}
/>
}
>
<EuiComboBox
compressed={false}
@ -63,7 +69,13 @@ exports[`it renders without crashing 1`] = `
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={false}
label="Privileges"
label={
<FormattedMessage
defaultMessage="Privileges"
id="xpack.security.management.editRoles.indexPrivilegeForm.privilegesFormRowLabel"
values={Object {}}
/>
}
>
<EuiComboBox
compressed={false}
@ -138,8 +150,20 @@ exports[`it renders without crashing 1`] = `
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={false}
helpText="If no fields are granted, then users assigned to this role will not be able to see any data for this index."
label="Granted fields (optional)"
helpText={
<FormattedMessage
defaultMessage="If no fields are granted, then users assigned to this role will not be able to see any data for this index."
id="xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowHelpText"
values={Object {}}
/>
}
label={
<FormattedMessage
defaultMessage="Granted fields (optional)"
id="xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowLabel"
values={Object {}}
/>
}
>
<React.Fragment>
<EuiComboBox
@ -177,7 +201,13 @@ exports[`it renders without crashing 1`] = `
<EuiSwitch
compressed={true}
data-test-subj="restrictDocumentsQuery0"
label="Grant read privileges to specific documents"
label={
<FormattedMessage
defaultMessage="Grant read privileges to specific documents"
id="xpack.security.management.editRoles.indexPrivilegeForm.grantReadPrivilegesLabel"
values={Object {}}
/>
}
onChange={[Function]}
value={false}
/>

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { RoleValidator } from '../../../lib/validate_role';
import { ClusterPrivileges } from './cluster_privileges';
import { ElasticsearchPrivileges } from './elasticsearch_privileges';
@ -34,7 +34,9 @@ test('it renders without crashing', () => {
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
};
const wrapper = shallow(<ElasticsearchPrivileges {...props} />);
const wrapper = shallowWithIntl(
<ElasticsearchPrivileges.WrappedComponent {...props} intl={null as any} />
);
expect(wrapper).toMatchSnapshot();
});
@ -61,7 +63,9 @@ test('it renders ClusterPrivileges', () => {
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
};
const wrapper = mount(<ElasticsearchPrivileges {...props} />);
const wrapper = mountWithIntl(
<ElasticsearchPrivileges.WrappedComponent {...props} intl={null as any} />
);
expect(wrapper.find(ClusterPrivileges)).toHaveLength(1);
});
@ -88,6 +92,8 @@ test('it renders IndexPrivileges', () => {
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
};
const wrapper = mount(<ElasticsearchPrivileges {...props} />);
const wrapper = mountWithIntl(
<ElasticsearchPrivileges.WrappedComponent {...props} intl={null as any} />
);
expect(wrapper.find(IndexPrivileges)).toHaveLength(1);
});

View file

@ -16,6 +16,7 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, I18nProvider, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
import { Role } from '../../../../../../../common/model/role';
// @ts-ignore
@ -36,14 +37,17 @@ interface Props {
indexPatterns: string[];
allowDocumentLevelSecurity: boolean;
allowFieldLevelSecurity: boolean;
intl: InjectedIntl;
}
export class ElasticsearchPrivileges extends Component<Props, {}> {
class ElasticsearchPrivilegesUI extends Component<Props, {}> {
public render() {
return (
<CollapsiblePanel iconType={'logoElasticsearch'} title={'Elasticsearch'}>
{this.getForm()}
</CollapsiblePanel>
<I18nProvider>
<CollapsiblePanel iconType={'logoElasticsearch'} title={'Elasticsearch'}>
{this.getForm()}
</CollapsiblePanel>
</I18nProvider>
);
}
@ -56,6 +60,7 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
indexPatterns,
allowDocumentLevelSecurity,
allowFieldLevelSecurity,
intl,
} = this.props;
const indexProps = {
@ -71,10 +76,20 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
return (
<Fragment>
<EuiDescribedFormGroup
title={<h3>Cluster privileges</h3>}
title={
<h3>
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.clusterPrivilegesTitle"
defaultMessage="Cluster privileges"
/>
</h3>
}
description={
<p>
Manage the actions this role can perform against your cluster.{' '}
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.manageRoleActionsDescription"
defaultMessage="Manage the actions this role can perform against your cluster. "
/>
{this.learnMore(documentationLinks.esClusterPrivileges)}
</p>
}
@ -87,17 +102,35 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
<EuiSpacer />
<EuiDescribedFormGroup
title={<h3>Run As privileges</h3>}
title={
<h3>
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.runAsPrivilegesTitle"
defaultMessage="Run As privileges"
/>
</h3>
}
description={
<p>
Allow requests to be submitted on the behalf of other users.{' '}
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.howToBeSubmittedOnBehalfOfOtherUsersDescription"
defaultMessage="Allow requests to be submitted on the behalf of other users. "
/>
{this.learnMore(documentationLinks.esRunAsPrivileges)}
</p>
}
>
<EuiFormRow hasEmptyLabelSpace>
<EuiComboBox
placeholder={this.props.editable ? 'Add a user...' : undefined}
placeholder={
this.props.editable
? intl.formatMessage({
id:
'xpack.security.management.editRoles.elasticSearchPrivileges.addUserTitle',
defaultMessage: 'Add a user...',
})
: undefined
}
options={this.props.runAsUsers.map(username => ({
id: username,
label: username,
@ -113,12 +146,20 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
<EuiSpacer />
<EuiTitle size={'xs'}>
<h3>Index privileges</h3>
<h3>
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.indexPrivilegesTitle"
defaultMessage="Index privileges"
/>
</h3>
</EuiTitle>
<EuiSpacer size={'s'} />
<EuiText size={'s'} color={'subdued'}>
<p>
Control access to the data in your cluster.{' '}
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.controlAccessToClusterDataDescription"
defaultMessage="Control access to the data in your cluster. "
/>
{this.learnMore(documentationLinks.esIndicesPrivileges)}
</p>
</EuiText>
@ -129,7 +170,10 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
{this.props.editable && (
<EuiButton size={'s'} iconType={'plusInCircle'} onClick={this.addIndexPrivilege}>
Add index privilege
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.addIndexPrivilegesButtonLabel"
defaultMessage="Add index privilege"
/>
</EuiButton>
)}
</Fragment>
@ -138,7 +182,10 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
public learnMore = (href: string) => (
<EuiLink className="editRole__learnMore" href={href} target={'_blank'}>
Learn more
<FormattedMessage
id="xpack.security.management.editRoles.elasticSearchPrivileges.learnMoreLinkText"
defaultMessage="Learn more"
/>
</EuiLink>
);
@ -189,3 +236,5 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
this.props.onChange(role);
};
}
export const ElasticsearchPrivileges = injectI18n(ElasticsearchPrivilegesUI);

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButtonIcon, EuiSwitch, EuiTextArea } from '@elastic/eui';
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { RoleValidator } from '../../../lib/validate_role';
import { IndexPrivilegeForm } from './index_privilege_form';
@ -31,7 +31,7 @@ test('it renders without crashing', () => {
onDelete: jest.fn(),
};
const wrapper = shallow(<IndexPrivilegeForm {...props} />);
const wrapper = shallowWithIntl(<IndexPrivilegeForm {...props} />);
expect(wrapper).toMatchSnapshot();
});
@ -62,7 +62,7 @@ describe('delete button', () => {
...props,
allowDelete: false,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find(EuiButtonIcon)).toHaveLength(0);
});
@ -71,7 +71,7 @@ describe('delete button', () => {
...props,
allowDelete: true,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find(EuiButtonIcon)).toHaveLength(1);
});
@ -80,7 +80,7 @@ describe('delete button', () => {
...props,
allowDelete: true,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
wrapper.find(EuiButtonIcon).simulate('click');
expect(testProps.onDelete).toHaveBeenCalledTimes(1);
});
@ -114,7 +114,7 @@ describe(`document level security`, () => {
allowDocumentLevelSecurity: false,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find(EuiSwitch)).toHaveLength(0);
expect(wrapper.find(EuiTextArea)).toHaveLength(0);
});
@ -128,7 +128,7 @@ describe(`document level security`, () => {
},
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find(EuiSwitch)).toHaveLength(1);
expect(wrapper.find(EuiTextArea)).toHaveLength(0);
});
@ -138,7 +138,7 @@ describe(`document level security`, () => {
...props,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find(EuiSwitch)).toHaveLength(1);
expect(wrapper.find(EuiTextArea)).toHaveLength(1);
});
@ -172,7 +172,7 @@ describe('field level security', () => {
allowFieldLevelSecurity: false,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find('.indexPrivilegeForm__grantedFieldsRow')).toHaveLength(0);
});
@ -181,7 +181,7 @@ describe('field level security', () => {
...props,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find('div.indexPrivilegeForm__grantedFieldsRow')).toHaveLength(1);
});
@ -196,7 +196,7 @@ describe('field level security', () => {
},
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find('div.indexPrivilegeForm__grantedFieldsRow')).toHaveLength(1);
expect(wrapper.find('.euiFormHelpText')).toHaveLength(1);
});
@ -206,7 +206,7 @@ describe('field level security', () => {
...props,
};
const wrapper = mount(<IndexPrivilegeForm {...testProps} />);
const wrapper = mountWithIntl(<IndexPrivilegeForm {...testProps} />);
expect(wrapper.find('div.indexPrivilegeForm__grantedFieldsRow')).toHaveLength(1);
expect(wrapper.find('.euiFormHelpText')).toHaveLength(0);
});

View file

@ -15,6 +15,7 @@ import {
EuiSwitch,
EuiTextArea,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { ChangeEvent, Component, Fragment } from 'react';
import { IndexPrivilege } from '../../../../../../../common/model/index_privilege';
// @ts-ignore
@ -81,7 +82,12 @@ export class IndexPrivilegeForm extends Component<Props, State> {
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={'Indices'}
label={
<FormattedMessage
id="xpack.security.management.editRoles.indexPrivilegeForm.indicesFormRowLabel"
defaultMessage="Indices"
/>
}
fullWidth={true}
{...this.props.validator.validateIndexPrivilege(this.props.indexPrivilege)}
>
@ -96,7 +102,15 @@ export class IndexPrivilegeForm extends Component<Props, State> {
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label={'Privileges'} fullWidth={true}>
<EuiFormRow
label={
<FormattedMessage
id="xpack.security.management.editRoles.indexPrivilegeForm.privilegesFormRowLabel"
defaultMessage="Privileges"
/>
}
fullWidth={true}
>
<EuiComboBox
data-test-subj={`privilegesInput${this.props.formIndex}`}
options={getIndexPrivileges().map(toOption)}
@ -129,13 +143,23 @@ export class IndexPrivilegeForm extends Component<Props, State> {
return (
<EuiFlexItem>
<EuiFormRow
label={'Granted fields (optional)'}
label={
<FormattedMessage
id="xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowLabel"
defaultMessage="Granted fields (optional)"
/>
}
fullWidth={true}
className="indexPrivilegeForm__grantedFieldsRow"
helpText={
!isReservedRole && grant.length === 0
? 'If no fields are granted, then users assigned to this role will not be able to see any data for this index.'
: undefined
!isReservedRole && grant.length === 0 ? (
<FormattedMessage
id="xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowHelpText"
defaultMessage="If no fields are granted, then users assigned to this role will not be able to see any data for this index."
/>
) : (
undefined
)
}
>
<Fragment>
@ -170,7 +194,12 @@ export class IndexPrivilegeForm extends Component<Props, State> {
<EuiFlexItem>
<EuiSwitch
data-test-subj={`restrictDocumentsQuery${this.props.formIndex}`}
label={'Grant read privileges to specific documents'}
label={
<FormattedMessage
id="xpack.security.management.editRoles.indexPrivilegeForm.grantReadPrivilegesLabel"
defaultMessage="Grant read privileges to specific documents"
/>
}
// @ts-ignore
compressed={true}
// @ts-ignore
@ -181,7 +210,15 @@ export class IndexPrivilegeForm extends Component<Props, State> {
)}
{this.state.queryExpanded && (
<EuiFlexItem>
<EuiFormRow label={'Granted documents query'} fullWidth={true}>
<EuiFormRow
label={
<FormattedMessage
id="xpack.security.management.editRoles.indexPrivilegeForm.grantedDocumentsQueryFormRowLabel"
defaultMessage="Granted documents query"
/>
}
fullWidth={true}
>
<EuiTextArea
data-test-subj={`queryInput${this.props.formIndex}`}
style={{ resize: 'none' }}

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { RoleValidator } from '../../../lib/validate_role';
import { IndexPrivilegeForm } from './index_privilege_form';
import { IndexPrivileges } from './index_privileges';
@ -31,7 +31,7 @@ test('it renders without crashing', () => {
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
};
const wrapper = shallow(<IndexPrivileges {...props} />);
const wrapper = shallowWithIntl(<IndexPrivileges {...props} />);
expect(wrapper).toMatchSnapshot();
});
@ -65,6 +65,6 @@ test('it renders a IndexPrivilegeForm for each privilege on the role', () => {
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
};
const wrapper = mount(<IndexPrivileges {...props} />);
const wrapper = mountWithIntl(<IndexPrivileges {...props} />);
expect(wrapper.find(IndexPrivilegeForm)).toHaveLength(1);
});

View file

@ -10,7 +10,11 @@ exports[`<ImpactedSpacesFlyout> renders without crashing 1`] = `
onClick={[Function]}
type="button"
>
View summary of spaces privileges
<FormattedMessage
defaultMessage="View summary of spaces privileges"
id="xpack.security.management.editRoles.impactedSpacesFlyout.viewSpacesPrivilegesSummaryLinkText"
values={Object {}}
/>
</EuiLink>
</div>
</React.Fragment>

View file

@ -1,56 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<KibanaPrivileges> renders without crashing 1`] = `
<CollapsiblePanel
iconType="logoKibana"
title="Kibana"
>
<SpaceAwarePrivilegeForm
editable={true}
kibanaAppPrivileges={
Array [
"all",
]
}
onChange={[MockFunction]}
role={
Object {
"elasticsearch": Object {
"cluster": Array [],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
<I18nProvider>
<CollapsiblePanel
iconType="logoKibana"
title="Kibana"
>
<InjectIntl(SpaceAwarePrivilegeFormUI)
editable={true}
kibanaAppPrivileges={
Array [
"all",
]
}
}
spaces={
Array [
onChange={[MockFunction]}
role={
Object {
"_reserved": true,
"id": "default",
"name": "Default Space",
},
"elasticsearch": Object {
"cluster": Array [],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
}
}
spaces={
Array [
Object {
"_reserved": true,
"id": "default",
"name": "Default Space",
},
Object {
"id": "marketing",
"name": "Marketing",
},
]
}
userProfile={
Object {
"id": "marketing",
"name": "Marketing",
},
]
}
userProfile={
Object {
"hasCapability": [Function],
"hasCapability": [Function],
}
}
}
validator={
RoleValidator {
"inProgressSpacePrivileges": Array [],
"shouldValidate": undefined,
validator={
RoleValidator {
"inProgressSpacePrivileges": Array [],
"shouldValidate": undefined,
}
}
}
/>
</CollapsiblePanel>
/>
</CollapsiblePanel>
</I18nProvider>
`;

View file

@ -3,13 +3,101 @@
exports[`PrivilegeCalloutWarning renders without crashing 1`] = `
<PrivilegeCalloutWarning
basePrivilege="all"
intl={
Object {
"defaultFormats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
isReservedRole={false}
>
<EuiCallOut
color="warning"
iconType="iInCircle"
size="m"
title="Minimum privilege is too high to customize individual spaces"
title={
<FormattedMessage
defaultMessage="Minimum privilege is too high to customize individual spaces"
id="xpack.security.management.editRoles.privilegeCalloutWarning.minimumPrivilegeTitle"
values={Object {}}
/>
}
>
<div
className="euiCallOut euiCallOut--warning"
@ -61,7 +149,13 @@ exports[`PrivilegeCalloutWarning renders without crashing 1`] = `
<span
className="euiCallOutHeader__title"
>
Minimum privilege is too high to customize individual spaces
<FormattedMessage
defaultMessage="Minimum privilege is too high to customize individual spaces"
id="xpack.security.management.editRoles.privilegeCalloutWarning.minimumPrivilegeTitle"
values={Object {}}
>
Minimum privilege is too high to customize individual spaces
</FormattedMessage>
</span>
</div>
<EuiText
@ -72,19 +166,71 @@ exports[`PrivilegeCalloutWarning renders without crashing 1`] = `
className="euiText euiText--small"
>
<p>
Setting the minimum privilege to
<strong>
all
</strong>
grants full access to all spaces. To customize privileges for individual spaces, the minimum privilege must be either
<strong>
read
</strong>
or
<strong>
none
</strong>
.
<FormattedMessage
defaultMessage="Setting the minimum privilege to {allText} grants full access to all
spaces. To customize privileges for individual spaces, the minimum privilege must be
either {readText} or {noneText}."
id="xpack.security.management.editRoles.privilegeCalloutWarning.howToCustomizePrivilegesForIndividualSpacesDescription"
values={
Object {
"allText": <strong>
<FormattedMessage
defaultMessage="all"
id="xpack.security.management.editRoles.privilegeCalloutWarning.allText"
values={Object {}}
/>
</strong>,
"noneText": <strong>
<FormattedMessage
defaultMessage="none"
id="xpack.security.management.editRoles.privilegeCalloutWarning.noneText"
values={Object {}}
/>
</strong>,
"readText": <strong>
<FormattedMessage
defaultMessage="read"
id="xpack.security.management.editRoles.privilegeCalloutWarning.readText"
values={Object {}}
/>
</strong>,
}
}
>
Setting the minimum privilege to
<strong>
<FormattedMessage
defaultMessage="all"
id="xpack.security.management.editRoles.privilegeCalloutWarning.allText"
values={Object {}}
>
all
</FormattedMessage>
</strong>
grants full access to all
spaces. To customize privileges for individual spaces, the minimum privilege must be
either
<strong>
<FormattedMessage
defaultMessage="read"
id="xpack.security.management.editRoles.privilegeCalloutWarning.readText"
values={Object {}}
>
read
</FormattedMessage>
</strong>
or
<strong>
<FormattedMessage
defaultMessage="none"
id="xpack.security.management.editRoles.privilegeCalloutWarning.noneText"
values={Object {}}
>
none
</FormattedMessage>
</strong>
.
</FormattedMessage>
</p>
</div>
</EuiText>

View file

@ -19,7 +19,13 @@ exports[`<PrivilegeSpaceForm> renders without crashing 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={false}
label="Spaces"
label={
<FormattedMessage
defaultMessage="Spaces"
id="xpack.security.management.editRoles.privilegeSpaceForm.spacesFormRowLabel"
values={Object {}}
/>
}
>
<SpaceSelector
onChange={[Function]}
@ -51,7 +57,13 @@ exports[`<PrivilegeSpaceForm> renders without crashing 1`] = `
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={false}
label="Privilege"
label={
<FormattedMessage
defaultMessage="Privilege"
id="xpack.security.management.editRoles.privilegeSpaceForm.privilegeFormRowLabel"
values={Object {}}
/>
}
>
<PrivilegeSelector
availablePrivileges={

View file

@ -5,14 +5,22 @@ exports[`<SimplePrivilegeForm> renders without crashing 1`] = `
<EuiDescribedFormGroup
description={
<p>
Specifies the Kibana privilege for this role.
<FormattedMessage
defaultMessage="Specifies the Kibana privilege for this role."
id="xpack.security.management.editRoles.simplePrivilegeForm.specifyPrivilegeForRoleDescription"
values={Object {}}
/>
</p>
}
fullWidth={false}
gutterSize="l"
title={
<h3>
Kibana privileges
<FormattedMessage
defaultMessage="Kibana privileges"
id="xpack.security.management.editRoles.simplePrivilegeForm.kibanaPrivilegesTitle"
values={Object {}}
/>
</h3>
}
titleSize="xs"

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SpaceAwarePrivilegeForm> hides the space table if there are no existing space privileges 1`] = `
<PrivilegeSpaceTable
<InjectIntl(PrivilegeSpaceTableUI)
availablePrivileges={
Array [
"all",
@ -39,7 +39,129 @@ exports[`<SpaceAwarePrivilegeForm> hides the space table if there are no existin
},
]
}
/>
>
<PrivilegeSpaceTableUI
availablePrivileges={
Array [
"all",
"read",
]
}
intl={
Object {
"defaultFormats": Object {
"date": Object {
"full": Object {
"day": "numeric",
"month": "long",
"weekday": "long",
"year": "numeric",
},
"long": Object {
"day": "numeric",
"month": "long",
"year": "numeric",
},
"medium": Object {
"day": "numeric",
"month": "short",
"year": "numeric",
},
"short": Object {
"day": "numeric",
"month": "numeric",
"year": "2-digit",
},
},
"number": Object {
"currency": Object {
"style": "currency",
},
"percent": Object {
"style": "percent",
},
},
"time": Object {
"full": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"long": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
"timeZoneName": "short",
},
"medium": Object {
"hour": "numeric",
"minute": "numeric",
"second": "numeric",
},
"short": Object {
"hour": "numeric",
"minute": "numeric",
},
},
},
"defaultLocale": "en",
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralFormat": [Function],
"getRelativeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"now": [Function],
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": null,
}
}
onChange={[Function]}
role={
Object {
"elasticsearch": Object {
"cluster": Array [
"manage",
],
"indices": Array [],
"run_as": Array [],
},
"kibana": Object {
"global": Array [],
"space": Object {},
},
"name": "",
}
}
spacePrivileges={Object {}}
spaces={
Array [
Object {
"_reserved": true,
"id": "default",
"name": "Default Space",
},
Object {
"id": "marketing",
"name": "Marketing",
},
]
}
/>
</InjectIntl(PrivilegeSpaceTableUI)>
`;
exports[`<SpaceAwarePrivilegeForm> renders without crashing 1`] = `
@ -47,14 +169,22 @@ exports[`<SpaceAwarePrivilegeForm> renders without crashing 1`] = `
<EuiDescribedFormGroup
description={
<p>
Specify the minimum actions users can perform in your spaces.
<FormattedMessage
defaultMessage="Specify the minimum actions users can perform in your spaces."
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.minimumActionsUserCanPerformInYourSpacesDescription"
values={Object {}}
/>
</p>
}
fullWidth={false}
gutterSize="l"
title={
<h3>
Minimum privileges for all spaces
<FormattedMessage
defaultMessage="Minimum privileges for all spaces"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.minPrivilegesForAllSpacesTitle"
values={Object {}}
/>
</h3>
}
titleSize="xs"
@ -89,7 +219,11 @@ exports[`<SpaceAwarePrivilegeForm> renders without crashing 1`] = `
textTransform="none"
>
<h3>
Higher privileges for individual spaces
<FormattedMessage
defaultMessage="Higher privileges for individual spaces"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.higherPrivilegesForIndividualSpacesTitle"
values={Object {}}
/>
</h3>
</EuiTitle>
<EuiSpacer
@ -101,24 +235,37 @@ exports[`<SpaceAwarePrivilegeForm> renders without crashing 1`] = `
size="s"
>
<p>
Grant more privileges on a per space basis. For example, if the privileges are
<strong>
read
</strong>
for all spaces, you can set the privileges to
<strong>
all
</strong>
for an individual space.
<FormattedMessage
defaultMessage="Grant more privileges on a per space basis. For example, if the privileges are
{read} for all spaces, you can set the privileges to {all}
for an individual space."
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.grantMorePrivilegesTitle"
values={
Object {
"all": <strong>
<FormattedMessage
defaultMessage="all"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.allText"
values={Object {}}
/>
</strong>,
"read": <strong>
<FormattedMessage
defaultMessage="read"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.readText"
values={Object {}}
/>
</strong>,
}
}
/>
</p>
</EuiText>
<EuiSpacer
size="s"
/>
<React.Fragment>
<PrivilegeSpaceTable
<InjectIntl(PrivilegeSpaceTableUI)
availablePrivileges={
Array [
"all",
@ -181,14 +328,18 @@ exports[`<SpaceAwarePrivilegeForm> renders without crashing 1`] = `
size="s"
type="button"
>
Add space privilege
<FormattedMessage
defaultMessage="Add space privilege"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.addSpacePrivilegeTitle"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<ImpactedSpacesFlyout
<InjectIntl(ImpactedSpacesFlyoutUI)
role={
Object {
"elasticsearch": Object {
@ -237,20 +388,38 @@ exports[`<SpaceAwarePrivilegeForm> with user profile disabling "manageSpaces" re
size="m"
title={
<p>
Insufficient Privileges
<FormattedMessage
defaultMessage="Insufficient Privileges"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.insufficientPrivilegesDescription"
values={Object {}}
/>
</p>
}
>
<p>
You are not authorized to view all available spaces.
<FormattedMessage
defaultMessage="You are not authorized to view all available spaces."
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription"
values={Object {}}
/>
</p>
<p>
Please ensure your account has all privileges granted by the
<strong>
kibana_user
</strong>
role, and try again.
<FormattedMessage
defaultMessage="Please ensure your account has all privileges granted by the
{kibanaUser} role, and try again."
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription"
values={
Object {
"kibanaUser": <strong>
<FormattedMessage
defaultMessage="kibana_user"
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.kibanaUserTitle"
values={Object {}}
/>
</strong>,
}
}
/>
</p>
</EuiCallOut>
`;

View file

@ -52,16 +52,24 @@ const buildProps = (customProps = {}) => {
describe('<ImpactedSpacesFlyout>', () => {
it('renders without crashing', () => {
expect(shallowWithIntl(<ImpactedSpacesFlyout {...buildProps()} />)).toMatchSnapshot();
expect(
shallowWithIntl(
<ImpactedSpacesFlyout.WrappedComponent {...buildProps()} intl={null as any} />
)
).toMatchSnapshot();
});
it('does not immediately show the flyout', () => {
const wrapper = mountWithIntl(<ImpactedSpacesFlyout {...buildProps()} />);
const wrapper = mountWithIntl(
<ImpactedSpacesFlyout.WrappedComponent {...buildProps()} intl={null as any} />
);
expect(wrapper.find(EuiFlyout)).toHaveLength(0);
});
it('shows the flyout after clicking the link', () => {
const wrapper = mountWithIntl(<ImpactedSpacesFlyout {...buildProps()} />);
const wrapper = mountWithIntl(
<ImpactedSpacesFlyout.WrappedComponent {...buildProps()} intl={null as any} />
);
wrapper.find(EuiLink).simulate('click');
expect(wrapper.find(EuiFlyout)).toHaveLength(1);
});
@ -82,7 +90,9 @@ describe('<ImpactedSpacesFlyout>', () => {
},
});
const wrapper = shallowWithIntl(<ImpactedSpacesFlyout {...props} />);
const wrapper = shallowWithIntl(
<ImpactedSpacesFlyout.WrappedComponent {...props} intl={null as any} />
);
wrapper.find(EuiLink).simulate('click');
const table = wrapper.find(PrivilegeSpaceTable);
@ -112,7 +122,9 @@ describe('<ImpactedSpacesFlyout>', () => {
},
});
const wrapper = shallowWithIntl(<ImpactedSpacesFlyout {...props} />);
const wrapper = shallowWithIntl(
<ImpactedSpacesFlyout.WrappedComponent {...props} intl={null as any} />
);
wrapper.find(EuiLink).simulate('click');
const table = wrapper.find(PrivilegeSpaceTable);
@ -141,7 +153,9 @@ describe('<ImpactedSpacesFlyout>', () => {
},
});
const wrapper = shallowWithIntl(<ImpactedSpacesFlyout {...props} />);
const wrapper = shallowWithIntl(
<ImpactedSpacesFlyout.WrappedComponent {...props} intl={null as any} />
);
wrapper.find(EuiLink).simulate('click');
const table = wrapper.find(PrivilegeSpaceTable);

View file

@ -12,6 +12,7 @@ import {
EuiLink,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
import { PrivilegeSpaceTable } from './privilege_space_table';
@ -26,13 +27,14 @@ interface Props {
role: Role;
spaces: Space[];
userProfile: UserProfile;
intl: InjectedIntl;
}
interface State {
showImpactedSpaces: boolean;
}
export class ImpactedSpacesFlyout extends Component<Props, State> {
class ImpactedSpacesFlyoutUI extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
@ -46,7 +48,10 @@ export class ImpactedSpacesFlyout extends Component<Props, State> {
<Fragment>
<div className="showImpactedSpaces">
<EuiLink onClick={this.toggleShowImpactedSpaces}>
View summary of spaces privileges
<FormattedMessage
id="xpack.security.management.editRoles.impactedSpacesFlyout.viewSpacesPrivilegesSummaryLinkText"
defaultMessage="View summary of spaces privileges"
/>
</EuiLink>
</div>
{flyout}
@ -61,13 +66,23 @@ export class ImpactedSpacesFlyout extends Component<Props, State> {
};
public getHighestPrivilege(...privileges: KibanaPrivilege[]): KibanaPrivilege {
const { intl } = this.props;
if (privileges.indexOf('all') >= 0) {
return 'all';
return intl.formatMessage({
id: 'xpack.security.management.editRoles.impactedSpacesFlyout.allLabel',
defaultMessage: 'all',
}) as KibanaPrivilege;
}
if (privileges.indexOf('read') >= 0) {
return 'read';
return intl.formatMessage({
id: 'xpack.security.management.editRoles.impactedSpacesFlyout.readLabel',
defaultMessage: 'read',
}) as KibanaPrivilege;
}
return 'none';
return intl.formatMessage({
id: 'xpack.security.management.editRoles.impactedSpacesFlyout.noneLabel',
defaultMessage: 'none',
}) as KibanaPrivilege;
}
public getFlyout = () => {
@ -106,7 +121,12 @@ export class ImpactedSpacesFlyout extends Component<Props, State> {
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h1 id="showImpactedSpacesTitle">Summary of space privileges</h1>
<h1 id="showImpactedSpacesTitle">
<FormattedMessage
id="xpack.security.management.editRoles.impactedSpacesFlyout.spacePrivilegesSummaryTitle"
defaultMessage="Summary of space privileges"
/>
</h1>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
@ -125,3 +145,5 @@ export class ImpactedSpacesFlyout extends Component<Props, State> {
);
};
}
export const ImpactedSpacesFlyout = injectI18n(ImpactedSpacesFlyoutUI);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { I18nProvider } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { Space } from '../../../../../../../../spaces/common/model/space';
import { UserProfile } from '../../../../../../../../xpack_main/public/services/user_profile';
@ -28,9 +29,11 @@ interface Props {
export class KibanaPrivileges extends Component<Props, {}> {
public render() {
return (
<CollapsiblePanel iconType={'logoKibana'} title={'Kibana'}>
{this.getForm()}
</CollapsiblePanel>
<I18nProvider>
<CollapsiblePanel iconType={'logoKibana'} title={'Kibana'}>
{this.getForm()}
</CollapsiblePanel>
</I18nProvider>
);
}

View file

@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount } from 'enzyme';
import React from 'react';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { PrivilegeCalloutWarning } from './privilege_callout_warning';
describe('PrivilegeCalloutWarning', () => {
it('renders without crashing', () => {
expect(
mount(<PrivilegeCalloutWarning basePrivilege={'all'} isReservedRole={false} />)
mountWithIntl(<PrivilegeCalloutWarning basePrivilege={'all'} isReservedRole={false} />)
).toMatchSnapshot();
});
});

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { KibanaPrivilege } from '../../../../../../../common/model/kibana_privilege';
import { NO_PRIVILEGE_VALUE } from '../../../lib/constants';
@ -33,11 +34,19 @@ export class PrivilegeCalloutWarning extends Component<Props, State> {
<EuiCallOut
color="warning"
iconType="iInCircle"
title={"Cannot customize a reserved role's space privileges"}
title={
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.notPossibleToCustomizeReservedRoleSpacePrivilegesTitle"
defaultMessage="Cannot customize a reserved role's space privileges"
/>
}
>
<p>
This role always grants full access to all spaces. To customize privileges for
individual spaces, you must create a new role.
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.howToCustomizePrivilegesDescription"
defaultMessage="This role always grants full access to all spaces. To customize privileges for
individual spaces, you must create a new role."
/>
</p>
</EuiCallOut>
);
@ -46,12 +55,46 @@ export class PrivilegeCalloutWarning extends Component<Props, State> {
<EuiCallOut
color="warning"
iconType="iInCircle"
title={'Minimum privilege is too high to customize individual spaces'}
title={
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.minimumPrivilegeTitle"
defaultMessage="Minimum privilege is too high to customize individual spaces"
/>
}
>
<p>
Setting the minimum privilege to <strong>all</strong> grants full access to all
spaces. To customize privileges for individual spaces, the minimum privilege must be
either <strong>read</strong> or <strong>none</strong>.
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.howToCustomizePrivilegesForIndividualSpacesDescription"
defaultMessage="Setting the minimum privilege to {allText} grants full access to all
spaces. To customize privileges for individual spaces, the minimum privilege must be
either {readText} or {noneText}."
values={{
allText: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.allText"
defaultMessage="all"
/>
</strong>
),
readText: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.readText"
defaultMessage="read"
/>
</strong>
),
noneText: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.noneText"
defaultMessage="none"
/>
</strong>
),
}}
/>
</p>
</EuiCallOut>
);
@ -64,11 +107,19 @@ export class PrivilegeCalloutWarning extends Component<Props, State> {
<EuiCallOut
color="warning"
iconType="iInCircle"
title={"Cannot customize a reserved role's space privileges"}
title={
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.notPossibleToCustomizeReservedRoleSpacePrivilegesTitle"
defaultMessage="Cannot customize a reserved role's space privileges"
/>
}
>
<p>
This role always grants read access to all spaces. To customize privileges for
individual spaces, you must create a new role.
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.alwaysGrantReadAccessToAllSpacesTitle"
defaultMessage="This role always grants read access to all spaces. To customize privileges for
individual spaces, you must create a new role."
/>
</p>
</EuiCallOut>
);
@ -79,7 +130,20 @@ export class PrivilegeCalloutWarning extends Component<Props, State> {
iconType="iInCircle"
title={
<span>
The minimal possible privilege is <strong>read</strong>.
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.minimalPossiblePrivilageTitle"
defaultMessage="The minimal possible privilege is {readText}."
values={{
readText: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.readText"
defaultMessage="read"
/>
</strong>
),
}}
/>
</span>
}
/>
@ -92,11 +156,19 @@ export class PrivilegeCalloutWarning extends Component<Props, State> {
<EuiCallOut
color="warning"
iconType="iInCircle"
title={"Cannot customize a reserved role's space privileges"}
title={
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.notPossibleToCustomizeReservedRoleSpacePrivilegesTitle"
defaultMessage="Cannot customize a reserved role's space privileges"
/>
}
>
<p>
This role never grants access to any spaces within Kibana. To customize privileges for
individual spaces, you must create a new role.
<FormattedMessage
id="xpack.security.management.editRoles.privilegeCalloutWarning.neverGrantReadAccessToAllSpacesTitle"
defaultMessage="This role never grants access to any spaces within Kibana. To customize privileges for
individual spaces, you must create a new role."
/>
</p>
</EuiCallOut>
);

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { shallow } from 'enzyme';
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { KibanaPrivilege } from '../../../../../../../common/model/kibana_privilege';
import { RoleValidator } from '../../../lib/validate_role';
import { PrivilegeSpaceForm } from './privilege_space_form';
@ -40,6 +40,6 @@ const buildProps = (customProps = {}) => {
describe('<PrivilegeSpaceForm>', () => {
it('renders without crashing', () => {
expect(shallow(<PrivilegeSpaceForm {...buildProps()} />)).toMatchSnapshot();
expect(shallowWithIntl(<PrivilegeSpaceForm {...buildProps()} />)).toMatchSnapshot();
});
});

View file

@ -5,6 +5,7 @@
*/
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { Space } from '../../../../../../../../spaces/common/model/space';
import { KibanaPrivilege } from '../../../../../../../common/model/kibana_privilege';
@ -41,7 +42,12 @@ export class PrivilegeSpaceForm extends Component<Props, {}> {
<EuiFlexGroup responsive={false}>
<EuiFlexItem>
<EuiFormRow
label={'Spaces'}
label={
<FormattedMessage
id="xpack.security.management.editRoles.privilegeSpaceForm.spacesFormRowLabel"
defaultMessage="Spaces"
/>
}
{...validator.validateSelectedSpaces(selectedSpaceIds, selectedPrivilege)}
>
<SpaceSelector
@ -53,7 +59,12 @@ export class PrivilegeSpaceForm extends Component<Props, {}> {
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={'Privilege'}
label={
<FormattedMessage
id="xpack.security.management.editRoles.privilegeSpaceForm.privilegeFormRowLabel"
defaultMessage="Privilege"
/>
}
{...validator.validateSelectedPrivilege(selectedSpaceIds, selectedPrivilege)}
>
<PrivilegeSelector

View file

@ -10,6 +10,7 @@ import {
EuiInMemoryTable,
EuiText,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { Space } from '../../../../../../../../spaces/common/model/space';
import { SpaceAvatar } from '../../../../../../../../spaces/public/components';
@ -25,6 +26,7 @@ interface Props {
spacePrivileges: any;
onChange?: (privs: { [spaceId: string]: KibanaPrivilege[] }) => void;
readonly?: boolean;
intl: InjectedIntl;
}
interface State {
@ -35,13 +37,13 @@ interface DeletedSpace extends Space {
deleted: boolean;
}
export class PrivilegeSpaceTable extends Component<Props, State> {
class PrivilegeSpaceTableUI extends Component<Props, State> {
public state = {
searchTerm: '',
};
public render() {
const { role, spaces, availablePrivileges, spacePrivileges } = this.props;
const { role, spaces, availablePrivileges, spacePrivileges, intl } = this.props;
const { searchTerm } = this.state;
@ -74,7 +76,10 @@ export class PrivilegeSpaceTable extends Component<Props, State> {
search={{
box: {
incremental: true,
placeholder: 'Filter',
placeholder: intl.formatMessage({
id: 'xpack.security.management.editRoles.privilegeSpaceTable.filterPlaceholder',
defaultMessage: 'Filter',
}),
},
onChange: (search: any) => {
this.setState({
@ -88,6 +93,7 @@ export class PrivilegeSpaceTable extends Component<Props, State> {
}
public getTableColumns = (role: Role, availablePrivileges: KibanaPrivilege[] = []) => {
const { intl } = this.props;
const columns: any[] = [
{
field: 'space',
@ -103,11 +109,22 @@ export class PrivilegeSpaceTable extends Component<Props, State> {
},
{
field: 'space',
name: 'Space',
name: intl.formatMessage({
id: 'xpack.security.management.editRoles.privilegeSpaceTable.spaceName',
defaultMessage: 'Space',
}),
width: this.props.readonly ? '75%' : '50%',
render: (space: Space | DeletedSpace) => {
if ('deleted' in space) {
return <EuiText color={'subdued'}>{space.id} (deleted)</EuiText>;
return (
<EuiText color={'subdued'}>
<FormattedMessage
id="xpack.security.management.editRoles.privilegeSpaceTable.deletedSpaceDescription"
defaultMessage="{value} (deleted)"
values={{ value: space.id }}
/>
</EuiText>
);
} else {
return <EuiText>{space.name}</EuiText>;
}
@ -115,7 +132,10 @@ export class PrivilegeSpaceTable extends Component<Props, State> {
},
{
field: 'privilege',
name: 'Privilege',
name: intl.formatMessage({
id: 'xpack.security.management.editRoles.privilegeSpaceTable.privilegeName',
defaultMessage: 'Privilege',
}),
width: this.props.readonly ? '25%' : undefined,
render: (privilege: KibanaPrivilege, record: any) => {
if (this.props.readonly || record.space.deleted) {
@ -137,7 +157,10 @@ export class PrivilegeSpaceTable extends Component<Props, State> {
];
if (!this.props.readonly) {
columns.push({
name: 'Actions',
name: intl.formatMessage({
id: 'xpack.security.management.editRoles.privilegeSpaceTable.actionsName',
defaultMessage: 'Actions',
}),
actions: [
{
render: (record: any) => {
@ -182,3 +205,5 @@ export class PrivilegeSpaceTable extends Component<Props, State> {
}
};
}
export const PrivilegeSpaceTable = injectI18n(PrivilegeSpaceTableUI);

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { PrivilegeSelector } from './privilege_selector';
import { SimplePrivilegeForm } from './simple_privilege_form';
@ -32,12 +32,12 @@ const buildProps = (customProps?: any) => {
describe('<SimplePrivilegeForm>', () => {
it('renders without crashing', () => {
expect(shallow(<SimplePrivilegeForm {...buildProps()} />)).toMatchSnapshot();
expect(shallowWithIntl(<SimplePrivilegeForm {...buildProps()} />)).toMatchSnapshot();
});
it('displays "none" when no privilege is selected', () => {
const props = buildProps();
const wrapper = shallow(<SimplePrivilegeForm {...props} />);
const wrapper = shallowWithIntl(<SimplePrivilegeForm {...props} />);
const selector = wrapper.find(PrivilegeSelector);
expect(selector.props()).toMatchObject({
value: 'none',
@ -53,7 +53,7 @@ describe('<SimplePrivilegeForm>', () => {
},
},
});
const wrapper = shallow(<SimplePrivilegeForm {...props} />);
const wrapper = shallowWithIntl(<SimplePrivilegeForm {...props} />);
const selector = wrapper.find(PrivilegeSelector);
expect(selector.props()).toMatchObject({
value: 'read',
@ -62,7 +62,7 @@ describe('<SimplePrivilegeForm>', () => {
it('fires its onChange callback when the privilege changes', () => {
const props = buildProps();
const wrapper = mount(<SimplePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SimplePrivilegeForm {...props} />);
const selector = wrapper.find(PrivilegeSelector).find('select');
selector.simulate('change', { target: { value: 'all' } });

View file

@ -9,6 +9,7 @@ import {
EuiDescribedFormGroup,
EuiFormRow,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
import { KibanaPrivilege } from '../../../../../../../common/model/kibana_privilege';
import { Role } from '../../../../../../../common/model/role';
@ -35,11 +36,28 @@ export class SimplePrivilegeForm extends Component<Props, {}> {
? (assignedPrivileges.global[0] as KibanaPrivilege)
: NO_PRIVILEGE_VALUE;
const description = <p>Specifies the Kibana privilege for this role.</p>;
const description = (
<p>
<FormattedMessage
id="xpack.security.management.editRoles.simplePrivilegeForm.specifyPrivilegeForRoleDescription"
defaultMessage="Specifies the Kibana privilege for this role."
/>
</p>
);
return (
<Fragment>
<EuiDescribedFormGroup title={<h3>Kibana privileges</h3>} description={description}>
<EuiDescribedFormGroup
title={
<h3>
<FormattedMessage
id="xpack.security.management.editRoles.simplePrivilegeForm.kibanaPrivilegesTitle"
defaultMessage="Kibana privileges"
/>
</h3>
}
description={description}
>
<EuiFormRow hasEmptyLabelSpace>
<PrivilegeSelector
data-test-subj={'kibanaPrivilege'}

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { RoleValidator } from '../../../lib/validate_role';
import { PrivilegeCalloutWarning } from './privilege_callout_warning';
import { PrivilegeSpaceForm } from './privilege_space_form';
@ -48,7 +48,9 @@ const buildProps = (customProps: any = {}) => {
describe('<SpaceAwarePrivilegeForm>', () => {
it('renders without crashing', () => {
expect(shallow(<SpaceAwarePrivilegeForm {...buildProps()} />)).toMatchSnapshot();
expect(
shallowWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...buildProps()} />)
).toMatchSnapshot();
});
it('shows the space table if exisitng space privileges are declared', () => {
@ -66,7 +68,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const table = wrapper.find(PrivilegeSpaceTable);
expect(table).toHaveLength(1);
@ -75,7 +77,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
it('hides the space table if there are no existing space privileges', () => {
const props = buildProps();
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const table = wrapper.find(PrivilegeSpaceTable);
expect(table).toMatchSnapshot();
@ -96,7 +98,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
expect(wrapper.find(PrivilegeSpaceForm)).toHaveLength(0);
wrapper.find('button[data-test-subj="addSpacePrivilegeButton"]').simulate('click');
@ -120,7 +122,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const warning = wrapper.find(PrivilegeCalloutWarning);
expect(warning.props()).toMatchObject({
@ -151,7 +153,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const warning = wrapper.find(PrivilegeCalloutWarning);
expect(warning.props()).toMatchObject({
@ -174,7 +176,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const table = wrapper.find(PrivilegeSpaceTable);
expect(table).toHaveLength(1);
@ -200,7 +202,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const warning = wrapper.find(PrivilegeCalloutWarning);
expect(warning).toHaveLength(0);
@ -221,7 +223,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = mount(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = mountWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
const table = wrapper.find(PrivilegeSpaceTable);
expect(table).toHaveLength(1);
@ -244,7 +246,7 @@ describe('<SpaceAwarePrivilegeForm>', () => {
},
});
const wrapper = shallow(<SpaceAwarePrivilegeForm {...props} />);
const wrapper = shallowWithIntl(<SpaceAwarePrivilegeForm.WrappedComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View file

@ -16,6 +16,7 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
import { Space } from '../../../../../../../../spaces/common/model/space';
import { UserProfile } from '../../../../../../../../xpack_main/public/services/user_profile';
@ -40,6 +41,7 @@ interface Props {
editable: boolean;
validator: RoleValidator;
userProfile: UserProfile;
intl: InjectedIntl;
}
interface PrivilegeForm {
@ -56,7 +58,7 @@ interface State {
privilegeForms: PrivilegeForm[];
}
export class SpaceAwarePrivilegeForm extends Component<Props, State> {
class SpaceAwarePrivilegeFormUI extends Component<Props, State> {
constructor(props: Props) {
super(props);
const { role } = props;
@ -73,15 +75,44 @@ export class SpaceAwarePrivilegeForm extends Component<Props, State> {
}
public render() {
const { kibanaAppPrivileges, role, userProfile } = this.props;
const { kibanaAppPrivileges, role, userProfile, intl } = this.props;
if (!userProfile.hasCapability('manageSpaces')) {
return (
<EuiCallOut title={<p>Insufficient Privileges</p>} iconType="alert" color="danger">
<p>You are not authorized to view all available spaces.</p>
<EuiCallOut
title={
<p>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.insufficientPrivilegesDescription"
defaultMessage="Insufficient Privileges"
/>
</p>
}
iconType="alert"
color="danger"
>
<p>
Please ensure your account has all privileges granted by the{' '}
<strong>kibana_user</strong> role, and try again.
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription"
defaultMessage="You are not authorized to view all available spaces."
/>
</p>
<p>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription"
defaultMessage="Please ensure your account has all privileges granted by the
{kibanaUser} role, and try again."
values={{
kibanaUser: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.kibanaUserTitle"
defaultMessage="kibana_user"
/>
</strong>
),
}}
/>
</p>
</EuiCallOut>
);
@ -92,21 +123,46 @@ export class SpaceAwarePrivilegeForm extends Component<Props, State> {
const basePrivilege =
assignedPrivileges.global.length > 0 ? assignedPrivileges.global[0] : NO_PRIVILEGE_VALUE;
const description = <p>Specify the minimum actions users can perform in your spaces.</p>;
const description = (
<p>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.minimumActionsUserCanPerformInYourSpacesDescription"
defaultMessage="Specify the minimum actions users can perform in your spaces."
/>
</p>
);
let helptext;
if (basePrivilege === NO_PRIVILEGE_VALUE) {
helptext = 'No access to spaces';
helptext = intl.formatMessage({
id: 'xpack.security.management.editRoles.spaceAwarePrivilegeForm.noAccessToSpacesHelpText',
defaultMessage: 'No access to spaces',
});
} else if (basePrivilege === 'all') {
helptext = 'View, edit, and share objects and apps within all spaces';
helptext = intl.formatMessage({
id:
'xpack.security.management.editRoles.spaceAwarePrivilegeForm.viewEditShareAppsWithinAllSpacesHelpText',
defaultMessage: 'View, edit, and share objects and apps within all spaces',
});
} else if (basePrivilege === 'read') {
helptext = 'View objects and apps within all spaces';
helptext = intl.formatMessage({
id:
'xpack.security.management.editRoles.spaceAwarePrivilegeForm.viewObjectsAndAppsWithinAllSpacesHelpText',
defaultMessage: 'View objects and apps within all spaces',
});
}
return (
<Fragment>
<EuiDescribedFormGroup
title={<h3>Minimum privileges for all spaces</h3>}
title={
<h3>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.minPrivilegesForAllSpacesTitle"
defaultMessage="Minimum privileges for all spaces"
/>
</h3>
}
description={description}
>
<EuiFormRow hasEmptyLabelSpace helpText={helptext}>
@ -147,7 +203,12 @@ export class SpaceAwarePrivilegeForm extends Component<Props, State> {
return (
<Fragment>
<EuiTitle size={'xs'}>
<h3>Higher privileges for individual spaces</h3>
<h3>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.higherPrivilegesForIndividualSpacesTitle"
defaultMessage="Higher privileges for individual spaces"
/>
</h3>
</EuiTitle>
<EuiSpacer size={'s'} />
<EuiText
@ -157,9 +218,30 @@ export class SpaceAwarePrivilegeForm extends Component<Props, State> {
color={'subdued'}
>
<p>
Grant more privileges on a per space basis. For example, if the privileges are{' '}
<strong>read</strong> for all spaces, you can set the privileges to <strong>all</strong>{' '}
for an individual space.
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.grantMorePrivilegesTitle"
defaultMessage="Grant more privileges on a per space basis. For example, if the privileges are
{read} for all spaces, you can set the privileges to {all}
for an individual space."
values={{
read: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.readText"
defaultMessage="read"
/>
</strong>
),
all: (
<strong>
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.allText"
defaultMessage="all"
/>
</strong>
),
}}
/>
</p>
</EuiText>
<EuiSpacer size={'s'} />
@ -200,7 +282,10 @@ export class SpaceAwarePrivilegeForm extends Component<Props, State> {
iconType={'plusInCircle'}
onClick={this.addSpacePrivilege}
>
Add space privilege
<FormattedMessage
id="xpack.security.management.editRoles.spaceAwarePrivilegeForm.addSpacePrivilegeTitle"
defaultMessage="Add space privilege"
/>
</EuiButton>
</EuiFlexItem>
)}
@ -367,3 +452,5 @@ export class SpaceAwarePrivilegeForm extends Component<Props, State> {
this.props.onChange(role);
};
}
export const SpaceAwarePrivilegeForm = injectI18n(SpaceAwarePrivilegeFormUI);

View file

@ -7,6 +7,7 @@
import React from 'react';
import { EuiIcon, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { Role } from '../../../../../common/model/role';
import { isReservedRole } from '../../../../lib/role';
@ -19,7 +20,14 @@ export const ReservedRoleBadge = (props: Props) => {
if (isReservedRole(role)) {
return (
<EuiToolTip content={'Reserved roles are built-in and cannot be removed or modified.'}>
<EuiToolTip
content={
<FormattedMessage
id="xpack.security.management.editRoles.reversedRoleBadget.reversedRolesCanNotBeRemovedTooltip"
defaultMessage="Reserved roles are built-in and cannot be removed or modified."
/>
}
>
<EuiIcon style={{ verticalAlign: 'super' }} type={'lock'} />
</EuiToolTip>
);

View file

@ -28,6 +28,7 @@ import { EditRolePage } from './components';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { KibanaAppPrivileges } from '../../../../common/model/kibana_privilege';
import { I18nProvider } from '@kbn/i18n/react';
routes.when(`${EDIT_ROLES_PATH}/:name?`, {
template,
@ -126,20 +127,23 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
$scope.$$postDigest(() => {
const domNode = document.getElementById('editRoleReactRoot');
render(<EditRolePage
runAsUsers={users}
role={role}
kibanaAppPrivileges={KibanaAppPrivileges}
indexPatterns={indexPatterns}
rbacEnabled={true}
rbacApplication={rbacApplication}
httpClient={$http}
allowDocumentLevelSecurity={allowDocumentLevelSecurity}
allowFieldLevelSecurity={allowFieldLevelSecurity}
spaces={spaces}
spacesEnabled={enableSpaceAwarePrivileges}
userProfile={userProfile}
/>, domNode);
render(
<I18nProvider>
<EditRolePage
runAsUsers={users}
role={role}
kibanaAppPrivileges={KibanaAppPrivileges}
indexPatterns={indexPatterns}
rbacEnabled={true}
rbacApplication={rbacApplication}
httpClient={$http}
allowDocumentLevelSecurity={allowDocumentLevelSecurity}
allowFieldLevelSecurity={allowFieldLevelSecurity}
spaces={spaces}
spacesEnabled={enableSpaceAwarePrivileges}
userProfile={userProfile}
/>
</I18nProvider>, domNode);
// unmount react on controller destroy
$scope.$on('$destroy', () => {

View file

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`validateIndexPrivileges it throws when indices is not an array 1`] = `"Expected role.elasticsearch.indices to be an array"`;
exports[`validateIndexPrivileges it throws when indices is not an array 1`] = `"Expected \\"role.elasticsearch.indices\\" to be an array"`;

View file

@ -10,6 +10,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { IndexPrivilege } from '../../../../../common/model/index_privilege';
import { KibanaPrivilege } from '../../../../../common/model/kibana_privilege';
import { Role } from '../../../../../common/model/role';
@ -52,14 +53,34 @@ export class RoleValidator {
}
if (!role.name) {
return invalid(`Please provide a role name`);
return invalid(
i18n.translate(
'xpack.security.management.editRoles.validateRole.provideRoleNameWarningMessage',
{
defaultMessage: 'Please provide a role name',
}
)
);
}
if (role.name.length > 1024) {
return invalid(`Name must not exceed 1024 characters`);
return invalid(
i18n.translate(
'xpack.security.management.editRoles.validateRole.nameLengthWarningMessage',
{
defaultMessage: 'Name must not exceed 1024 characters',
}
)
);
}
if (!role.name.match(/^[a-zA-Z_][a-zA-Z0-9_@\-\$\.]*$/)) {
return invalid(
`Name must begin with a letter or underscore and contain only letters, underscores, and numbers.`
i18n.translate(
'xpack.security.management.editRoles.validateRole.nameAllowedCharactersWarningMessage',
{
defaultMessage:
'Name must begin with a letter or underscore and contain only letters, underscores, and numbers.',
}
)
);
}
return valid();
@ -71,7 +92,14 @@ export class RoleValidator {
}
if (!Array.isArray(role.elasticsearch.indices)) {
throw new TypeError(`Expected role.elasticsearch.indices to be an array`);
throw new TypeError(
i18n.translate('xpack.security.management.editRoles.validateRole.indicesTypeErrorMessage', {
defaultMessage: 'Expected {elasticIndices} to be an array',
values: {
elasticIndices: '"role.elasticsearch.indices"',
},
})
);
}
const areIndicesValid =
@ -91,7 +119,14 @@ export class RoleValidator {
}
if (indexPrivilege.names.length && !indexPrivilege.privileges.length) {
return invalid(`At least one privilege is required`);
return invalid(
i18n.translate(
'xpack.security.management.editRoles.validateRole.onePrivilegeRequiredWarningMessage',
{
defaultMessage: 'At least one privilege is required',
}
)
);
}
return valid();
}
@ -112,7 +147,14 @@ export class RoleValidator {
if (Array.isArray(spaceIds) && spaceIds.length > 0) {
return valid();
}
return invalid('At least one space is required');
return invalid(
i18n.translate(
'xpack.security.management.editRoles.validateRole.oneSpaceRequiredWarningMessage',
{
defaultMessage: 'At least one space is required',
}
)
);
}
public validateSelectedPrivilege(
@ -131,7 +173,14 @@ export class RoleValidator {
if (privilege) {
return valid();
}
return invalid('Privilege is required');
return invalid(
i18n.translate(
'xpack.security.management.editRoles.validateRole.privilegeRequiredWarningMessage',
{
defaultMessage: 'Privilege is required',
}
)
);
}
public setInProgressSpacePrivileges(inProgressSpacePrivileges: any[]) {

View file

@ -16,6 +16,7 @@ import '../../services/shield_user';
import { ROLES_PATH, USERS_PATH } from './management_urls';
import { management } from 'ui/management';
import { i18n } from '@kbn/i18n';
routes.defaults(/\/management/, {
resolve: {
@ -29,7 +30,10 @@ routes.defaults(/\/management/, {
function ensureSecurityRegistered() {
const registerSecurity = () => management.register('security', {
display: 'Security',
display: i18n.translate(
'xpack.security.management.securityTitle', {
defaultMessage: 'Security',
}),
order: 10,
icon: 'securityApp',
});
@ -41,7 +45,10 @@ routes.defaults(/\/management/, {
security.register('users', {
name: 'securityUsersLink',
order: 10,
display: 'Users',
display: i18n.translate(
'xpack.security.management.usersTitle', {
defaultMessage: 'Users',
}),
url: `#${USERS_PATH}`,
});
}
@ -50,7 +57,10 @@ routes.defaults(/\/management/, {
security.register('roles', {
name: 'securityRolesLink',
order: 20,
display: 'Roles',
display: i18n.translate(
'xpack.security.management.rolesTitle', {
defaultMessage: 'Roles',
}),
url: `#${ROLES_PATH}`,
});
}

View file

@ -3,15 +3,19 @@
<div class="kuiInfoPanel kuiInfoPanel--error" ng-if="forbidden">
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>
<span class="kuiInfoPanelHeader__title">
You do not have permission to manage roles.
</span>
<span
class="kuiInfoPanelHeader__title"
i18n-id="xpack.security.management.roles.noPermissionToManageRolesDescription"
i18n-default-message="You do not have permission to manage roles."
></span>
</div>
<div class="kuiInfoPanelBody">
<div class="kuiInfoPanelBody__message">
Please contact your administrator.
</div>
<div
class="kuiInfoPanelBody__message"
i18n-id="xpack.security.management.roles.contactAdministratorDescription"
i18n-default-message="Please contact your administrator."
></div>
</div>
</div>
@ -30,7 +34,7 @@
aria-label="Filter"
ng-model="query"
>
</div>
</div>
</div>
<div class="kuiToolBarSection">
@ -42,7 +46,10 @@
>
<span class="kuiButton__inner">
<span class="kuiButton__icon kuiIcon fa-trash"></span>
<span>Delete</span>
<span
i18n-id="xpack.security.management.roles.deleteButtonLabel"
i18n-default-message="Delete"
></span>
</span>
</button>
@ -54,7 +61,11 @@
ng-if="!selectedRoles.length"
data-test-subj="createRoleButton"
>
<span class="kuiButton__icon kuiIcon fa-plus"></span><span>Create role</span>
<span class="kuiButton__icon kuiIcon fa-plus"></span>
<span
i18n-id="xpack.security.management.roles.createRoleButtonLabel"
i18n-default-message="Create role"
></span>
</a>
</div>
@ -65,8 +76,23 @@
<!-- NoResults -->
<div class="kuiPanel kuiPanel--centered" ng-show="!(roles | filter:query).length">
<div class="kuiNoItems">
No <span ng-show="query">matching</span> roles found.
<div
class="kuiNoItems"
>
<span
i18n-id="xpack.security.management.roles.noFoundMatchingRolesDescription1"
i18n-default-message="No "
i18n-context="Part of composite label xpack.security.management.roles.noFoundMatchingRolesDescription1 + {matchingText} + xpack.security.management.roles.noFoundMatchingRolesDescription2"
></span>
<span
ng-show="query"
i18n-id="xpack.security.management.roles.matchingText"
i18n-default-message="matching"
></span>
<span
i18n-id="xpack.security.management.roles.noFoundMatchingRolesDescription2"
i18n-default-message=" roles found"
></span>
</div>
</div>
@ -97,7 +123,10 @@
aria-label="{{sort.reverse ? 'Sort role ascending' : 'Sort role descending'}}"
>
<span class="kuiTableHeaderCell__liner">
Role
<span
i18n-id="xpack.security.management.roles.roleTitle"
i18n-default-message="Role"
></span>
<span
aria-hidden="true"
class="kuiTableSortIcon kuiIcon"
@ -108,14 +137,18 @@
</th>
<th scope="col" class="kuiTableHeaderCell">
<span class="kuiTableHeaderCell__liner">
Reserved
<span
class="kuiIcon fa-question-circle"
tooltip="Reserved roles are built-in and cannot be removed or modified. Only the password may be changed."
aria-label="Reserved roles are built-in and cannot be removed or modified. Only the password may be changed."
></span>
</span>
<span
class="kuiTableHeaderCell__liner"
i18n-id="xpack.security.management.roles.reversedTitle"
i18n-default-message="Reserved {icon}"
i18n-values="{
icon: '<span
class=\'kuiIcon fa-question-circle\'
tooltip={{reversedTooltip}}
aria-label={{reversedAriaLabel}}
></span>'
}"
></span>
</th>
</tr>
</thead>
@ -151,9 +184,13 @@
class="kuiBadge kuiBadge--default"
tooltip="This role is currently disabled. You may only view or delete it."
aria-label="This role is currently disabled. You may only view or delete it."
i18n-id="xpack.security.management.roles.disableTitle"
i18n-default-message="{icon} Disabled"
i18n-values="{
icon: '<span class=\'kuiIcon fa-warning\'></span>'
}"
>
<span class="kuiIcon fa-warning"></span>
Disabled
</span>
</a>
</div>
@ -175,9 +212,13 @@
<!-- ToolBarFooter -->
<div class="kuiToolBarFooter">
<div class="kuiToolBarFooterSection">
<div class="kuiToolBarText" ng-hide="selectedRoles.length === 0">
{{ selectedRoles.length }} roles selected
</div>
<div
class="kuiToolBarText"
ng-hide="selectedRoles.length === 0"
i18n-id="xpack.security.management.roles.selectedRolesTitle"
i18n-default-message="{selectedRolesLength} roles selected"
i18n-values="{ selectedRolesLength: selectedRoles.length }"
></div>
</div>
<div class="kuiToolBarFooterSection">
<!-- We need an empty section for the buttons to be positioned consistently. -->

View file

@ -19,25 +19,34 @@ routes.when(ROLES_PATH, {
resolve: {
roles(ShieldRole, kbnUrl, Promise, Private) {
// $promise is used here because the result is an ngResource, not a promise itself
return ShieldRole.query().$promise
.catch(checkLicenseError(kbnUrl, Promise, Private))
return ShieldRole.query()
.$promise.catch(checkLicenseError(kbnUrl, Promise, Private))
.catch(_.identity); // Return the error if there is one
}
},
},
controller($scope, $route, $q, confirmModal) {
controller($scope, $route, $q, confirmModal, i18n) {
$scope.roles = $route.current.locals.roles;
$scope.forbidden = !_.isArray($scope.roles);
$scope.selectedRoles = [];
$scope.sort = { orderBy: 'name', reverse: false };
$scope.editRolesHref = `#${EDIT_ROLES_PATH}`;
$scope.getEditRoleHref = (role) => `#${EDIT_ROLES_PATH}/${role}`;
$scope.getEditRoleHref = role => `#${EDIT_ROLES_PATH}/${role}`;
$scope.deleteRoles = () => {
const doDelete = () => {
$q.all($scope.selectedRoles.map((role) => role.$delete()))
.then(() => toastNotifications.addSuccess(`Deleted ${$scope.selectedRoles.length > 1 ? 'roles' : 'role'}`))
$q.all($scope.selectedRoles.map(role => role.$delete()))
.then(() =>
toastNotifications.addSuccess(
i18n('xpack.security.management.roles.deleteRoleTitle', {
defaultMessage: 'Deleted {value, plural, one {role} other {roles}}',
values: {
value: $scope.selectedRoles.length,
},
})
)
)
.then(() => {
$scope.selectedRoles.map((role) => {
$scope.selectedRoles.map(role => {
const i = $scope.roles.indexOf(role);
$scope.roles.splice(i, 1);
});
@ -45,12 +54,17 @@ routes.when(ROLES_PATH, {
});
};
const confirmModalOptions = {
confirmButtonText: 'Delete role(s)',
onConfirm: doDelete
confirmButtonText: i18n('xpack.security.management.roles.deleteRoleConfirmButtonLabel', {
defaultMessage: 'Delete role(s)',
}),
onConfirm: doDelete,
};
confirmModal(`
Are you sure you want to delete the selected role(s)? This action is irreversible!`,
confirmModalOptions
confirmModal(
i18n('xpack.security.management.roles.deletingRolesWarningMessage', {
defaultMessage:
'Are you sure you want to delete the selected role(s)? This action is irreversible!',
}),
confirmModalOptions
);
};
@ -83,7 +97,16 @@ routes.when(ROLES_PATH, {
$scope.toggleSort = toggleSort;
function getActionableRoles() {
return $scope.roles.filter((role) => !role.metadata._reserved);
return $scope.roles.filter(role => !role.metadata._reserved);
}
}
$scope.reversedTooltip = i18n('xpack.security.management.roles.reversedTooltip', {
defaultMessage: 'Reserved roles are built-in and cannot be removed or modified. Only the password may be changed.',
});
$scope.reversedAriaLabel = i18n('xpack.security.management.roles.reversedAriaLabel', {
defaultMessage: 'Reserved roles are built-in and cannot be removed or modified. Only the password may be changed.',
});
},
});