Merge pull request #985 from influxdata/feature/937-add-users-to-roles

Feature/937 feature/933 updating users and permissions for a role
pull/987/head
Jared Scheib 2017-03-09 15:13:39 -08:00 committed by GitHub
commit 2dd011d088
8 changed files with 156 additions and 39 deletions

View File

@ -46,7 +46,7 @@
'arrow-parens': 0,
'comma-dangle': [2, 'always-multiline'],
'no-cond-assign': 2,
'no-console': 2,
'no-console': ['error', {allow: ['error']}],
'no-constant-condition': 2,
'no-control-regex': 2,
'no-debugger': 2,

View File

@ -4,9 +4,10 @@ import {
createUser as createUserAJAX,
deleteRole as deleteRoleAJAX,
deleteUser as deleteUserAJAX,
updateRoleUsers as updateRoleUsersAJAX,
updateRolePermissions as updateRolePermissionsAJAX,
} from 'src/admin/apis'
import {killQuery as killQueryProxy} from 'shared/apis/metaQuery'
import {publishNotification} from 'src/shared/actions/notifications';
import {ADMIN_NOTIFICATION_DELAY} from 'shared/constants'
@ -141,3 +142,21 @@ export const deleteUserAsync = (user, addFlashMessage) => (dispatch) => {
// delete user on server
deleteUserAJAX(user.links.self, addFlashMessage, user.name)
}
export const updateRoleUsersAsync = (role, users) => async (dispatch) => {
try {
await updateRoleUsersAJAX(role.links.self, users)
dispatch(publishNotification('success', 'Role users updated'))
} catch (error) {
dispatch(publishNotification('error', `Failed to update role: ${error.data.message}`))
}
}
export const updateRolePermissionsAsync = (role, permissions) => async (dispatch) => {
try {
await updateRolePermissionsAJAX(role.links.self, permissions)
dispatch(publishNotification('success', 'Role permissions updated'))
} catch (error) {
dispatch(publishNotification('error', `Failed to updated role: ${error.data.message}`))
}
}

View File

@ -73,3 +73,31 @@ export const deleteUser = async (url, addFlashMessage, username) => {
})
}
}
export const updateRoleUsers = async (url, users) => {
try {
await AJAX({
method: 'PATCH',
url,
data: {
users,
},
})
} catch (error) {
console.error(error)
}
}
export const updateRolePermissions = async (url, permissions) => {
try {
await AJAX({
method: 'PATCH',
url,
data: {
permissions,
},
})
} catch (error) {
console.error(error)
}
}

View File

@ -18,6 +18,8 @@ const AdminTabs = ({
onDeleteUser,
onFilterRoles,
onFilterUsers,
onUpdateRoleUsers,
onUpdateRolePermissions,
}) => {
const hasRoles = !!source.links.roles
@ -39,7 +41,16 @@ const AdminTabs = ({
},
{
type: 'Roles',
component: (<RolesTable roles={roles} onDelete={onDeleteRole} onFilter={onFilterRoles} />),
component: (
<RolesTable
roles={roles}
allUsers={users}
onDelete={onDeleteRole}
onFilter={onFilterRoles}
onUpdateRoleUsers={onUpdateRoleUsers}
onUpdateRolePermissions={onUpdateRolePermissions}
/>
),
},
{
type: 'Queries',
@ -94,6 +105,8 @@ AdminTabs.propTypes = {
onDeleteUser: func.isRequired,
onFilterRoles: func.isRequired,
onFilterUsers: func.isRequired,
onUpdateRoleUsers: func.isRequired,
onUpdateRolePermissions: func.isRequired,
}
export default AdminTabs

View File

@ -4,7 +4,8 @@ import _ from 'lodash'
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
import DeleteRow from 'src/admin/components/DeleteRow'
const PERMISSIONS = [
// TODO: replace with permissions list from server
const ALL_PERMISSIONS = [
"NoPermissions",
"ViewAdmin",
"ViewChronograf",
@ -26,38 +27,55 @@ const PERMISSIONS = [
"KapacitorConfigAPI",
]
const RoleRow = ({role: {name, permissions, users}, role, onDelete}) => (
<tr>
<td>{name}</td>
<td>
{
permissions && permissions.length ?
<MultiSelectDropdown
items={PERMISSIONS}
selectedItems={_.get(permissions, ['0', 'allowed'], [])}
label={'Select Permissions'}
onApply={() => '//TODO'}
/> :
'\u2014'
}
</td>
<td>
{
users && users.length ?
<MultiSelectDropdown
items={users.map((r) => r.name)}
selectedItems={[]}
label={'Select Users'}
onApply={() => '//TODO'}
/> :
'\u2014'
}
</td>
<td className="text-right" style={{width: "85px"}}>
<DeleteRow onDelete={onDelete} item={role} />
</td>
</tr>
)
const RoleRow = ({
role: {name, permissions, users},
role,
allUsers,
onDelete,
onUpdateRoleUsers,
onUpdateRolePermissions,
}) => {
const handleUpdateUsers = (u) => {
onUpdateRoleUsers(role, u.map((n) => ({name: n})))
}
const handleUpdatePermissions = (allowed) => {
onUpdateRolePermissions(role, [{scope: 'all', allowed}])
}
const perms = _.get(permissions, ['0', 'allowed'], [])
return (
<tr>
<td>{name}</td>
<td>
{
permissions && permissions.length ?
<MultiSelectDropdown
items={ALL_PERMISSIONS}
selectedItems={perms}
label={perms.length ? '' : 'Select Permissions'}
onApply={handleUpdatePermissions}
/> : '\u2014'
}
</td>
<td>
{
allUsers && allUsers.length ?
<MultiSelectDropdown
items={allUsers.map((u) => u.name)}
selectedItems={users.map((u) => u.name)}
label={users.length ? '' : 'Select Users'}
onApply={handleUpdateUsers}
/> : '\u2014'
}
</td>
<td className="text-right" style={{width: "85px"}}>
<DeleteRow onDelete={onDelete} item={role} />
</td>
</tr>
)
}
const {
arrayOf,
@ -77,6 +95,9 @@ RoleRow.propTypes = {
})),
}).isRequired,
onDelete: func.isRequired,
allUsers: arrayOf(shape()),
onUpdateRoleUsers: func.isRequired,
onUpdateRolePermissions: func.isRequired,
}
export default RoleRow

View File

@ -3,7 +3,14 @@ import RoleRow from 'src/admin/components/RoleRow'
import EmptyRow from 'src/admin/components/EmptyRow'
import FilterBar from 'src/admin/components/FilterBar'
const RolesTable = ({roles, onDelete, onFilter}) => (
const RolesTable = ({
roles,
allUsers,
onDelete,
onFilter,
onUpdateRoleUsers,
onUpdateRolePermissions,
}) => (
<div className="panel panel-info">
<FilterBar type="roles" onFilter={onFilter} />
<div className="panel-body">
@ -20,7 +27,14 @@ const RolesTable = ({roles, onDelete, onFilter}) => (
{
roles.length ?
roles.filter(r => !r.hidden).map((role) =>
<RoleRow key={role.name} role={role} onDelete={onDelete} />
<RoleRow
key={role.name}
allUsers={allUsers}
role={role}
onDelete={onDelete}
onUpdateRoleUsers={onUpdateRoleUsers}
onUpdateRolePermissions={onUpdateRolePermissions}
/>
) : <EmptyRow tableName={'Roles'} />
}
</tbody>
@ -49,6 +63,9 @@ RolesTable.propTypes = {
})),
onDelete: func.isRequired,
onFilter: func,
allUsers: arrayOf(shape()),
onUpdateRoleUsers: func.isRequired,
onUpdateRolePermissions: func.isRequired,
}
export default RolesTable

View File

@ -10,6 +10,8 @@ import {
createUserAsync,
deleteRoleAsync,
deleteUserAsync,
updateRoleUsersAsync,
updateRolePermissionsAsync,
filterRoles as filterRolesAction,
filterUsers as filterUsersAction,
} from 'src/admin/actions'
@ -30,6 +32,8 @@ class AdminPage extends Component {
this.handleCancelEdit = ::this.handleCancelEdit
this.handleDeleteRole = ::this.handleDeleteRole
this.handleDeleteUser = ::this.handleDeleteUser
this.handleUpdateRoleUsers = ::this.handleUpdateRoleUsers
this.handleUpdateRolePermissions = ::this.handleUpdateRolePermissions
}
componentDidMount() {
@ -76,6 +80,14 @@ class AdminPage extends Component {
this.props.deleteUser(user, this.props.addFlashMessage)
}
handleUpdateRoleUsers(role, users) {
this.props.updateRoleUsers(role, users)
}
handleUpdateRolePermissions(role, permissions) {
this.props.updateRolePermissions(role, permissions)
}
render() {
const {users, roles, source, filterUsers, filterRoles, addFlashMessage} = this.props
@ -109,6 +121,8 @@ class AdminPage extends Component {
onFilterUsers={filterUsers}
onFilterRoles={filterRoles}
addFlashMessage={addFlashMessage}
onUpdateRoleUsers={this.handleUpdateRoleUsers}
onUpdateRolePermissions={this.handleUpdateRolePermissions}
/> :
<span>Loading...</span>
}
@ -147,6 +161,8 @@ AdminPage.propTypes = {
addFlashMessage: func,
filterRoles: func,
filterUsers: func,
updateRoleUsers: func,
updateRolePermissions: func,
}
const mapStateToProps = ({admin: {users, roles}}) => ({
@ -165,6 +181,8 @@ const mapDispatchToProps = (dispatch) => ({
deleteUser: bindActionCreators(deleteUserAsync, dispatch),
filterRoles: bindActionCreators(filterRolesAction, dispatch),
filterUsers: bindActionCreators(filterUsersAction, dispatch),
updateRoleUsers: bindActionCreators(updateRoleUsersAsync, dispatch),
updateRolePermissions: bindActionCreators(updateRolePermissionsAsync, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AdminPage)

View File

@ -10,10 +10,11 @@ const labelText = ({localSelectedItems, isOpen, label}) => {
return localSelectedItems.map((s) => s).join(', ')
}
// TODO: be smarter about the text displayed here
if (isOpen) {
return '0 Selected'
}
return 'Apply Function'
return 'None'
}
class MultiSelectDropdown extends Component {