Duplicate UsersPage components into AllUsersPage; remove SuperAdmin

pull/10616/head
Jared Scheib 2018-01-09 18:01:15 -08:00
parent a5ba7b035b
commit 97739e84be
12 changed files with 639 additions and 88 deletions

View File

@ -9,6 +9,7 @@ import {
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage'
import UsersPage from 'src/admin/containers/chronograf/UsersPage'
import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage'
const ORGANIZATIONS_TAB_NAME = 'Organizations'
const CURRENT_ORG_USERS_TAB_NAME = 'Current Org Users'
@ -36,7 +37,10 @@ const AdminTabs = ({
requiredRole: SUPERADMIN_ROLE,
type: ALL_USERS_TAB_NAME,
component: (
<UsersPage meID={meID} meCurrentOrganization={meCurrentOrganization} />
<AllUsersPage
meID={meID}
meCurrentOrganization={meCurrentOrganization}
/>
),
},
].filter(t => isUserAuthorized(meRole, t.requiredRole))

View File

@ -0,0 +1,143 @@
import React, {Component, PropTypes} from 'react'
import uuid from 'node-uuid'
import AllUsersTableHeader from 'src/admin/components/chronograf/AllUsersTableHeader'
import AllUsersTableRowNew from 'src/admin/components/chronograf/AllUsersTableRowNew'
import AllUsersTableRow from 'src/admin/components/chronograf/AllUsersTableRow'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
class AllUsersTable extends Component {
constructor(props) {
super(props)
this.state = {
isCreatingUser: false,
}
}
handleChangeUserRole = (user, currentRole) => newRole => {
this.props.onUpdateUserRole(user, currentRole, newRole)
}
handleChangeSuperAdmin = user => newStatus => {
this.props.onUpdateUserSuperAdmin(user, newStatus)
}
handleDeleteUser = user => {
this.props.onDeleteUser(user)
}
handleClickCreateUser = () => {
this.setState({isCreatingUser: true})
}
handleBlurCreateUserRow = () => {
this.setState({isCreatingUser: false})
}
render() {
const {organization, users, onCreateUser, meID, notify} = this.props
const {isCreatingUser} = this.state
const {
colRole,
colSuperAdmin,
colProvider,
colScheme,
colActions,
} = USERS_TABLE
return (
<div className="panel panel-default">
<AllUsersTableHeader
numUsers={users.length}
onClickCreateUser={this.handleClickCreateUser}
isCreatingUser={isCreatingUser}
organization={organization}
/>
<div className="panel-body">
<table className="table table-highlight v-center chronograf-admin-table">
<thead>
<tr>
<th>Username</th>
<th style={{width: colRole}} className="align-with-col-text">
Role
</th>
<th style={{width: colSuperAdmin}} className="text-center">
SuperAdmin
</th>
<th style={{width: colProvider}}>Provider</th>
<th style={{width: colScheme}}>Scheme</th>
<th className="text-right" style={{width: colActions}} />
</tr>
</thead>
<tbody>
{isCreatingUser
? <AllUsersTableRowNew
organization={organization}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
notify={notify}
/>
: null}
{users.length || !isCreatingUser
? users.map(user =>
<AllUsersTableRow
user={user}
key={uuid.v4()}
organization={organization}
onChangeUserRole={this.handleChangeUserRole}
onChangeSuperAdmin={this.handleChangeSuperAdmin}
onDelete={this.handleDeleteUser}
meID={meID}
/>
)
: <tr className="table-empty-state">
<th colSpan="6">
<p>No Users to display</p>
</th>
</tr>}
</tbody>
</table>
</div>
</div>
)
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
AllUsersTable.propTypes = {
users: arrayOf(
shape({
id: string,
links: shape({
self: string.isRequired,
}),
name: string.isRequired,
provider: string.isRequired,
roles: arrayOf(
shape({
name: string.isRequired,
organization: string.isRequired,
})
),
scheme: string.isRequired,
superAdmin: bool,
})
).isRequired,
organization: shape({
name: string.isRequired,
id: string.isRequired,
}),
onCreateUser: func.isRequired,
onUpdateUserRole: func.isRequired,
onUpdateUserSuperAdmin: func.isRequired,
onDeleteUser: func.isRequired,
meID: string.isRequired,
notify: func.isRequired,
}
export default AllUsersTable

View File

@ -0,0 +1,46 @@
import React from 'react'
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
const EmptyUsersTable = () => {
const {
colRole,
colSuperAdmin,
colProvider,
colScheme,
colActions,
} = USERS_TABLE
return (
<div className="panel panel-default">
<UsersTableHeader />
<div className="panel-body">
<table className="table table-highlight v-center chronograf-admin-table">
<thead>
<tr>
<th>Username</th>
<th style={{width: colRole}} className="align-with-col-text">
Role
</th>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<th style={{width: colSuperAdmin}} className="text-center">
SuperAdmin
</th>
</Authorized>
<th style={{width: colProvider}}>Provider</th>
<th style={{width: colScheme}}>Scheme</th>
<th className="text-right" style={{width: colActions}} />
</tr>
</thead>
<tbody />
</table>
</div>
</div>
)
}
export default EmptyUsersTable

View File

@ -0,0 +1,55 @@
import React, {Component, PropTypes} from 'react'
class AllUsersTableHeader extends Component {
constructor(props) {
super(props)
}
render() {
const {
onClickCreateUser,
numUsers,
isCreatingUser,
organization,
} = this.props
const panelTitle = numUsers === 1 ? `${numUsers} User` : `${numUsers} Users`
return (
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">
{panelTitle} in <em>{organization.name}</em>
</h2>
<button
className="btn btn-primary btn-sm"
onClick={onClickCreateUser}
disabled={isCreatingUser || !onClickCreateUser}
>
<span className="icon plus" />
Create User
</button>
</div>
)
}
}
const {bool, func, shape, string, number} = PropTypes
AllUsersTableHeader.defaultProps = {
numUsers: 0,
organization: {
name: '',
},
isCreatingUser: false,
}
AllUsersTableHeader.propTypes = {
numUsers: number.isRequired,
onClickCreateUser: func,
isCreatingUser: bool.isRequired,
organization: shape({
name: string.isRequired,
}),
}
export default AllUsersTableHeader

View File

@ -0,0 +1,93 @@
import React, {PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'
import SlideToggle from 'shared/components/SlideToggle'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
const AllUsersTableRow = ({
user,
organization,
onChangeUserRole,
onChangeSuperAdmin,
onDelete,
meID,
}) => {
const {colRole, colSuperAdmin, colProvider, colScheme} = USERS_TABLE
const dropdownRolesItems = USER_ROLES.map(r => ({
...r,
text: r.name,
}))
const currentRole = user.roles.find(
role => role.organization === organization.id
)
const userIsMe = user.id === meID
return (
<tr className={'chronograf-admin-table--user'}>
<td>
{userIsMe
? <strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
: <strong>
{user.name}
</strong>}
</td>
<td style={{width: colRole}}>
<span className="chronograf-user--role">
<Dropdown
items={dropdownRolesItems}
selected={currentRole.name}
onChoose={onChangeUserRole(user, currentRole)}
buttonColor="btn-primary"
buttonSize="btn-xs"
className="dropdown-stretch"
/>
</span>
</td>
<td style={{width: colSuperAdmin}} className="text-center">
<SlideToggle
active={user.superAdmin}
onToggle={onChangeSuperAdmin(user)}
size="xs"
disabled={userIsMe}
/>
</td>
<td style={{width: colProvider}}>
{user.provider}
</td>
<td style={{width: colScheme}}>
{user.scheme}
</td>
<DeleteConfirmTableCell
text="Remove"
onDelete={onDelete}
item={user}
buttonSize="btn-xs"
disabled={userIsMe}
/>
</tr>
)
}
const {func, shape, string} = PropTypes
AllUsersTableRow.propTypes = {
user: shape(),
organization: shape({
name: string.isRequired,
id: string.isRequired,
}),
onChangeUserRole: func.isRequired,
onChangeSuperAdmin: func.isRequired,
onDelete: func.isRequired,
meID: string.isRequired,
}
export default AllUsersTableRow

View File

@ -0,0 +1,160 @@
import React, {Component, PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
class UsersTableRowNew extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
provider: '',
scheme: 'oauth2',
role: this.props.organization.defaultRole,
}
}
handleInputChange = fieldName => e => {
this.setState({[fieldName]: e.target.value.trim()})
}
handleConfirmCreateUser = () => {
const {onBlur, onCreateUser, organization} = this.props
const {name, provider, scheme, role, superAdmin} = this.state
const newUser = {
name,
provider,
scheme,
superAdmin,
roles: [
{
name: role,
organization: organization.id,
},
],
}
onCreateUser(newUser)
onBlur()
}
handleInputFocus = e => {
e.target.select()
}
handleSelectRole = newRole => {
this.setState({role: newRole.text})
}
handleKeyDown = e => {
const {name, provider} = this.state
const preventCreate = !name || !provider
if (e.key === 'Escape') {
this.props.onBlur()
}
if (e.key === 'Enter') {
if (preventCreate) {
return this.props.notify(
'warning',
'User must have a name and provider'
)
}
this.handleConfirmCreateUser()
}
}
render() {
const {
colRole,
colProvider,
colScheme,
colSuperAdmin,
colActions,
} = USERS_TABLE
const {onBlur} = this.props
const {name, provider, scheme, role} = this.state
const dropdownRolesItems = USER_ROLES.map(r => ({...r, text: r.name}))
const preventCreate = !name || !provider
return (
<tr className="chronograf-admin-table--new-user">
<td>
<input
className="form-control input-xs"
type="text"
placeholder="OAuth Username..."
autoFocus={true}
value={name}
onChange={this.handleInputChange('name')}
onKeyDown={this.handleKeyDown}
/>
</td>
<td style={{width: colRole}}>
<Dropdown
items={dropdownRolesItems}
selected={role}
onChoose={this.handleSelectRole}
buttonColor="btn-primary"
buttonSize="btn-xs"
className="dropdown-stretch"
/>
</td>
<td style={{width: colSuperAdmin}} className="text-center">
&mdash;
</td>
<td style={{width: colProvider}}>
<input
className="form-control input-xs"
type="text"
placeholder="OAuth Provider..."
value={provider}
onChange={this.handleInputChange('provider')}
onKeyDown={this.handleKeyDown}
/>
</td>
<td style={{width: colScheme}}>
<input
className="form-control input-xs disabled"
type="text"
disabled={true}
placeholder="OAuth Scheme..."
value={scheme}
/>
</td>
<td className="text-right" style={{width: colActions}}>
<button className="btn btn-xs btn-square btn-info" onClick={onBlur}>
<span className="icon remove" />
</button>
<button
className="btn btn-xs btn-square btn-success"
disabled={preventCreate}
onClick={this.handleConfirmCreateUser}
>
<span className="icon checkmark" />
</button>
</td>
</tr>
)
}
}
const {func, shape, string} = PropTypes
UsersTableRowNew.propTypes = {
organization: shape({
id: string.isRequired,
name: string.isRequired,
}),
onBlur: func.isRequired,
onCreateUser: func.isRequired,
notify: func.isRequired,
}
export default UsersTableRowNew

View File

@ -2,18 +2,10 @@ import React from 'react'
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
const EmptyUsersTable = () => {
const {
colRole,
colSuperAdmin,
colProvider,
colScheme,
colActions,
} = USERS_TABLE
const {colRole, colProvider, colScheme, colActions} = USERS_TABLE
return (
<div className="panel panel-default">
@ -26,11 +18,6 @@ const EmptyUsersTable = () => {
<th style={{width: colRole}} className="align-with-col-text">
Role
</th>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<th style={{width: colSuperAdmin}} className="text-center">
SuperAdmin
</th>
</Authorized>
<th style={{width: colProvider}}>Provider</th>
<th style={{width: colScheme}}>Scheme</th>
<th className="text-right" style={{width: colActions}} />

View File

@ -2,8 +2,6 @@ import React, {Component, PropTypes} from 'react'
import uuid from 'node-uuid'
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew'
import UsersTableRow from 'src/admin/components/chronograf/UsersTableRow'
@ -23,10 +21,6 @@ class UsersTable extends Component {
this.props.onUpdateUserRole(user, currentRole, newRole)
}
handleChangeSuperAdmin = user => newStatus => {
this.props.onUpdateUserSuperAdmin(user, newStatus)
}
handleDeleteUser = user => {
this.props.onDeleteUser(user)
}
@ -43,13 +37,7 @@ class UsersTable extends Component {
const {organization, users, onCreateUser, meID, notify} = this.props
const {isCreatingUser} = this.state
const {
colRole,
colSuperAdmin,
colProvider,
colScheme,
colActions,
} = USERS_TABLE
const {colRole, colProvider, colScheme, colActions} = USERS_TABLE
return (
<div className="panel panel-default">
@ -67,11 +55,6 @@ class UsersTable extends Component {
<th style={{width: colRole}} className="align-with-col-text">
Role
</th>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<th style={{width: colSuperAdmin}} className="text-center">
SuperAdmin
</th>
</Authorized>
<th style={{width: colProvider}}>Provider</th>
<th style={{width: colScheme}}>Scheme</th>
<th className="text-right" style={{width: colActions}} />
@ -93,24 +76,14 @@ class UsersTable extends Component {
key={uuid.v4()}
organization={organization}
onChangeUserRole={this.handleChangeUserRole}
onChangeSuperAdmin={this.handleChangeSuperAdmin}
onDelete={this.handleDeleteUser}
meID={meID}
/>
)
: <tr className="table-empty-state">
<Authorized
requiredRole={SUPERADMIN_ROLE}
replaceWithIfNotAuthorized={
<th colSpan="5">
<p>No Users to display</p>
</th>
}
>
<th colSpan="6">
<p>No Users to display</p>
</th>
</Authorized>
<th colSpan="5">
<p>No Users to display</p>
</th>
</tr>}
</tbody>
</table>
@ -120,7 +93,7 @@ class UsersTable extends Component {
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
const {arrayOf, func, shape, string} = PropTypes
UsersTable.propTypes = {
users: arrayOf(
@ -138,7 +111,6 @@ UsersTable.propTypes = {
})
),
scheme: string.isRequired,
superAdmin: bool,
})
).isRequired,
organization: shape({
@ -147,7 +119,6 @@ UsersTable.propTypes = {
}),
onCreateUser: func.isRequired,
onUpdateUserRole: func.isRequired,
onUpdateUserSuperAdmin: func.isRequired,
onDeleteUser: func.isRequired,
meID: string.isRequired,
notify: func.isRequired,

View File

@ -1,9 +1,6 @@
import React, {PropTypes} from 'react'
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
import Dropdown from 'shared/components/Dropdown'
import SlideToggle from 'shared/components/SlideToggle'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
@ -13,11 +10,10 @@ const UsersTableRow = ({
user,
organization,
onChangeUserRole,
onChangeSuperAdmin,
onDelete,
meID,
}) => {
const {colRole, colSuperAdmin, colProvider, colScheme} = USERS_TABLE
const {colRole, colProvider, colScheme} = USERS_TABLE
const dropdownRolesItems = USER_ROLES.map(r => ({
...r,
@ -53,16 +49,6 @@ const UsersTableRow = ({
/>
</span>
</td>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<td style={{width: colSuperAdmin}} className="text-center">
<SlideToggle
active={user.superAdmin}
onToggle={onChangeSuperAdmin(user)}
size="xs"
disabled={userIsMe}
/>
</td>
</Authorized>
<td style={{width: colProvider}}>
{user.provider}
</td>
@ -89,7 +75,6 @@ UsersTableRow.propTypes = {
id: string.isRequired,
}),
onChangeUserRole: func.isRequired,
onChangeSuperAdmin: func.isRequired,
onDelete: func.isRequired,
meID: string.isRequired,
}

View File

@ -1,7 +1,5 @@
import React, {Component, PropTypes} from 'react'
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
import Dropdown from 'shared/components/Dropdown'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
@ -25,13 +23,12 @@ class UsersTableRowNew extends Component {
handleConfirmCreateUser = () => {
const {onBlur, onCreateUser, organization} = this.props
const {name, provider, scheme, role, superAdmin} = this.state
const {name, provider, scheme, role} = this.state
const newUser = {
name,
provider,
scheme,
superAdmin,
roles: [
{
name: role,
@ -72,13 +69,7 @@ class UsersTableRowNew extends Component {
}
render() {
const {
colRole,
colProvider,
colScheme,
colSuperAdmin,
colActions,
} = USERS_TABLE
const {colRole, colProvider, colScheme, colActions} = USERS_TABLE
const {onBlur} = this.props
const {name, provider, scheme, role} = this.state
@ -108,11 +99,6 @@ class UsersTableRowNew extends Component {
className="dropdown-stretch"
/>
</td>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<td style={{width: colSuperAdmin}} className="text-center">
&mdash;
</td>
</Authorized>
<td style={{width: colProvider}}>
<input
className="form-control input-xs"

View File

@ -0,0 +1,128 @@
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as adminChronografActionCreators from 'src/admin/actions/chronograf'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import AllUsersTableEmpty from 'src/admin/components/chronograf/AllUsersTableEmpty'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable'
class AllUsersPage extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: true,
}
}
handleCreateUser = user => {
const {links, actions: {createUserAsync}} = this.props
createUserAsync(links.users, user)
}
handleUpdateUserRole = (user, currentRole, {name}) => {
const {actions: {updateUserAsync}} = this.props
const updatedRole = {...currentRole, name}
const newRoles = user.roles.map(
r => (r.organization === currentRole.organization ? updatedRole : r)
)
updateUserAsync(user, {...user, roles: newRoles})
}
handleUpdateUserSuperAdmin = (user, superAdmin) => {
const {actions: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(user, updatedUser)
}
handleDeleteUser = user => {
const {actions: {deleteUserAsync}} = this.props
deleteUserAsync(user)
}
async componentWillMount() {
const {
links,
actions: {loadOrganizationsAsync, loadUsersAsync},
} = this.props
this.setState({isLoading: true})
await Promise.all([
loadOrganizationsAsync(links.organizations),
loadUsersAsync(links.users),
])
this.setState({isLoading: false})
}
render() {
const {
meCurrentOrganization,
organizations,
meID,
users,
notify,
} = this.props
const {isLoading} = this.state
if (isLoading) {
return <AllUsersTableEmpty />
}
const organization = organizations.find(
o => o.id === meCurrentOrganization.id
)
return (
<AllUsersTable
meID={meID}
users={users}
organization={organization}
onCreateUser={this.handleCreateUser}
onUpdateUserRole={this.handleUpdateUserRole}
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin}
onDeleteUser={this.handleDeleteUser}
notify={notify}
/>
)
}
}
const {arrayOf, func, shape, string} = PropTypes
AllUsersPage.propTypes = {
links: shape({
users: string.isRequired,
}),
meID: string.isRequired,
meCurrentOrganization: shape({
id: string.isRequired,
name: string.isRequired,
}).isRequired,
users: arrayOf(shape),
organizations: arrayOf(shape),
actions: shape({
loadUsersAsync: func.isRequired,
loadOrganizationsAsync: func.isRequired,
createUserAsync: func.isRequired,
updateUserAsync: func.isRequired,
deleteUserAsync: func.isRequired,
}),
notify: func.isRequired,
}
const mapStateToProps = ({links, adminChronograf: {organizations, users}}) => ({
links,
organizations,
users,
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminChronografActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AllUsersPage)

View File

@ -31,12 +31,6 @@ class UsersPage extends Component {
updateUserAsync(user, {...user, roles: newRoles})
}
handleUpdateUserSuperAdmin = (user, superAdmin) => {
const {actions: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(user, updatedUser)
}
handleDeleteUser = user => {
const {actions: {deleteUserAsync}} = this.props
deleteUserAsync(user)
@ -83,7 +77,6 @@ class UsersPage extends Component {
organization={organization}
onCreateUser={this.handleCreateUser}
onUpdateUserRole={this.handleUpdateUserRole}
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin}
onDeleteUser={this.handleDeleteUser}
notify={notify}
/>