Merge pull request #985 from influxdata/feature/937-add-users-to-roles
Feature/937 feature/933 updating users and permissions for a rolepull/987/head
commit
2dd011d088
|
@ -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,
|
||||
|
|
|
@ -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}`))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue