Remove batch actions, user add/remove to/from org, & user selectability from Chronograf Admin Table
parent
cdbec69280
commit
676c079d62
|
@ -1,59 +0,0 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
|
||||
import {USER_ROLES} from 'src/admin/constants/dummyUsers'
|
||||
|
||||
const BatchActionsBar = ({
|
||||
organizations,
|
||||
onDeleteUsers,
|
||||
onChangeRoles,
|
||||
numUsersSelected,
|
||||
onAddUserToOrg,
|
||||
}) => {
|
||||
const rolesDropdownItems = USER_ROLES.map(role => ({
|
||||
...role,
|
||||
text: role.name,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="chronograf-admin-table--batch">
|
||||
<p className="chronograf-admin-table--num-selected">
|
||||
{numUsersSelected} User{numUsersSelected === 1 ? ' ' : 's '}Selected
|
||||
</p>
|
||||
{numUsersSelected > 0
|
||||
? <div className="chronograf-admin-table--batch-actions">
|
||||
<div className="btn btn-sm btn-danger" onClick={onDeleteUsers}>
|
||||
Delete
|
||||
</div>
|
||||
<Dropdown
|
||||
items={rolesDropdownItems}
|
||||
selected={'Set New Role'}
|
||||
onChoose={onChangeRoles}
|
||||
buttonColor="btn-primary"
|
||||
className="dropdown-140"
|
||||
/>
|
||||
<Dropdown
|
||||
items={organizations.map(org => ({...org, text: org.name}))}
|
||||
selected={'Add to Org'}
|
||||
onChoose={onAddUserToOrg}
|
||||
buttonColor="btn-primary"
|
||||
className="dropdown-240"
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {arrayOf, func, number, shape} = PropTypes
|
||||
|
||||
BatchActionsBar.propTypes = {
|
||||
organizations: arrayOf(shape()),
|
||||
onDeleteUsers: func.isRequired,
|
||||
onChangeRoles: func.isRequired,
|
||||
numUsersSelected: number.isRequired,
|
||||
onAddUserToOrg: func.isRequired,
|
||||
}
|
||||
|
||||
export default BatchActionsBar
|
|
@ -74,7 +74,6 @@ class NewUserTableRow extends Component {
|
|||
|
||||
return (
|
||||
<tr className="chronograf-admin-table--new-user">
|
||||
<td className="chronograf-admin-table--check-col" />
|
||||
<td>
|
||||
<input
|
||||
className="form-control input-xs"
|
||||
|
|
|
@ -11,9 +11,6 @@ import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
|
|||
const OrgTableRow = ({
|
||||
user,
|
||||
organization,
|
||||
onToggleUserSelected,
|
||||
selectedUsers,
|
||||
isSameUser,
|
||||
onChangeUserRole,
|
||||
onChangeSuperAdmin,
|
||||
}) => {
|
||||
|
@ -25,30 +22,13 @@ const OrgTableRow = ({
|
|||
colActions,
|
||||
} = USERS_TABLE
|
||||
|
||||
const isSelected = selectedUsers.find(u => isSameUser(user, u))
|
||||
|
||||
const currentRole = user.roles.find(
|
||||
role => role.organization === organization.id
|
||||
)
|
||||
|
||||
return (
|
||||
<tr
|
||||
className={
|
||||
isSelected
|
||||
? 'chronograf-admin-table--user selected'
|
||||
: 'chronograf-admin-table--user'
|
||||
}
|
||||
>
|
||||
<td
|
||||
onClick={onToggleUserSelected(user)}
|
||||
className="chronograf-admin-table--check-col chronograf-admin-table--selectable"
|
||||
>
|
||||
<div className="user-checkbox" />
|
||||
</td>
|
||||
<td
|
||||
onClick={onToggleUserSelected(user)}
|
||||
className="chronograf-admin-table--selectable"
|
||||
>
|
||||
<tr className={'chronograf-admin-table--user'}>
|
||||
<td>
|
||||
<strong>
|
||||
{user.name}
|
||||
</strong>
|
||||
|
@ -88,7 +68,7 @@ const OrgTableRow = ({
|
|||
)
|
||||
}
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
OrgTableRow.propTypes = {
|
||||
user: shape(),
|
||||
|
@ -96,9 +76,6 @@ OrgTableRow.propTypes = {
|
|||
name: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}),
|
||||
onToggleUserSelected: func.isRequired,
|
||||
selectedUsers: arrayOf(shape()),
|
||||
isSameUser: func.isRequired,
|
||||
onChangeUserRole: func.isRequired,
|
||||
onChangeSuperAdmin: func.isRequired,
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
|
||||
import OrgTableRow from 'src/admin/components/chronograf/OrgTableRow'
|
||||
import NewUserTableRow from 'src/admin/components/chronograf/NewUserTableRow'
|
||||
import BatchActionsBar from 'src/admin/components/chronograf/BatchActionsBar'
|
||||
|
||||
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
|
||||
|
||||
|
@ -36,28 +33,8 @@ class UsersTable extends Component {
|
|||
this.setState({isCreatingUser: false})
|
||||
}
|
||||
|
||||
areSameUsers = (usersA, usersB) => {
|
||||
if (usersA.length === 0 && usersB.length === 0) {
|
||||
return false
|
||||
}
|
||||
const {isSameUser} = this.props
|
||||
return !_.differenceWith(usersA, usersB, isSameUser).length
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
organization,
|
||||
users,
|
||||
organizations,
|
||||
onToggleAllUsersSelected,
|
||||
onToggleUserSelected,
|
||||
selectedUsers,
|
||||
isSameUser,
|
||||
onCreateUser,
|
||||
onDeleteUsers,
|
||||
onChangeRoles,
|
||||
onAddUserToOrg,
|
||||
} = this.props
|
||||
const {organization, users, onCreateUser} = this.props
|
||||
|
||||
const {isCreatingUser} = this.state
|
||||
const {
|
||||
|
@ -68,35 +45,16 @@ class UsersTable extends Component {
|
|||
colActions,
|
||||
} = USERS_TABLE
|
||||
|
||||
const areAllSelected = this.areSameUsers(users, selectedUsers)
|
||||
|
||||
return (
|
||||
<div className="panel panel-minimal">
|
||||
<UsersTableHeader
|
||||
numUsers={users.length}
|
||||
onCreateUserRow={this.handleClickCreateUserRow}
|
||||
/>
|
||||
<BatchActionsBar
|
||||
organizations={organizations}
|
||||
numUsersSelected={selectedUsers.length}
|
||||
onDeleteUsers={onDeleteUsers}
|
||||
onChangeRoles={onChangeRoles}
|
||||
onAddUserToOrg={onAddUserToOrg}
|
||||
/>
|
||||
<div className="panel-body">
|
||||
<table className="table table-highlight v-center chronograf-admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="chronograf-admin-table--check-col">
|
||||
<div
|
||||
className={
|
||||
areAllSelected
|
||||
? 'user-checkbox selected'
|
||||
: 'user-checkbox'
|
||||
}
|
||||
onClick={onToggleAllUsersSelected(areAllSelected)}
|
||||
/>
|
||||
</th>
|
||||
<th>Username</th>
|
||||
<th style={{width: colRole}} className="align-with-col-text">
|
||||
Role
|
||||
|
@ -124,9 +82,6 @@ class UsersTable extends Component {
|
|||
<OrgTableRow
|
||||
user={user}
|
||||
key={i}
|
||||
onToggleUserSelected={onToggleUserSelected}
|
||||
selectedUsers={selectedUsers}
|
||||
isSameUser={isSameUser}
|
||||
organization={organization}
|
||||
onChangeUserRole={this.handleChangeUserRole}
|
||||
onChangeSuperAdmin={this.handleChangeSuperAdmin}
|
||||
|
@ -158,20 +113,12 @@ const {arrayOf, func, shape, string} = PropTypes
|
|||
|
||||
UsersTable.propTypes = {
|
||||
users: arrayOf(shape()),
|
||||
organizations: arrayOf(shape()),
|
||||
selectedUsers: arrayOf(shape()),
|
||||
onToggleUserSelected: func.isRequired,
|
||||
onToggleAllUsersSelected: func.isRequired,
|
||||
isSameUser: func.isRequired,
|
||||
organization: shape({
|
||||
name: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}),
|
||||
onUpdateUserRole: func.isRequired,
|
||||
onCreateUser: func.isRequired,
|
||||
onUpdateUserRole: func.isRequired,
|
||||
onUpdateUserSuperAdmin: func.isRequired,
|
||||
onDeleteUsers: func.isRequired,
|
||||
onChangeRoles: func.isRequired,
|
||||
onAddUserToOrg: func.isRequired,
|
||||
}
|
||||
export default UsersTable
|
||||
|
|
|
@ -12,19 +12,9 @@ import UsersTable from 'src/admin/components/chronograf/UsersTable'
|
|||
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
|
||||
import {isSameUser} from 'shared/reducers/helpers/auth'
|
||||
|
||||
import {DEFAULT_ORG_ID} from 'src/admin/constants/dummyUsers'
|
||||
|
||||
class AdminChronografPage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
selectedUsers: [],
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: revisit this, possibly don't call setState if both are deep equal
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {currentOrganization} = nextProps
|
||||
|
@ -42,36 +32,9 @@ class AdminChronografPage extends Component {
|
|||
}
|
||||
|
||||
loadUsers = () => {
|
||||
const {
|
||||
links,
|
||||
actions: {loadUsersAsync, loadOrganizationsAsync},
|
||||
} = this.props
|
||||
const {links, actions: {loadUsersAsync}} = this.props
|
||||
|
||||
loadUsersAsync(links.users)
|
||||
loadOrganizationsAsync(links.organizations)
|
||||
}
|
||||
|
||||
handleToggleUserSelected = user => e => {
|
||||
e.preventDefault()
|
||||
|
||||
const {selectedUsers} = this.state
|
||||
|
||||
const isUserSelected = selectedUsers.find(u => isSameUser(user, u))
|
||||
|
||||
const newSelectedUsers = isUserSelected
|
||||
? selectedUsers.filter(u => !isSameUser(user, u))
|
||||
: [...selectedUsers, user]
|
||||
|
||||
this.setState({selectedUsers: newSelectedUsers})
|
||||
}
|
||||
handleToggleAllUsersSelected = areAllSelected => () => {
|
||||
const {users} = this.props
|
||||
|
||||
if (areAllSelected) {
|
||||
this.setState({selectedUsers: []})
|
||||
} else {
|
||||
this.setState({selectedUsers: users})
|
||||
}
|
||||
}
|
||||
|
||||
// SINGLE USER ACTIONS
|
||||
|
@ -93,33 +56,7 @@ class AdminChronografPage extends Component {
|
|||
}
|
||||
createUserAsync(links.users, newUser)
|
||||
}
|
||||
// handleAddUserToOrg will add a user to an organization as a 'member'. if
|
||||
// the user already has a role in that organization, it will do nothing.
|
||||
|
||||
handleAddUserToOrg = (user, organization) => {
|
||||
const {actions: {updateUserAsync}} = this.props
|
||||
|
||||
updateUserAsync(user, {
|
||||
...user,
|
||||
roles: [
|
||||
...user.roles,
|
||||
{
|
||||
name: MEMBER_ROLE, // TODO: remove this to let server decide when default org role is implemented
|
||||
organization: organization.id,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
handleRemoveUserFromOrg = (user, organization) => {
|
||||
const {actions: {updateUserAsync}} = this.props
|
||||
|
||||
let newRoles = user.roles.filter(r => r.organization !== organization.id)
|
||||
if (newRoles.length === 0) {
|
||||
newRoles = [{organization: DEFAULT_ORG_ID, name: MEMBER_ROLE}]
|
||||
}
|
||||
|
||||
updateUserAsync(user, {...user, roles: newRoles})
|
||||
}
|
||||
handleUpdateUserRole = () => (user, currentRole, {name}) => {
|
||||
const {actions: {updateUserAsync}} = this.props
|
||||
|
||||
|
@ -142,54 +79,8 @@ class AdminChronografPage extends Component {
|
|||
deleteUserAsync(user)
|
||||
}
|
||||
|
||||
// BATCH USER ACTIONS
|
||||
// TODO: make batch actions work for batch. currently only work for one user
|
||||
// since batch actions have not been implemented in the API.
|
||||
handleBatchChangeUsersRole = () => {}
|
||||
handleBatchAddUsersToOrg = organization => {
|
||||
const {notify} = this.props
|
||||
const {selectedUsers} = this.state
|
||||
|
||||
if (selectedUsers.length > 1) {
|
||||
notify(
|
||||
'error',
|
||||
'Batch actions for more than 1 user not currently supported'
|
||||
)
|
||||
} else {
|
||||
this.handleAddUserToOrg(selectedUsers[0], organization)
|
||||
}
|
||||
}
|
||||
handleBatchRemoveUsersFromOrg = organization => {
|
||||
const {notify} = this.props
|
||||
const {selectedUsers} = this.state
|
||||
|
||||
if (selectedUsers.length > 1) {
|
||||
notify(
|
||||
'error',
|
||||
'Batch actions for more than 1 user not currently supported'
|
||||
)
|
||||
} else {
|
||||
this.handleRemoveUserFromOrg(selectedUsers[0], organization)
|
||||
}
|
||||
}
|
||||
handleBatchDeleteUsers = () => {
|
||||
const {notify} = this.props
|
||||
// const {selectedUsers} = this.state
|
||||
|
||||
if (this.state.selectedUsers.length > 1) {
|
||||
notify(
|
||||
'error',
|
||||
'Batch actions for more than 1 user not currently supported'
|
||||
)
|
||||
} else {
|
||||
this.handleDeleteUser(this.state.selectedUsers[0])
|
||||
this.setState({selectedUsers: []})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {users, organizations, currentOrganization} = this.props
|
||||
const {selectedUsers} = this.state
|
||||
const {users, currentOrganization} = this.props
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
|
@ -200,21 +91,12 @@ class AdminChronografPage extends Component {
|
|||
<div className="row">
|
||||
<div className="col-xs-12">
|
||||
<UsersTable
|
||||
onAddUserToOrg={this.handleBatchAddUsersToOrg}
|
||||
users={users}
|
||||
organizations={organizations}
|
||||
selectedUsers={selectedUsers}
|
||||
onToggleUserSelected={this.handleToggleUserSelected}
|
||||
onToggleAllUsersSelected={
|
||||
this.handleToggleAllUsersSelected
|
||||
}
|
||||
isSameUser={isSameUser}
|
||||
organization={currentOrganization}
|
||||
onUpdateUserRole={this.handleUpdateUserRole()}
|
||||
onCreateUser={this.handleCreateUser}
|
||||
onUpdateUserRole={this.handleUpdateUserRole()}
|
||||
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin()}
|
||||
onDeleteUsers={this.handleBatchDeleteUsers}
|
||||
onChangeRoles={this.handleBatchChangeUsersRole}
|
||||
onDeleteUser={this.handleDeleteUser}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -231,17 +113,14 @@ const {arrayOf, func, shape, string} = PropTypes
|
|||
AdminChronografPage.propTypes = {
|
||||
links: shape({
|
||||
users: string.isRequired,
|
||||
organizations: string.isRequired,
|
||||
}),
|
||||
users: arrayOf(shape),
|
||||
organizations: arrayOf(shape),
|
||||
currentOrganization: shape({
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
actions: shape({
|
||||
loadUsersAsync: func.isRequired,
|
||||
loadOrganizationsAsync: func.isRequired,
|
||||
createUserAsync: func.isRequired,
|
||||
updateUserAsync: func.isRequired,
|
||||
deleteUserAsync: func.isRequired,
|
||||
|
@ -251,12 +130,11 @@ AdminChronografPage.propTypes = {
|
|||
|
||||
const mapStateToProps = ({
|
||||
links,
|
||||
adminChronograf: {users, organizations},
|
||||
adminChronograf: {users},
|
||||
auth: {me: {currentOrganization}},
|
||||
}) => ({
|
||||
links,
|
||||
users,
|
||||
organizations,
|
||||
currentOrganization,
|
||||
})
|
||||
|
||||
|
|
|
@ -25,57 +25,6 @@ table.table.chronograf-admin-table .dropdown {
|
|||
table.table.chronograf-admin-table thead tr th.align-with-col-text {
|
||||
padding-left: 15px;
|
||||
}
|
||||
.user-checkbox {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 3px;
|
||||
background-color: $g2-kevlar;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(1, 1);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
background-color: $c-pool;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
tr.selected .user-checkbox:after,
|
||||
.user-checkbox.selected:after {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(0.4, 0.4);
|
||||
}
|
||||
|
||||
table.table.chronograf-admin-table tbody tr.selected {
|
||||
background-color: $g5-pepper;
|
||||
}
|
||||
table.table.chronograf-admin-table tbody tr.selected td {
|
||||
color: $g18-cloud;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
table.table.chronograf-admin-table thead tr th.chronograf-admin-table--check-col,
|
||||
table.table.chronograf-admin-table tbody tr td.chronograf-admin-table--check-col {
|
||||
width: 28px;
|
||||
padding-right: 0;
|
||||
}
|
||||
table.table.chronograf-admin-table thead tr th.chronograf-admin-table--selectable,
|
||||
table.table.chronograf-admin-table tbody tr td.chronograf-admin-table--selectable {
|
||||
&:hover {cursor: pointer;}
|
||||
}
|
||||
|
||||
.dropdown-label {
|
||||
margin: 0 8px 0 0;
|
||||
font-weight: 700;
|
||||
|
@ -123,7 +72,9 @@ table.table.chronograf-admin-table tbody tr.chronograf-admin-table--user td div.
|
|||
background-color: $g3-castle;
|
||||
color: $g13-mist;
|
||||
|
||||
> .caret {opacity: 0;}
|
||||
> .caret {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
table.table.chronograf-admin-table tbody tr.chronograf-admin-table--user.selected td div.dropdown div.btn.dropdown-toggle {
|
||||
background-color: $g5-pepper;
|
||||
|
@ -132,7 +83,9 @@ table.table.chronograf-admin-table tbody tr.chronograf-admin-table--user:hover t
|
|||
background-color: $c-pool;
|
||||
color: $g20-white;
|
||||
|
||||
> .caret {opacity: 1;}
|
||||
> .caret {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $c-laser;
|
||||
|
@ -143,7 +96,9 @@ table.table.chronograf-admin-table tbody tr.chronograf-admin-table--user td div.
|
|||
background-color: $c-hydrogen;
|
||||
color: $g20-white;
|
||||
|
||||
> .caret {opacity: 1;}
|
||||
> .caret {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles for new user row */
|
||||
|
|
Loading…
Reference in New Issue