diff --git a/.i18nrc.json b/.i18nrc.json index 0e56bc59ed398a..ad2adb78a400f4 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -11,7 +11,8 @@ "tileMap": "src/core_plugins/tile_map", "tagCloud": "src/core_plugins/tagcloud", "xpack.idxMgmt": "x-pack/plugins/index_management", - "xpack.watcher": "x-pack/plugins/watcher" + "xpack.watcher": "x-pack/plugins/watcher", + "xpack.security": "x-pack/plugins/security" }, "exclude": [ "src/ui/ui_render/bootstrap/app_bootstrap.js", diff --git a/x-pack/plugins/security/public/components/management/users/confirm_delete.js b/x-pack/plugins/security/public/components/management/users/confirm_delete.js index 53d9bd3bed1276..984c84bfafa9b8 100644 --- a/x-pack/plugins/security/public/components/management/users/confirm_delete.js +++ b/x-pack/plugins/security/public/components/management/users/confirm_delete.js @@ -7,17 +7,29 @@ import React, { Component, Fragment } from 'react'; import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; -export class ConfirmDelete extends Component { +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; + +class ConfirmDeleteUI extends Component { deleteUsers = () => { const { usersToDelete, apiClient, callback } = this.props; const errors = []; usersToDelete.forEach(async username => { try { await apiClient.deleteUser(username); - toastNotifications.addSuccess(`Deleted user ${username}`); + toastNotifications.addSuccess( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.userSuccessfullyDeletedNotificationMessage", + defaultMessage: "Deleted user {username}" + }, { username }) + ); } catch (e) { errors.push(username); - toastNotifications.addDanger(`Error deleting user ${username}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.userDeletingErrorNotificationMessage", + defaultMessage: "Error deleting user {username}" + }, { username }) + ); } if (callback) { callback(usersToDelete, errors); @@ -25,34 +37,56 @@ export class ConfirmDelete extends Component { }); }; render() { - const { usersToDelete, onCancel } = this.props; + const { usersToDelete, onCancel, intl } = this.props; const moreThanOne = usersToDelete.length > 1; const title = moreThanOne - ? `Delete ${usersToDelete.length} users` - : `Delete user '${usersToDelete[0]}'`; + ? intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle", + defaultMessage: "Delete {userLength} users" + }, { userLength: usersToDelete.length }) + : intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.deleteOneUserTitle", + defaultMessage: "Delete user {userLength}" + }, { userLength: usersToDelete[0] }); return (
{moreThanOne ? (

- You are about to delete these users: +

    {usersToDelete.map(username =>
  • {username}
  • )}
) : null} -

This operation cannot be undone.

+

+ +

); } } + +export const ConfirmDelete = injectI18n(ConfirmDeleteUI); diff --git a/x-pack/plugins/security/public/components/management/users/edit_user.js b/x-pack/plugins/security/public/components/management/users/edit_user.js index 5cda86dd947df9..8d41a549c37726 100644 --- a/x-pack/plugins/security/public/components/management/users/edit_user.js +++ b/x-pack/plugins/security/public/components/management/users/edit_user.js @@ -32,9 +32,11 @@ import { import { toastNotifications } from 'ui/notify'; import { USERS_PATH } from '../../../views/management/management_urls'; import { ConfirmDelete } from './confirm_delete'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; + const validEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; //eslint-disable-line max-len const validUsernameRegex = /[a-zA-Z_][a-zA-Z0-9_@\-\$\.]*/; -export class EditUser extends Component { +class EditUserUI extends Component { constructor(props) { super(props); this.state = { @@ -63,7 +65,10 @@ export class EditUser extends Component { currentUser = await apiClient.getCurrentUser(); } catch (err) { toastNotifications.addDanger({ - title: `Error loading user`, + title: this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.errorLoadingUserTitle", + defaultMessage: "Error loading user" + }), text: get(err, 'data.message') || err.message, }); return; @@ -75,7 +80,10 @@ export class EditUser extends Component { roles = await apiClient.getRoles(); } catch (err) { toastNotifications.addDanger({ - title: `Error loading roles`, + title: this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.errorLoadingRolesTitle", + defaultMessage: "Error loading roles" + }), text: get(err, 'data.message') || err.message, }); return; @@ -99,19 +107,28 @@ export class EditUser extends Component { passwordError = () => { const { password } = this.state; if (password !== null && password.length < 6) { - return 'Password must be at least 6 characters'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.passwordLengthErrorMessage", + defaultMessage: "Password must be at least 6 characters" + }); } }; currentPasswordError = () => { const { currentPasswordError } = this.state; if (currentPasswordError) { - return 'The current password you entered is incorrect'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.incorrectPasswordErrorMessage", + defaultMessage: "The current password you entered is incorrect" + }); } }; confirmPasswordError = () => { const { password, confirmPassword } = this.state; if (password && confirmPassword !== null && password !== confirmPassword) { - return 'Passwords do not match'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.passwordDoNotMatchErrorMessage", + defaultMessage: "Passwords do not match" + }); } }; usernameError = () => { @@ -119,19 +136,28 @@ export class EditUser extends Component { if (username !== null && !username) { return 'Username is required'; } else if (username && !username.match(validUsernameRegex)) { - return 'Username must begin with a letter or underscore and contain only letters, underscores, and numbers'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.usernameAllowedCharactersErrorMessage", + defaultMessage: "Username must begin with a letter or underscore and contain only letters, underscores, and numbers" + }); } }; fullnameError = () => { const { full_name } = this.state.user; if (full_name !== null && !full_name) { - return 'Full name is required'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.fullNameRequiredErrorMessage", + defaultMessage: "Full name is required" + }); } }; emailError = () => { const { email } = this.state.user; if (email !== null && (!email || !email.match(validEmailRegex))) { - return 'A valid email address is required'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.validEmailRequiredErrorMessage", + defaultMessage: "A valid email address is required" + }); } }; changePassword = async () => { @@ -139,12 +165,22 @@ export class EditUser extends Component { const { user, password, currentPassword } = this.state; try { await apiClient.changePassword(user.username, password, currentPassword); - toastNotifications.addSuccess('Password changed.'); + toastNotifications.addSuccess( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.passwordSuccessfullyChangedNotificationMessage", + defaultMessage: "Password changed." + }) + ); } catch (e) { if (e.status === 401) { return this.setState({ currentPasswordError: true }); } else { - toastNotifications.addDanger(`Error setting password: ${e.data.message}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.settingPasswordErrorMessage", + defaultMessage: "Error setting password: {message}" + }, { message: e.data.message }) + ); } } this.clearPasswordForm(); @@ -161,10 +197,20 @@ export class EditUser extends Component { } try { await apiClient.saveUser(userToSave); - toastNotifications.addSuccess(`Saved user ${user.username}`); + toastNotifications.addSuccess( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.userSuccessfullySavedNotificationMessage", + defaultMessage: "Saved user {message}" + }, { message: user.username }) + ); changeUrl(USERS_PATH); } catch (e) { - toastNotifications.addDanger(`Error saving user: ${e.data.message}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.savingUserErrorMessage", + defaultMessage: "Error saving user: {message}" + }, { message: e.data.message }) + ); } }; clearPasswordForm = () => { @@ -181,7 +227,10 @@ export class EditUser extends Component { {userIsLoggedInUser ? ( @@ -193,7 +242,15 @@ export class EditUser extends Component { ) : null} @@ -206,7 +263,10 @@ export class EditUser extends Component { /> @@ -237,10 +297,21 @@ export class EditUser extends Component { {this.passwordFields()} {username === 'kibana' ? ( - +

- After you change the password for the kibana user, you must update the kibana.yml - file and restart Kibana. +

@@ -258,7 +329,10 @@ export class EditUser extends Component { this.changePassword(password); }} > - Save password + @@ -268,7 +342,10 @@ export class EditUser extends Component { this.clearPasswordForm(); }} > - Cancel + @@ -298,7 +375,7 @@ export class EditUser extends Component { this.setState({ showDeleteConfirmation: false }); }; render() { - const { changeUrl, apiClient } = this.props; + const { changeUrl, apiClient, intl } = this.props; const { user, roles, @@ -323,7 +400,20 @@ export class EditUser extends Component { -

{isNewUser ? 'New user' : `Edit "${user.username}" user`}

+

+ {isNewUser ? + + : + + } +

{reserved && ( @@ -336,8 +426,11 @@ export class EditUser extends Component { {reserved && (

- Reserved users are built-in and cannot be removed or modified. Only the password - may be changed. +

)} @@ -362,10 +455,16 @@ export class EditUser extends Component { error={this.usernameError()} helpText={ !isNewUser && !reserved - ? "Username's cannot be changed after creation." + ? intl.formatMessage({ + id: "xpack.security.management.users.editUser.changingUserNameAfterCreationDescription", + defaultMessage: "Username's cannot be changed after creation." + }) : null } - label="Username" + label={intl.formatMessage({ + id: "xpack.security.management.users.editUser.usernameFormRowLabel", + defaultMessage: "Username" + })} > @@ -393,7 +492,10 @@ export class EditUser extends Component { @@ -420,7 +522,10 @@ export class EditUser extends Component { @@ -446,10 +551,18 @@ export class EditUser extends Component {
)} - + - Change password + + + )} {this.changePasswordForm()} @@ -470,7 +588,12 @@ export class EditUser extends Component { {reserved && ( - changeUrl(USERS_PATH)}>Return to user list + changeUrl(USERS_PATH)}> + + )} {reserved ? null : ( @@ -481,7 +604,16 @@ export class EditUser extends Component { data-test-subj="userFormSaveButton" onClick={() => this.saveUser()} > - {isNewUser ? 'Create user' : 'Update user'} + {isNewUser ? + + : + } @@ -489,7 +621,10 @@ export class EditUser extends Component { data-test-subj="userFormCancelButton" onClick={() => changeUrl(USERS_PATH)} > - Cancel + @@ -502,7 +637,10 @@ export class EditUser extends Component { data-test-subj="userFormDeleteButton" color="danger" > - Delete user + )} @@ -517,3 +655,5 @@ export class EditUser extends Component { ); } } + +export const EditUser = injectI18n(EditUserUI); diff --git a/x-pack/plugins/security/public/components/management/users/users.js b/x-pack/plugins/security/public/components/management/users/users.js index 9bcd58edb217cf..da2617c48b30a3 100644 --- a/x-pack/plugins/security/public/components/management/users/users.js +++ b/x-pack/plugins/security/public/components/management/users/users.js @@ -21,8 +21,9 @@ import { } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { ConfirmDelete } from './confirm_delete'; +import { injectI18n, FormattedMessage } from "@kbn/i18n/react"; -export class Users extends Component { +class UsersUI extends Component { constructor(props) { super(props); this.state = { @@ -53,7 +54,12 @@ export class Users extends Component { if (e.status === 403) { this.setState({ permissionDenied: true }); } else { - toastNotifications.addDanger(`Error fetching users: ${e.data.message}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.fetchingUsersErrorMessage", + defaultMessage: "Error fetching users: {message}" + }, { message: e.data.message }) + ); } } } @@ -69,7 +75,13 @@ export class Users extends Component { color="danger" onClick={() => this.setState({ showDeleteConfirmation: true })} > - Delete {numSelected} user{numSelected > 1 ? 's' : ''} + ); } @@ -78,7 +90,7 @@ export class Users extends Component { } render() { const { users, filter, permissionDenied, showDeleteConfirmation, selection } = this.state; - const { apiClient } = this.props; + const { apiClient, intl } = this.props; if (permissionDenied) { return ( @@ -87,8 +99,20 @@ export class Users extends Component { Permission denied} - body={

You do not have permission to manage users.

} + title={ +

+ +

} + body={ +

+ +

} /> @@ -99,7 +123,7 @@ export class Users extends Component { const columns = [ { field: 'full_name', - name: 'Full Name', + name: intl.formatMessage({ id: "xpack.security.management.users.fullNameColumnName", defaultMessage: "Full Name" }), sortable: true, truncateText: true, render: fullName => { @@ -108,7 +132,7 @@ export class Users extends Component { }, { field: 'username', - name: 'User Name', + name: intl.formatMessage({ id: "xpack.security.management.users.userNameColumnName", defaultMessage: "User Name" }), sortable: true, truncateText: true, render: username => ( @@ -119,13 +143,16 @@ export class Users extends Component { }, { field: 'email', - name: 'Email Address', + name: intl.formatMessage({ + id: "xpack.security.management.users.emailAddressColumnName", + defaultMessage: "Email Address" + }), sortable: true, truncateText: true, }, { field: 'roles', - name: 'Roles', + name: intl.formatMessage({ id: "xpack.security.management.users.rolesColumnName", defaultMessage: "Roles" }), render: rolenames => { const roleLinks = rolenames.map((rolename, index) => { return ( @@ -140,12 +167,15 @@ export class Users extends Component { }, { field: 'metadata._reserved', - name: 'Reserved', + name: intl.formatMessage({ id: "xpack.security.management.users.reservedColumnName", defaultMessage: "Reserved" }), sortable: false, width: '100px', align: 'right', description: - 'Reserved users are built-in and cannot be removed. Only the password can be changed.', + intl.formatMessage({ + id: "xpack.security.management.users.reservedColumnDescription", + defaultMessage: "Reserved users are built-in and cannot be removed. Only the password can be changed." + }), render: reserved => reserved ? ( @@ -198,7 +228,12 @@ export class Users extends Component { -

Users

+

+ +

@@ -206,7 +241,10 @@ export class Users extends Component { data-test-subj="createUserButton" href="#/management/security/users/edit" > - Create new user +
@@ -241,3 +279,5 @@ export class Users extends Component { ); } } + +export const Users = injectI18n(UsersUI); diff --git a/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap b/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap index 97fe1b7aeaee36..cf98d33862dade 100644 --- a/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap +++ b/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap @@ -29,7 +29,7 @@ exports[`LoginPage disabled form states renders as expected when a connection to

@@ -43,7 +43,7 @@ exports[`LoginPage disabled form states renders as expected when a connection to

@@ -73,14 +73,14 @@ exports[`LoginPage disabled form states renders as expected when a connection to message={ } title={ } @@ -121,7 +121,7 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi

@@ -135,7 +135,7 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi

@@ -165,14 +165,14 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi message={ } title={ } @@ -213,7 +213,7 @@ exports[`LoginPage disabled form states renders as expected when secure cookies

@@ -227,7 +227,7 @@ exports[`LoginPage disabled form states renders as expected when secure cookies

@@ -257,14 +257,14 @@ exports[`LoginPage disabled form states renders as expected when secure cookies message={ } title={ } @@ -305,7 +305,7 @@ exports[`LoginPage disabled form states renders as expected when xpack is not av

@@ -319,7 +319,7 @@ exports[`LoginPage disabled form states renders as expected when xpack is not av

@@ -349,14 +349,14 @@ exports[`LoginPage disabled form states renders as expected when xpack is not av message={ } title={ } @@ -397,7 +397,7 @@ exports[`LoginPage enabled form state renders as expected 1`] = `

@@ -411,7 +411,7 @@ exports[`LoginPage enabled form state renders as expected 1`] = `

diff --git a/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx b/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx index 7485cebfc9dfdc..29ca0b3d978b63 100644 --- a/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx +++ b/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx @@ -58,7 +58,7 @@ export class LoginPage extends Component {

@@ -66,7 +66,7 @@ export class LoginPage extends Component {

@@ -98,13 +98,13 @@ export class LoginPage extends Component { } message={ } @@ -121,13 +121,13 @@ export class LoginPage extends Component { } message={ } @@ -138,13 +138,13 @@ export class LoginPage extends Component { } message={ } @@ -155,13 +155,13 @@ export class LoginPage extends Component { } message={ } diff --git a/x-pack/plugins/security/public/views/management/edit_user.js b/x-pack/plugins/security/public/views/management/edit_user.js index 5ba229605e4c75..160315338b0916 100644 --- a/x-pack/plugins/security/public/views/management/edit_user.js +++ b/x-pack/plugins/security/public/views/management/edit_user.js @@ -14,14 +14,17 @@ import { EditUser } from '../../components/management/users'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { createApiClient } from '../../lib/api'; +import { I18nProvider } from '@kbn/i18n/react'; const renderReact = (elem, httpClient, changeUrl, username) => { render( - , + + + , elem ); }; diff --git a/x-pack/plugins/security/public/views/management/users.js b/x-pack/plugins/security/public/views/management/users.js index fdbb1157519074..c61e421d52b4bd 100644 --- a/x-pack/plugins/security/public/views/management/users.js +++ b/x-pack/plugins/security/public/views/management/users.js @@ -12,12 +12,13 @@ import 'plugins/security/services/shield_user'; import { SECURITY_PATH, USERS_PATH } from './management_urls'; import { Users } from '../../components/management/users'; import { createApiClient } from '../../lib/api'; +import { I18nProvider } from '@kbn/i18n/react'; routes.when(SECURITY_PATH, { redirectTo: USERS_PATH, }); const renderReact = (elem, httpClient, changeUrl) => { - render(, elem); + render(, elem); }; routes.when(USERS_PATH, {