diff --git a/ui/src/admin/components/chronograf/OrganizationsTable.js b/ui/src/admin/components/chronograf/OrganizationsTable.js new file mode 100644 index 0000000000..2514d3ab21 --- /dev/null +++ b/ui/src/admin/components/chronograf/OrganizationsTable.js @@ -0,0 +1,89 @@ +import React, {Component, PropTypes} from 'react' + +import Organization from 'src/admin/components/chronograf/Organization' +import NewOrganization from 'src/admin/components/chronograf/NewOrganization' + +class OrganizationsTable extends Component { + constructor(props) { + super(props) + + this.state = { + isAddingOrganization: false, + } + } + handleClickCreateOrganization = () => { + this.setState({isAddingOrganization: true}) + } + + handleCancelCreateOrganization = () => { + this.setState({isAddingOrganization: false}) + } + + handleCreateOrganization = newOrganization => { + const {onCreateOrg} = this.props + onCreateOrg(newOrganization) + } + + render() { + const {organizations, onDeleteOrg, onRenameOrg} = this.props + const {isAddingOrganization} = this.state + + return ( +
+
+
+
+
+

22 Organizations

+ +
+
+
+
ID
+
Name
+
+ {isAddingOrganization + ? + : null} + {organizations.map(org => + + )} +
+
+
+
+
+ ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +OrganizationsTable.propTypes = { + organizations: arrayOf( + shape({ + id: string, // when optimistically created, organization will not have an id + name: string.isRequired, + }) + ).isRequired, + onCreateOrg: func.isRequired, + onDeleteOrg: func.isRequired, + onRenameOrg: func.isRequired, +} +export default OrganizationsTable diff --git a/ui/src/admin/containers/OrganizationsPage.js b/ui/src/admin/containers/OrganizationsPage.js new file mode 100644 index 0000000000..dc1654a9b9 --- /dev/null +++ b/ui/src/admin/containers/OrganizationsPage.js @@ -0,0 +1,96 @@ +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 OrganizationsTable from 'src/admin/components/chronograf/OrganizationsTable' +import FancyScrollbar from 'shared/components/FancyScrollbar' + +class OrganizationsPage extends Component { + constructor(props) { + super(props) + } + + componentDidMount() { + const {links, actions: {loadOrganizationsAsync}} = this.props + + loadOrganizationsAsync(links.organizations) + } + + handleCreateOrganization = organizationName => { + const {links, actions: {createOrganizationAsync}} = this.props + createOrganizationAsync(links.organizations, {name: organizationName}) + } + + handleRenameOrganization = (organization, name) => { + const {actions: {renameOrganizationAsync}} = this.props + renameOrganizationAsync(organization, {...organization, name}) + } + + handleDeleteOrganization = organization => { + const {actions: {deleteOrganizationAsync}} = this.props + deleteOrganizationAsync(organization) + } + + render() { + const {organizations} = this.props + + return ( +
+
+
+
+

Admin

+
+
+
+ + {organizations + ? + :
} + +
+ ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +OrganizationsPage.propTypes = { + links: shape({ + organizations: string.isRequired, + }), + organizations: arrayOf( + shape({ + id: string, // when optimistically created, it will not have an id + name: string.isRequired, + link: string, + }) + ), + actions: shape({ + loadOrganizationsAsync: func.isRequired, + createOrganizationAsync: func.isRequired, + renameOrganizationAsync: func.isRequired, + deleteOrganizationAsync: func.isRequired, + }), + notify: func.isRequired, +} + +const mapStateToProps = ({links, adminChronograf: {organizations}}) => ({ + links, + organizations, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators(adminChronografActionCreators, dispatch), + notify: bindActionCreators(publishAutoDismissingNotification, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(OrganizationsPage) diff --git a/ui/src/admin/index.js b/ui/src/admin/index.js index a985904015..5663e7aeb3 100644 --- a/ui/src/admin/index.js +++ b/ui/src/admin/index.js @@ -1,4 +1,5 @@ import AdminInfluxDBPage from './containers/AdminInfluxDBPage' import AdminChronografPage from './containers/AdminChronografPage' +import OrganizationsPage from './containers/OrganizationsPage' -export {AdminChronografPage, AdminInfluxDBPage} +export {AdminChronografPage, AdminInfluxDBPage, OrganizationsPage} diff --git a/ui/src/side_nav/components/UserNavBlock.js b/ui/src/side_nav/components/UserNavBlock.js index eee12c2314..8d64518094 100644 --- a/ui/src/side_nav/components/UserNavBlock.js +++ b/ui/src/side_nav/components/UserNavBlock.js @@ -1,7 +1,10 @@ import React, {PropTypes, Component} from 'react' +import {Link} from 'react-router' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' +import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized' + import classnames from 'classnames' import {meChangeOrganizationAsync} from 'shared/actions/auth' @@ -31,9 +34,25 @@ class UserNavBlock extends Component {
+ {currentOrganization.name} (Member) +
+
{me.name}
-
Organizations
+ + + Manage Users + + + + + Manage Organizations + + + + Logout + +
Switch Organizations
{roles.map((role, i) => { const isLinkCurrentOrg = currentOrganization.id === role.organization @@ -53,10 +72,6 @@ class UserNavBlock extends Component { ) })} - - Logout - - {customLinks ?
: null} {customLinks ?
Custom Links
: null} diff --git a/ui/src/side_nav/containers/SideNav.js b/ui/src/side_nav/containers/SideNav.js index 8f73692a6e..41b01d9a58 100644 --- a/ui/src/side_nav/containers/SideNav.js +++ b/ui/src/side_nav/containers/SideNav.js @@ -88,6 +88,9 @@ const SideNav = React.createClass({
+ {isUsingAuth + ? + : null} - {isUsingAuth - ? - : null} }, }) diff --git a/ui/src/style/layout/sidebar.scss b/ui/src/style/layout/sidebar.scss index 26261ad99c..c7c4fd2fe4 100644 --- a/ui/src/style/layout/sidebar.scss +++ b/ui/src/style/layout/sidebar.scss @@ -204,11 +204,7 @@ $sidebar-menu--gutter: 18px; left: 0px; transform: translate(-50%,-50%) rotate(45deg); } -.sidebar-menu--divider { - width: 100%; - height: 2px; - @include gradient-h($c-laser,$c-potassium); -} + .sidebar-menu--section { white-space: nowrap; font-size: 13px; @@ -217,4 +213,16 @@ $sidebar-menu--gutter: 18px; padding: 4px $sidebar-menu--gutter; text-transform: uppercase; color: $c-hydrogen; + @include no-user-select(); + position: relative; + + &:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 2px; + @include gradient-h($c-laser,$c-potassium); + } }