Merge pull request #2298 from influxdata/multitenancy_ui_manage_orgs
Dedicated Organizations Pagepull/10616/head
commit
85ede680d8
|
@ -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
|
|
@ -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)
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue