Merge pull request #2298 from influxdata/multitenancy_ui_manage_orgs

Dedicated Organizations Page
pull/10616/head
Alex Paxton 2017-11-07 20:37:45 -08:00 committed by GitHub
commit 85ede680d8
6 changed files with 223 additions and 14 deletions

View File

@ -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 (
<div className="container-fluid">
<div className="row">
<div className="col-xs-12">
<div className="panel panel-minimal">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">22 Organizations</h2>
<button
className="btn btn-sm btn-primary"
onClick={this.handleClickCreateOrganization}
disabled={isAddingOrganization}
>
<span className="icon plus" /> Create Organization
</button>
</div>
<div className="panel-body">
<div className="manage-orgs-form--org-labels">
<div className="manage-orgs-form--id">ID</div>
<div className="manage-orgs-form--name">Name</div>
</div>
{isAddingOrganization
? <NewOrganization
onCreateOrganization={this.handleCreateOrganization}
onCancelCreateOrganization={
this.handleCancelCreateOrganization
}
/>
: null}
{organizations.map(org =>
<Organization
key={org.name}
organization={org}
onDelete={onDeleteOrg}
onRename={onRenameOrg}
/>
)}
</div>
</div>
</div>
</div>
</div>
)
}
}
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

View File

@ -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 (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Admin</h1>
</div>
</div>
</div>
<FancyScrollbar className="page-contents">
{organizations
? <OrganizationsTable
organizations={organizations}
onCreateOrg={this.handleCreateOrganization}
onDeleteOrg={this.handleDeleteOrganization}
onRenameOrg={this.handleRenameOrganization}
/>
: <div className="page-spinner" />}
</FancyScrollbar>
</div>
)
}
}
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)

View File

@ -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}

View File

@ -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 {
</div>
<div className="sidebar-menu">
<div className="sidebar-menu--heading">
{currentOrganization.name} (Member)
</div>
<div className="sidebar-menu--section">
{me.name}
</div>
<div className="sidebar-menu--section">Organizations</div>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<Link className="sidebar-menu--item" to="/admin/users">
Manage Users
</Link>
</Authorized>
<Authorized requiredRole={SUPERADMIN_ROLE}>
<Link className="sidebar-menu--item" to="/admin/organizations">
Manage Organizations
</Link>
</Authorized>
<a className="sidebar-menu--item" href={logoutLink}>
Logout
</a>
<div className="sidebar-menu--section">Switch Organizations</div>
{roles.map((role, i) => {
const isLinkCurrentOrg =
currentOrganization.id === role.organization
@ -53,10 +72,6 @@ class UserNavBlock extends Component {
</span>
)
})}
<a className="sidebar-menu--item" href={logoutLink}>
Logout
</a>
{customLinks ? <div className="sidebar-menu--divider" /> : null}
{customLinks
? <div className="sidebar-menu--section">Custom Links</div>
: null}

View File

@ -88,6 +88,9 @@ const SideNav = React.createClass({
<span className="sidebar--icon icon cubo-uniform" />
</Link>
</div>
{isUsingAuth
? <UserNavBlock logoutLink={logoutLink} links={links} me={me} />
: null}
<NavBlock
icon="cubo-node"
link={`${sourcePrefix}/hosts`}
@ -168,9 +171,6 @@ const SideNav = React.createClass({
title="Configuration"
/>
</NavBlock>
{isUsingAuth
? <UserNavBlock logoutLink={logoutLink} links={links} me={me} />
: null}
</NavBar>
},
})

View File

@ -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);
}
}