[6.x] Management: EUI Navigation sidebar (#25905) (#29490)

* Management: EUI Navigation sidebar (#25905)

* partial work - sidebar works, need to address rendering issues

* rendering fixes

* refactor sidenav into its own file

* refactor sidenav into its own file

* remove unneeded changes

* remove unneeded formatting changes

* remove unneeded formatting changes

* remove unneeded formatting changes

* remove unneeded formatting changes

* remove more unneeded EuiPage

* remove more unneeded EuiPage

* snap snap

* remove unused dependencies

* functional tests

* sidebar tweaks, beats-cm

* lint

* properly sharing UI code

* fix eui export

* type fixes

* add test

* add test

* testy test

* partial progress

* attmpt to fix functional test

* from merge

* snap snap

* clean up management for side nav changes

* functional test fix

* snap, blank landing

* snap snap

* change management item order

* test fix

* disable tslint line

* ts fix

* functional test fix

* functional test fixes

* fix functional test

* minor cleanup

* simplify management registry callback

* remove comment

* react island with kibana version

* merge

* fix scss reference

* fix types

* remove mistaken commit

* remove k7 switch

* snapshot update

* fix sidebar nav headers

* landing page copy

* remove dummy text

* merge

* i18n and revert unneeded change

* Update edit_role_page.tsx

* i18n

* i18n

* snap snap

* better text

* snap snap

* mergi

* pop open nav on mobile

* add management section tests

* NOTICE

* fix subhead text, add padding to bottom of management nav

* Update sidebar_nav.tsx

* merge merge

* fix sass lint

* .

* snap snap

* snap snap
This commit is contained in:
Matthew Kime 2019-01-31 14:31:18 -06:00 committed by GitHub
parent 7925589df9
commit ad92f4b451
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 2987 additions and 2880 deletions

View file

@ -19,9 +19,7 @@ import {
EuiIcon,
EuiText,
EuiFieldText,
EuiPage,
EuiComboBox,
EuiPageBody,
EuiPageContent,
EuiPageContentHeader,
EuiPageContentHeaderSection,
@ -386,262 +384,260 @@ class EditUserUI extends Component {
}
return (
<EuiPage className="mgtUsersEditPage">
<EuiPageBody>
<EuiPageContent className="mgtUsersEditPage__content">
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle>
<h2>
{isNewUser ?
<FormattedMessage
id="xpack.security.management.users.editUser.newUserTitle"
defaultMessage="New user"
/>
:
<FormattedMessage
id="xpack.security.management.users.editUser.editUserTitle"
defaultMessage="Edit {userName} user"
values={{ userName: user.username }}
/>
}
</h2>
</EuiTitle>
</EuiPageContentHeaderSection>
{reserved && (
<EuiPageContentHeaderSection>
<EuiIcon type="lock" size="l" color="subdued" />
</EuiPageContentHeaderSection>
)}
</EuiPageContentHeader>
<EuiPageContentBody>
{reserved && (
<EuiText size="s" color="subdued">
<p>
<div className="mgtUsersEditPage">
<EuiPageContent className="mgtUsersEditPage__content">
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle>
<h2>
{isNewUser ?
<FormattedMessage
id="xpack.security.management.users.editUser.modifyingReservedUsersDescription"
defaultMessage="Reserved users are built-in and cannot be removed or modified. Only the password
may be changed."
id="xpack.security.management.users.editUser.newUserTitle"
defaultMessage="New user"
/>
</p>
</EuiText>
)}
:
<FormattedMessage
id="xpack.security.management.users.editUser.editUserTitle"
defaultMessage="Edit {userName} user"
values={{ userName: user.username }}
/>
}
</h2>
</EuiTitle>
</EuiPageContentHeaderSection>
{reserved && (
<EuiPageContentHeaderSection>
<EuiIcon type="lock" size="l" color="subdued" />
</EuiPageContentHeaderSection>
)}
</EuiPageContentHeader>
<EuiPageContentBody>
{reserved && (
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.security.management.users.editUser.modifyingReservedUsersDescription"
defaultMessage="Reserved users are built-in and cannot be removed or modified. Only the password
may be changed."
/>
</p>
</EuiText>
)}
{showDeleteConfirmation ? (
<ConfirmDelete
onCancel={this.onCancelDelete}
apiClient={apiClient}
usersToDelete={[user.username]}
callback={this.handleDelete}
/>
) : null}
{showDeleteConfirmation ? (
<ConfirmDelete
onCancel={this.onCancelDelete}
apiClient={apiClient}
usersToDelete={[user.username]}
callback={this.handleDelete}
/>
) : null}
<form
onSubmit={event => {
event.preventDefault();
}}
>
<EuiForm>
<EuiFormRow
isInvalid={!!this.usernameError()}
error={this.usernameError()}
helpText={
!isNewUser && !reserved
? intl.formatMessage({
id: "xpack.security.management.users.editUser.changingUserNameAfterCreationDescription",
defaultMessage: "Username's cannot be changed after creation."
})
: null
<form
onSubmit={event => {
event.preventDefault();
}}
>
<EuiForm>
<EuiFormRow
isInvalid={!!this.usernameError()}
error={this.usernameError()}
helpText={
!isNewUser && !reserved
? intl.formatMessage({
id: "xpack.security.management.users.editUser.changingUserNameAfterCreationDescription",
defaultMessage: "Username's cannot be changed after creation."
})
: null
}
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.usernameFormRowLabel",
defaultMessage: "Username"
})}
>
<EuiFieldText
onBlur={event =>
this.setState({
user: {
...this.state.user,
username: event.target.value || '',
},
})
}
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.usernameFormRowLabel",
defaultMessage: "Username"
})}
>
<EuiFieldText
onBlur={event =>
this.setState({
user: {
...this.state.user,
username: event.target.value || '',
},
})
}
value={user.username || ''}
name="username"
data-test-subj="userFormUserNameInput"
disabled={!isNewUser}
onChange={event => {
this.setState({
user: { ...this.state.user, username: event.target.value },
});
}}
/>
</EuiFormRow>
{isNewUser ? this.passwordFields() : null}
{reserved ? null : (
<Fragment>
<EuiFormRow
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.fullNameFormRowLabel",
defaultMessage: "Full name"
})}
>
<EuiFieldText
onBlur={event =>
this.setState({
user: {
...this.state.user,
full_name: event.target.value || '',
},
})
}
data-test-subj="userFormFullNameInput"
name="full_name"
value={user.full_name || ''}
onChange={event => {
this.setState({
user: {
...this.state.user,
full_name: event.target.value,
},
});
}}
/>
</EuiFormRow>
<EuiFormRow
isInvalid={!!this.emailError()}
error={this.emailError()}
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.emailAddressFormRowLabel",
defaultMessage: "Email address"
})}
>
<EuiFieldText
onBlur={event =>
this.setState({
user: {
...this.state.user,
email: event.target.value || '',
},
})
}
data-test-subj="userFormEmailInput"
name="email"
value={user.email || ''}
onChange={event => {
this.setState({
user: {
...this.state.user,
email: event.target.value,
},
});
}}
/>
</EuiFormRow>
</Fragment>
)}
<EuiFormRow
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.rolesFormRowLabel",
defaultMessage: "Roles"
})}
>
<EuiComboBox
data-test-subj="userFormRolesDropdown"
placeholder={intl.formatMessage({
id: "xpack.security.management.users.editUser.addRolesPlaceholder",
defaultMessage: "Add roles"
value={user.username || ''}
name="username"
data-test-subj="userFormUserNameInput"
disabled={!isNewUser}
onChange={event => {
this.setState({
user: { ...this.state.user, username: event.target.value },
});
}}
/>
</EuiFormRow>
{isNewUser ? this.passwordFields() : null}
{reserved ? null : (
<Fragment>
<EuiFormRow
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.fullNameFormRowLabel",
defaultMessage: "Full name"
})}
onChange={this.onRolesChange}
isDisabled={reserved}
name="roles"
options={roles.map(role => {
return { 'data-test-subj': `roleOption-${role.name}`, label: role.name };
})}
selectedOptions={selectedRoles}
/>
</EuiFormRow>
{isNewUser || showChangePasswordForm ? null : (
<EuiFormRow label="Password">
<EuiLink onClick={this.toggleChangePasswordForm}>
<FormattedMessage
id="xpack.security.management.users.editUser.changePasswordButtonLabel"
defaultMessage="Change password"
/>
</EuiLink>
</EuiFormRow>
)}
{this.changePasswordForm()}
<EuiHorizontalRule />
{reserved && (
<EuiButton onClick={() => changeUrl(USERS_PATH)}>
<FormattedMessage
id="xpack.security.management.users.editUser.returnToUserListButtonLabel"
defaultMessage="Return to user list"
>
<EuiFieldText
onBlur={event =>
this.setState({
user: {
...this.state.user,
full_name: event.target.value || '',
},
})
}
data-test-subj="userFormFullNameInput"
name="full_name"
value={user.full_name || ''}
onChange={event => {
this.setState({
user: {
...this.state.user,
full_name: event.target.value,
},
});
}}
/>
</EuiButton>
)}
{reserved ? null : (
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
<EuiButton
disabled={this.cannotSaveUser()}
fill
data-test-subj="userFormSaveButton"
onClick={() => this.saveUser()}
>
{isNewUser ?
<FormattedMessage
id="xpack.security.management.users.editUser.createUserButtonLabel"
defaultMessage="Create user"
/>
:
<FormattedMessage
id="xpack.security.management.users.editUser.updateUserButtonLabel"
defaultMessage="Update user"
/>}
</EuiButton>
</EuiFlexItem>
</EuiFormRow>
<EuiFormRow
isInvalid={!!this.emailError()}
error={this.emailError()}
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.emailAddressFormRowLabel",
defaultMessage: "Email address"
})}
>
<EuiFieldText
onBlur={event =>
this.setState({
user: {
...this.state.user,
email: event.target.value || '',
},
})
}
data-test-subj="userFormEmailInput"
name="email"
value={user.email || ''}
onChange={event => {
this.setState({
user: {
...this.state.user,
email: event.target.value,
},
});
}}
/>
</EuiFormRow>
</Fragment>
)}
<EuiFormRow
label={intl.formatMessage({
id: "xpack.security.management.users.editUser.rolesFormRowLabel",
defaultMessage: "Roles"
})}
>
<EuiComboBox
data-test-subj="userFormRolesDropdown"
placeholder={intl.formatMessage({
id: "xpack.security.management.users.editUser.addRolesPlaceholder",
defaultMessage: "Add roles"
})}
onChange={this.onRolesChange}
isDisabled={reserved}
name="roles"
options={roles.map(role => {
return { 'data-test-subj': `roleOption-${role.name}`, label: role.name };
})}
selectedOptions={selectedRoles}
/>
</EuiFormRow>
{isNewUser || showChangePasswordForm ? null : (
<EuiFormRow label="Password">
<EuiLink onClick={this.toggleChangePasswordForm}>
<FormattedMessage
id="xpack.security.management.users.editUser.changePasswordButtonLabel"
defaultMessage="Change password"
/>
</EuiLink>
</EuiFormRow>
)}
{this.changePasswordForm()}
<EuiHorizontalRule />
{reserved && (
<EuiButton onClick={() => changeUrl(USERS_PATH)}>
<FormattedMessage
id="xpack.security.management.users.editUser.returnToUserListButtonLabel"
defaultMessage="Return to user list"
/>
</EuiButton>
)}
{reserved ? null : (
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
<EuiButton
disabled={this.cannotSaveUser()}
fill
data-test-subj="userFormSaveButton"
onClick={() => this.saveUser()}
>
{isNewUser ?
<FormattedMessage
id="xpack.security.management.users.editUser.createUserButtonLabel"
defaultMessage="Create user"
/>
:
<FormattedMessage
id="xpack.security.management.users.editUser.updateUserButtonLabel"
defaultMessage="Update user"
/>}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="userFormCancelButton"
onClick={() => changeUrl(USERS_PATH)}
>
<FormattedMessage
id="xpack.security.management.users.editUser.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={true} />
{isNewUser || reserved ? null : (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="userFormCancelButton"
onClick={() => changeUrl(USERS_PATH)}
onClick={() => {
this.setState({ showDeleteConfirmation: true });
}}
data-test-subj="userFormDeleteButton"
color="danger"
>
<FormattedMessage
id="xpack.security.management.users.editUser.cancelButtonLabel"
defaultMessage="Cancel"
id="xpack.security.management.users.editUser.deleteUserButtonLabel"
defaultMessage="Delete user"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={true} />
{isNewUser || reserved ? null : (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={() => {
this.setState({ showDeleteConfirmation: true });
}}
data-test-subj="userFormDeleteButton"
color="danger"
>
<FormattedMessage
id="xpack.security.management.users.editUser.deleteUserButtonLabel"
defaultMessage="Delete user"
/>
</EuiButtonEmpty>
</EuiFlexItem>
)}
</EuiFlexGroup>
)}
</EuiForm>
</form>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
)}
</EuiFlexGroup>
)}
</EuiForm>
</form>
</EuiPageContentBody>
</EuiPageContent>
</div>
);
}
}

View file

@ -10,8 +10,6 @@ import {
EuiIcon,
EuiLink,
EuiInMemoryTable,
EuiPage,
EuiPageBody,
EuiPageContent,
EuiTitle,
EuiPageContentHeader,
@ -93,30 +91,28 @@ class UsersUI extends Component {
const { apiClient, intl } = this.props;
if (permissionDenied) {
return (
<EuiPage className="mgtUsersListingPage">
<EuiPageBody>
<EuiPageContent horizontalPosition="center">
<EuiEmptyPrompt
iconType="securityApp"
iconColor={null}
title={
<h2>
<FormattedMessage
id="xpack.security.management.users.deniedPermissionTitle"
defaultMessage="Permission denied"
/>
</h2>}
body={
<p data-test-subj="permissionDeniedMessage">
<FormattedMessage
id="xpack.security.management.users.permissionDeniedToManageUsersDescription"
defaultMessage="You do not have permission to manage users."
/>
</p>}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<div className="mgtUsersListingPage">
<EuiPageContent horizontalPosition="center">
<EuiEmptyPrompt
iconType="securityApp"
iconColor={null}
title={
<h2>
<FormattedMessage
id="xpack.security.management.users.deniedPermissionTitle"
defaultMessage="Permission denied"
/>
</h2>}
body={
<p data-test-subj="permissionDeniedMessage">
<FormattedMessage
id="xpack.security.management.users.permissionDeniedToManageUsersDescription"
defaultMessage="You do not have permission to manage users."
/>
</p>}
/>
</EuiPageContent>
</div>
);
}
const path = '#/management/security/';
@ -222,60 +218,58 @@ class UsersUI extends Component {
return normalized.indexOf(normalizedQuery) !== -1;
}) : users;
return (
<EuiPage className="mgtUsersListingPage">
<EuiPageBody>
<EuiPageContent className="mgtUsersListingPage__content">
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle>
<h2>
<FormattedMessage
id="xpack.security.management.users.usersTitle"
defaultMessage="Users"
/>
</h2>
</EuiTitle>
</EuiPageContentHeaderSection>
<EuiPageContentHeaderSection>
<EuiButton
data-test-subj="createUserButton"
href="#/management/security/users/edit"
>
<div className="mgtUsersListingPage">
<EuiPageContent className="mgtUsersListingPage__content">
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle>
<h2>
<FormattedMessage
id="xpack.security.management.users.createNewUserButtonLabel"
defaultMessage="Create new user"
id="xpack.security.management.users.usersTitle"
defaultMessage="Users"
/>
</EuiButton>
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiPageContentBody>
{showDeleteConfirmation ? (
<ConfirmDelete
onCancel={this.onCancelDelete}
apiClient={apiClient}
usersToDelete={selection.map((user) => user.username)}
callback={this.handleDelete}
</h2>
</EuiTitle>
</EuiPageContentHeaderSection>
<EuiPageContentHeaderSection>
<EuiButton
data-test-subj="createUserButton"
href="#/management/security/users/edit"
>
<FormattedMessage
id="xpack.security.management.users.createNewUserButtonLabel"
defaultMessage="Create new user"
/>
) : null}
</EuiButton>
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiPageContentBody>
<EuiInMemoryTable
itemId="username"
columns={columns}
selection={selectionConfig}
pagination={pagination}
items={usersToShow}
loading={users.length === 0}
search={search}
sorting={sorting}
rowProps={rowProps}
isSelectable
{showDeleteConfirmation ? (
<ConfirmDelete
onCancel={this.onCancelDelete}
apiClient={apiClient}
usersToDelete={selection.map((user) => user.username)}
callback={this.handleDelete}
/>
) : null}
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
<EuiInMemoryTable
itemId="username"
columns={columns}
selection={selectionConfig}
pagination={pagination}
items={usersToShow}
loading={users.length === 0}
search={search}
sorting={sorting}
rowProps={rowProps}
isSelectable
/>
</EuiPageContentBody>
</EuiPageContent>
</div>
);
}
}

View file

@ -13,8 +13,6 @@ import {
// @ts-ignore
EuiForm,
EuiFormRow,
EuiPage,
EuiPageBody,
EuiPanel,
EuiSpacer,
EuiText,
@ -83,43 +81,41 @@ class EditRolePageUI extends Component<Props, State> {
);
return (
<EuiPage className="editRolePage" restrictWidth>
<EuiPageBody>
<EuiForm {...this.state.formError}>
{this.getFormTitle()}
<div className="editRolePage">
<EuiForm {...this.state.formError}>
{this.getFormTitle()}
<EuiSpacer />
<EuiSpacer />
<EuiText size="s">{description}</EuiText>
<EuiText size="s">{description}</EuiText>
{isReservedRole(this.props.role) && (
<Fragment>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p id="reservedRoleDescription" tabIndex={0}>
<FormattedMessage
id="xpack.security.management.editRole.modifyingReversedRolesDescription"
defaultMessage="Reserved roles are built-in and cannot be removed or modified."
/>
</p>
</EuiText>
</Fragment>
)}
{isReservedRole(this.props.role) && (
<Fragment>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p id="reservedRoleDescription" tabIndex={0}>
<FormattedMessage
id="xpack.security.management.editRole.modifyingReversedRolesDescription"
defaultMessage="Reserved roles are built-in and cannot be removed or modified."
/>
</p>
</EuiText>
</Fragment>
)}
<EuiSpacer />
<EuiSpacer />
{this.getRoleName()}
{this.getRoleName()}
{this.getElasticsearchPrivileges()}
{this.getElasticsearchPrivileges()}
{this.getKibanaPrivileges()}
{this.getKibanaPrivileges()}
<EuiSpacer />
<EuiSpacer />
{this.getFormButtons()}
</EuiForm>
</EuiPageBody>
</EuiPage>
{this.getFormButtons()}
</EuiForm>
</div>
);
}

View file

@ -1,3 +1,3 @@
<kbn-management-app section="security" omit-breadcrumb-pages="['edit']">
<kbn-management-app section="security/roles" omit-breadcrumb-pages="['edit']">
<div id="editRoleReactRoot" />
</kbn-management-app>

View file

@ -1,4 +0,0 @@
#editRoleReactRoot {
background: #F5F7FA;
min-height: ~"calc(100vh - 70px)";
}

View file

@ -1,5 +0,0 @@
//sass-lint:disable-block no-ids
#editRoleReactRoot {
background: $euiColorLightestShade;
min-height: calc(100vh - 70px);
}

View file

@ -1,3 +1,3 @@
<kbn-management-app section="security" omit-breadcrumb-pages="['edit']">
<kbn-management-app section="security/users" omit-breadcrumb-pages="['edit']">
<div id="editUserReactRoot" />
</kbn-management-app>

View file

@ -1,8 +1,6 @@
// Edit role styles
@import './edit_role/components/privileges/kibana/impacted_spaces_flyout';
@import './edit_role/components/collapsible_panel';
@import './edit_role/edit_role';
$securityFormWidth: 460px;
@ -29,11 +27,6 @@ $securityFormWidth: 460px;
}
}
.mgtUsersEditPage,
.mgtUsersListingPage {
min-height: calc(100vh - 70px);
}
.mgtUsersListingPage__content {
flex-grow: 0;
}

View file

@ -35,7 +35,7 @@ routes.defaults(/\/management/, {
'xpack.security.management.securityTitle', {
defaultMessage: 'Security',
}),
order: 10,
order: 100,
icon: 'securityApp',
});
const getSecurity = () => management.getSection('security');
@ -69,16 +69,17 @@ routes.defaults(/\/management/, {
}
}
deregisterSecurity();
if (!showSecurityLinks) return;
// getCurrent will reject if there is no authenticated user, so we prevent them from seeing the security
// management screens
//
// $promise is used here because the result is an ngResource, not a promise itself
return ShieldUser.getCurrent().$promise
.then(ensureSecurityRegistered)
.catch(deregisterSecurity);
if (!showSecurityLinks) {
deregisterSecurity();
} else {
// getCurrent will reject if there is no authenticated user, so we prevent them from seeing the security
// management screens
//
// $promise is used here because the result is an ngResource, not a promise itself
return ShieldUser.getCurrent().$promise
.then(ensureSecurityRegistered)
.catch(deregisterSecurity);
}
}
}
});

View file

@ -1,5 +1,13 @@
<kbn-management-app section="security">
<div class="kuiViewContent kuiViewContent--constrainedWidth kuiViewContentItem">
<kbn-management-app section="security/roles">
<div class="euiPanel euiPanel--paddingLarge euiPageContent" style="flex-grow: 0">
<h1 class="euiTitle euiTitle--medium"
i18n-id="xpack.security.management.roles.title"
i18n-default-message="Roles"></h1>
<div class="euiText euiTextColor--subdued">
<p i18n-id="xpack.security.management.roles.subtitle"
i18n-default-message="Apply roles to groups of users and manage permissions across the stack"></p>
</div>
<div class="euiSpacer euiSpacer--l"></div>
<div class="kuiInfoPanel kuiInfoPanel--error" ng-if="forbidden">
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>

View file

@ -1,3 +1,3 @@
<kbn-management-app section="security">
<kbn-management-app section="security/users">
<div id="usersReactRoot" />
</kbn-management-app>