Refactor AdminChronograf into UsersPage to ensure correct users via AJAX & guard

Move OrganizationsPage into /chronograf & cleanup
pull/10616/head
Jared Scheib 2017-12-18 11:59:21 -08:00
parent 0f075d7b9d
commit db91aca339
6 changed files with 219 additions and 229 deletions

View File

@ -7,44 +7,28 @@ import {
} from 'src/auth/Authorized'
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import OrganizationsPage from 'src/admin/containers/OrganizationsPage'
import UsersTable from 'src/admin/components/chronograf/UsersTable'
import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage'
import UsersPage from 'src/admin/containers/chronograf/UsersPage'
const ORGANIZATIONS_TAB_NAME = 'Organizations'
const USERS_TAB_NAME = 'Users'
const AdminTabs = ({
meRole,
// UsersTable
users,
organization,
onCreateUser,
onUpdateUserRole,
onUpdateUserSuperAdmin,
onDeleteUser,
meID,
notify,
me: {currentOrganization: meCurrentOrganization, role: meRole, id: meID},
}) => {
const tabs = [
{
requiredRole: SUPERADMIN_ROLE,
type: ORGANIZATIONS_TAB_NAME,
component: <OrganizationsPage currentOrganization={organization} />,
component: (
<OrganizationsPage meCurrentOrganization={meCurrentOrganization} />
),
},
{
requiredRole: ADMIN_ROLE,
type: USERS_TAB_NAME,
component: (
<UsersTable
users={users}
organization={organization}
onCreateUser={onCreateUser}
onUpdateUserRole={onUpdateUserRole}
onUpdateUserSuperAdmin={onUpdateUserSuperAdmin}
onDeleteUser={onDeleteUser}
meID={meID}
notify={notify}
/>
<UsersPage meID={meID} meCurrentOrganization={meCurrentOrganization} />
),
},
].filter(t => isUserAuthorized(meRole, t.requiredRole))
@ -69,46 +53,17 @@ const AdminTabs = ({
)
}
const {arrayOf, bool, func, shape, string} = PropTypes
AdminTabs.defaultProps = {
organization: {
name: '',
id: '0',
},
}
const {shape, string} = PropTypes
AdminTabs.propTypes = {
meRole: string.isRequired,
meID: string.isRequired,
// UsersTable
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,
me: shape({
id: string.isRequired,
role: string.isRequired,
currentOrganization: shape({
name: string.isRequired,
id: string.isRequired,
}),
}).isRequired,
onCreateUser: func.isRequired,
onUpdateUserRole: func.isRequired,
onUpdateUserSuperAdmin: func.isRequired,
onDeleteUser: func.isRequired,
notify: func.isRequired,
}
export default AdminTabs

View File

@ -120,10 +120,27 @@ class UsersTable extends Component {
}
}
const {arrayOf, func, shape, string} = PropTypes
const {arrayOf, bool, func, shape, string} = PropTypes
UsersTable.propTypes = {
users: arrayOf(shape()),
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,

View File

@ -1,170 +1,42 @@
import React, {Component, PropTypes} from 'react'
import React, {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 AdminTabs from 'src/admin/components/chronograf/AdminTabs'
import FancyScrollbar from 'shared/components/FancyScrollbar'
class AdminChronografPage extends Component {
// TODO: revisit this, possibly don't call setState if both are deep equal
componentWillReceiveProps(nextProps) {
const {meCurrentOrganization} = nextProps
const hasChangedCurrentOrganization =
meCurrentOrganization.id !== this.props.meCurrentOrganization.id
if (hasChangedCurrentOrganization) {
this.loadUsers()
}
}
componentDidMount() {
this.loadOrganizations()
this.loadUsers()
}
loadUsers = () => {
const {links, actions: {loadUsersAsync}} = this.props
loadUsersAsync(links.users)
}
loadOrganizations = () => {
const {links, actions: {loadOrganizationsAsync}} = this.props
loadOrganizationsAsync(links.organizations)
}
// SINGLE USER ACTIONS
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)
}
render() {
const {
users,
meRole,
meID,
notify,
organizations,
meCurrentOrganization,
} = this.props
const organization = organizations.find(
o => o.id === meCurrentOrganization.id
)
return (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Chronograf Admin</h1>
</div>
</div>
const AdminChronografPage = ({me}) =>
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Chronograf Admin</h1>
</div>
<FancyScrollbar className="page-contents">
{users.length && organizations.length
? <div className="container-fluid">
<div className="row">
<AdminTabs
meRole={meRole}
meID={meID}
// UsersTable
users={users}
organization={organization}
onCreateUser={this.handleCreateUser}
onUpdateUserRole={this.handleUpdateUserRole}
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin}
onDeleteUser={this.handleDeleteUser}
notify={notify}
/>
</div>
</div>
: <div className="page-spinner" />}
</FancyScrollbar>
</div>
)
}
}
</div>
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<AdminTabs me={me} />
</div>
</div>
</FancyScrollbar>
</div>
const {arrayOf, func, shape, string} = PropTypes
const {shape, string} = PropTypes
AdminChronografPage.propTypes = {
links: shape({
users: string.isRequired,
}),
users: arrayOf(shape),
organizations: arrayOf(shape),
meCurrentOrganization: shape({
id: string.isRequired,
name: string.isRequired,
}).isRequired,
meRole: string.isRequired,
me: shape({
name: string.isRequired,
id: string.isRequired,
role: string.isRequired,
currentOrganization: shape({
name: string.isRequired,
id: string.isRequired,
}),
}).isRequired,
meID: string.isRequired,
actions: shape({
loadUsersAsync: func.isRequired,
loadOrganizationsAsync: func.isRequired,
createUserAsync: func.isRequired,
updateUserAsync: func.isRequired,
deleteUserAsync: func.isRequired,
}),
notify: func.isRequired,
}
const mapStateToProps = ({
links,
adminChronograf: {users, organizations},
auth: {
me,
me: {currentOrganization: meCurrentOrganization, role: meRole, id: meID},
},
}) => ({
const mapStateToProps = ({auth: {me}}) => ({
me,
meID,
links,
users,
meRole,
organizations,
meCurrentOrganization,
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(adminChronografActionCreators, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AdminChronografPage)
export default connect(mapStateToProps, null)(AdminChronografPage)

View File

@ -71,21 +71,25 @@ class OrganizationsPage extends Component {
}
render() {
const {organizations, currentOrganization, authConfig} = this.props
const {meCurrentOrganization, organizations, authConfig} = this.props
return (
<OrganizationsTable
organizations={organizations}
currentOrganization={currentOrganization}
onCreateOrg={this.handleCreateOrganization}
onDeleteOrg={this.handleDeleteOrganization}
onRenameOrg={this.handleRenameOrganization}
onTogglePublic={this.handleTogglePublic}
onChooseDefaultRole={this.handleChooseDefaultRole}
authConfig={authConfig}
onChangeAuthConfig={this.handleUpdateAuthConfig}
/>
const organization = organizations.find(
o => o.id === meCurrentOrganization.id
)
return organizations.length
? <OrganizationsTable
organizations={organizations}
currentOrganization={organization}
onCreateOrg={this.handleCreateOrganization}
onDeleteOrg={this.handleDeleteOrganization}
onRenameOrg={this.handleRenameOrganization}
onTogglePublic={this.handleTogglePublic}
onChooseDefaultRole={this.handleChooseDefaultRole}
authConfig={authConfig}
onChangeAuthConfig={this.handleUpdateAuthConfig}
/>
: <div className="page-spinner" />
}
}
@ -116,7 +120,7 @@ OrganizationsPage.propTypes = {
updateAuthConfigAsync: func.isRequired,
}),
getMe: func.isRequired,
currentOrganization: shape({
meCurrentOrganization: shape({
name: string.isRequired,
id: string.isRequired,
}),

View File

@ -0,0 +1,143 @@
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 UsersTable from 'src/admin/components/chronograf/UsersTable'
class UsersPage extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: true,
}
}
// // TODO: revisit this, possibly don't call setState if both are deep equal
// componentWillReceiveProps(nextProps) {
// const {meCurrentOrganization} = nextProps
//
// const hasChangedCurrentOrganization =
// meCurrentOrganization.id !== this.props.meCurrentOrganization.id
//
// if (hasChangedCurrentOrganization) {
// this.setState({isLoading: true})
// this.loadUsers()
// }
// }
// loadUsers = () => {
// // this.setState({isLoading: true})
// return loadUsersAsync(links.users)
// }
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})
loadOrganizationsAsync(links.organizations)
await loadUsersAsync(links.users)
this.setState({isLoading: false})
}
render() {
const {
meCurrentOrganization,
organizations,
meID,
users,
notify,
} = this.props
const {isLoading} = this.state
if (isLoading || !(organizations.length && users.length)) {
return <div className="generic-empty-state">Loading...</div>
}
const organization = organizations.find(
o => o.id === meCurrentOrganization.id
)
return (
<UsersTable
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
UsersPage.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)(UsersPage)

View File

@ -1,5 +1,4 @@
import AdminInfluxDBPage from './containers/AdminInfluxDBPage'
import AdminChronografPage from './containers/AdminChronografPage'
import OrganizationsPage from './containers/OrganizationsPage'
export {AdminChronografPage, AdminInfluxDBPage, OrganizationsPage}
export {AdminChronografPage, AdminInfluxDBPage}