diff --git a/ui/src/admin/components/chronograf/AdminTabs.js b/ui/src/admin/components/chronograf/AdminTabs.js index 5c7bb145b1..8d4be7f9f8 100644 --- a/ui/src/admin/components/chronograf/AdminTabs.js +++ b/ui/src/admin/components/chronograf/AdminTabs.js @@ -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: , + component: ( + + ), }, { requiredRole: ADMIN_ROLE, type: USERS_TAB_NAME, component: ( - + ), }, ].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 diff --git a/ui/src/admin/components/chronograf/UsersTable.js b/ui/src/admin/components/chronograf/UsersTable.js index 18816f5ac9..9da6c5d8ea 100644 --- a/ui/src/admin/components/chronograf/UsersTable.js +++ b/ui/src/admin/components/chronograf/UsersTable.js @@ -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, diff --git a/ui/src/admin/containers/AdminChronografPage.js b/ui/src/admin/containers/AdminChronografPage.js index c57b662a10..8dc0dd6b94 100644 --- a/ui/src/admin/containers/AdminChronografPage.js +++ b/ui/src/admin/containers/AdminChronografPage.js @@ -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 ( -
-
-
-
-

Chronograf Admin

-
-
+const AdminChronografPage = ({me}) => +
+
+
+
+

Chronograf Admin

- - {users.length && organizations.length - ?
-
- -
-
- :
} -
- ) - } -} +
+ +
+
+ +
+
+
+
-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) diff --git a/ui/src/admin/containers/OrganizationsPage.js b/ui/src/admin/containers/chronograf/OrganizationsPage.js similarity index 82% rename from ui/src/admin/containers/OrganizationsPage.js rename to ui/src/admin/containers/chronograf/OrganizationsPage.js index 4c57c88fb6..630357de3d 100644 --- a/ui/src/admin/containers/OrganizationsPage.js +++ b/ui/src/admin/containers/chronograf/OrganizationsPage.js @@ -71,21 +71,25 @@ class OrganizationsPage extends Component { } render() { - const {organizations, currentOrganization, authConfig} = this.props + const {meCurrentOrganization, organizations, authConfig} = this.props - return ( - + const organization = organizations.find( + o => o.id === meCurrentOrganization.id ) + + return organizations.length + ? + :
} } @@ -116,7 +120,7 @@ OrganizationsPage.propTypes = { updateAuthConfigAsync: func.isRequired, }), getMe: func.isRequired, - currentOrganization: shape({ + meCurrentOrganization: shape({ name: string.isRequired, id: string.isRequired, }), diff --git a/ui/src/admin/containers/chronograf/UsersPage.js b/ui/src/admin/containers/chronograf/UsersPage.js new file mode 100644 index 0000000000..46cb87cb04 --- /dev/null +++ b/ui/src/admin/containers/chronograf/UsersPage.js @@ -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
Loading...
+ } + + const organization = organizations.find( + o => o.id === meCurrentOrganization.id + ) + + return ( + + ) + } +} + +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) diff --git a/ui/src/admin/index.js b/ui/src/admin/index.js index 5663e7aeb3..a985904015 100644 --- a/ui/src/admin/index.js +++ b/ui/src/admin/index.js @@ -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}