Merge pull request #3112 from influxdata/bugfix/influxdb-delete-user-role

Bugfix: Delete UserRole and handle providing no roles or permissions
pull/10616/head
Andrew Watkins 2018-04-03 12:02:27 -07:00 committed by GitHub
commit 8137ed998f
8 changed files with 346 additions and 185 deletions

View File

@ -635,7 +635,7 @@ type sourceUsersResponse struct {
}
func (r *sourceUserRequest) ValidUpdate() error {
if r.Password == "" && len(r.Permissions) == 0 && len(r.Roles) == 0 {
if r.Password == "" && r.Permissions == nil && r.Roles == nil {
return fmt.Errorf("No fields to update")
}
return validPermissions(&r.Permissions)

View File

@ -0,0 +1,66 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import MultiSelectDropdown from 'src/shared/components/MultiSelectDropdown'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import {User} from 'src/types/influxAdmin'
interface Props {
user: User
allPermissions: string[]
onUpdatePermissions: (user: User, permissions: any[]) => void
}
class UserPermissionsDropdown extends PureComponent<Props> {
public render() {
return (
<MultiSelectDropdown
buttonSize="btn-xs"
buttonColor="btn-primary"
resetStateOnReceiveProps={false}
items={this.allPermissions}
label={this.permissionsLabel}
customClass={this.permissionsClass}
selectedItems={this.selectedPermissions}
onApply={this.handleUpdatePermissions}
/>
)
}
private handleUpdatePermissions = (permissions): void => {
const {onUpdatePermissions, user} = this.props
const allowed = permissions.map(p => p.name)
onUpdatePermissions(user, [{scope: 'all', allowed}])
}
private get allPermissions() {
return this.props.allPermissions.map(p => ({name: p}))
}
private get userPermissions() {
return _.get(this.props.user, ['permissions', '0', 'allowed'], [])
}
private get selectedPermissions() {
return this.userPermissions.map(p => ({name: p}))
}
private get permissionsLabel() {
const {user} = this.props
if (user.permissions && user.permissions.length) {
return 'Select Permissions'
}
return ''
}
private get permissionsClass() {
return classnames(`dropdown-${USERS_TABLE.colPermissions}`, {
'admin-table--multi-select-empty': !this.props.user.permissions.length,
})
}
}
export default UserPermissionsDropdown

View File

@ -0,0 +1,58 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import MultiSelectDropdown from 'src/shared/components/MultiSelectDropdown'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import {User, UserRole} from 'src/types/influxAdmin'
interface Props {
user: User
allRoles: any[]
onUpdateRoles: (user: User, roles: UserRole[]) => void
}
class UserRoleDropdown extends PureComponent<Props> {
public render() {
const {allRoles} = this.props
return (
<MultiSelectDropdown
buttonSize="btn-xs"
buttonColor="btn-primary"
items={allRoles}
label={this.rolesLabel}
selectedItems={this.roles}
customClass={this.rolesClass}
onApply={this.handleUpdateRoles}
resetStateOnReceiveProps={false}
/>
)
}
private handleUpdateRoles = (roleNames): void => {
const {user, allRoles, onUpdateRoles} = this.props
const roles = allRoles.filter(r => roleNames.find(rn => rn.name === r.name))
onUpdateRoles(user, roles)
}
private get roles(): UserRole[] {
const roles = _.get(this.props.user, 'roles', []) as UserRole[]
return roles.map(({name}) => ({name}))
}
private get rolesClass(): string {
return classnames(`dropdown-${USERS_TABLE.colRoles}`, {
'admin-table--multi-select-empty': !this.roles.length,
})
}
private get rolesLabel(): string {
return this.roles.length ? '' : 'Select Roles'
}
}
export default UserRoleDropdown

View File

@ -1,169 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
import UserEditName from 'src/admin/components/UserEditName'
import UserNewPassword from 'src/admin/components/UserNewPassword'
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
import ConfirmOrCancel from 'shared/components/ConfirmOrCancel'
import ConfirmButton from 'shared/components/ConfirmButton'
import ChangePassRow from 'src/admin/components/ChangePassRow'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
const UserRow = ({
user: {name, roles = [], permissions, password},
user,
allRoles,
allPermissions,
hasRoles,
isNew,
isEditing,
onEdit,
onSave,
onCancel,
onDelete,
onUpdatePermissions,
onUpdateRoles,
onUpdatePassword,
}) => {
function handleUpdatePermissions(perms) {
const allowed = perms.map(p => p.name)
onUpdatePermissions(user, [{scope: 'all', allowed}])
}
function handleUpdateRoles(roleNames) {
onUpdateRoles(
user,
allRoles.filter(r => roleNames.find(rn => rn.name === r.name))
)
}
function handleUpdatePassword() {
onUpdatePassword(user, password)
}
const perms = _.get(permissions, ['0', 'allowed'], [])
const wrappedDelete = () => {
onDelete(user)
}
if (isEditing) {
return (
<tr className="admin-table--edit-row">
<UserEditName user={user} onEdit={onEdit} onSave={onSave} />
<UserNewPassword
user={user}
onEdit={onEdit}
onSave={onSave}
isNew={isNew}
/>
{hasRoles ? <td className="admin-table--left-offset">--</td> : null}
<td className="admin-table--left-offset">--</td>
<td
className="text-right"
style={{width: `${USERS_TABLE.colDelete}px`}}
>
<ConfirmOrCancel
item={user}
onConfirm={onSave}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</td>
</tr>
)
}
return (
<tr>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>{name}</td>
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
<ChangePassRow
onEdit={onEdit}
onApply={handleUpdatePassword}
user={user}
buttonSize="btn-xs"
/>
</td>
{hasRoles ? (
<td>
<MultiSelectDropdown
items={allRoles}
selectedItems={roles.map(r => ({name: r.name}))}
label={roles.length ? '' : 'Select Roles'}
onApply={handleUpdateRoles}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colRoles}`, {
'admin-table--multi-select-empty': !roles.length,
})}
resetStateOnReceiveProps={false}
/>
</td>
) : null}
<td>
{allPermissions && allPermissions.length ? (
<MultiSelectDropdown
items={allPermissions.map(p => ({name: p}))}
selectedItems={perms.map(p => ({name: p}))}
label={
permissions && permissions.length ? '' : 'Select Permissions'
}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colPermissions}`, {
'admin-table--multi-select-empty': !permissions.length,
})}
resetStateOnReceiveProps={false}
/>
) : null}
</td>
<td className="text-right" style={{width: `${USERS_TABLE.colDelete}px`}}>
<ConfirmButton
size="btn-xs"
type="btn-danger"
text="Delete User"
confirmAction={wrappedDelete}
customClass="table--show-on-row-hover"
/>
</td>
</tr>
)
}
const {arrayOf, bool, func, shape, string} = PropTypes
UserRow.propTypes = {
user: shape({
name: string,
roles: arrayOf(
shape({
name: string,
})
),
permissions: arrayOf(
shape({
name: string,
})
),
password: string,
}).isRequired,
allRoles: arrayOf(shape()),
allPermissions: arrayOf(string),
hasRoles: bool,
isNew: bool,
isEditing: bool,
onCancel: func,
onEdit: func,
onSave: func,
onDelete: func.isRequired,
onUpdatePermissions: func,
onUpdateRoles: func,
onUpdatePassword: func,
}
export default UserRow

View File

@ -0,0 +1,120 @@
import React, {PureComponent} from 'react'
import UserPermissionsDropdown from 'src/admin/components/UserPermissionsDropdown'
import UserRoleDropdown from 'src/admin/components/UserRoleDropdown'
import ChangePassRow from 'src/admin/components/ChangePassRow'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import UserRowEdit from 'src/admin/components/UserRowEdit'
import {User} from 'src/types/influxAdmin'
interface UserRowProps {
user: User
allRoles: any[]
allPermissions: string[]
hasRoles: boolean
isNew: boolean
isEditing: boolean
onCancel: () => void
onEdit: () => void
onSave: () => void
onDelete: (user: User) => void
onUpdatePermissions: (user: User, permissions: any[]) => void
onUpdateRoles: (user: User, roles: any[]) => void
onUpdatePassword: (user: User, password: string) => void
}
class UserRow extends PureComponent<UserRowProps> {
public render() {
const {
user,
allRoles,
allPermissions,
hasRoles,
isNew,
isEditing,
onEdit,
onSave,
onCancel,
onUpdatePermissions,
onUpdateRoles,
} = this.props
if (isEditing) {
return (
<UserRowEdit
user={user}
isNew={isNew}
onEdit={onEdit}
onSave={onSave}
onCancel={onCancel}
hasRoles={hasRoles}
/>
)
}
return (
<tr>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>{user.name}</td>
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
<ChangePassRow
user={user}
onEdit={onEdit}
buttonSize="btn-xs"
onApply={this.handleUpdatePassword}
/>
</td>
{hasRoles && (
<td>
<UserRoleDropdown
user={user}
allRoles={allRoles}
onUpdateRoles={onUpdateRoles}
/>
</td>
)}
<td>
{this.hasPermissions && (
<UserPermissionsDropdown
user={user}
allPermissions={allPermissions}
onUpdatePermissions={onUpdatePermissions}
/>
)}
</td>
<td
className="text-right"
style={{width: `${USERS_TABLE.colDelete}px`}}
>
<ConfirmButton
size="btn-xs"
type="btn-danger"
text="Delete User"
confirmAction={this.handleDelete}
customClass="table--show-on-row-hover"
/>
</td>
</tr>
)
}
private handleDelete = (): void => {
const {user, onDelete} = this.props
onDelete(user)
}
private handleUpdatePassword = (): void => {
const {user, onUpdatePassword} = this.props
onUpdatePassword(user, user.password)
}
private get hasPermissions() {
const {allPermissions} = this.props
return allPermissions && !!allPermissions.length
}
}
export default UserRow

View File

@ -0,0 +1,47 @@
import React, {SFC} from 'react'
import UserEditName from 'src/admin/components/UserEditName'
import UserNewPassword from 'src/admin/components/UserNewPassword'
import ConfirmOrCancel from 'src/shared/components/ConfirmOrCancel'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import {User} from 'src/types/influxAdmin'
interface UserRowEditProps {
user: User
onEdit: () => void
onSave: () => void
onCancel: () => void
isNew: boolean
hasRoles: boolean
}
const UserRowEdit: SFC<UserRowEditProps> = ({
user,
onEdit,
onSave,
onCancel,
isNew,
hasRoles,
}) => (
<tr className="admin-table--edit-row">
<UserEditName user={user} onEdit={onEdit} onSave={onSave} />
<UserNewPassword
user={user}
onEdit={onEdit}
onSave={onSave}
isNew={isNew}
/>
{hasRoles ? <td className="admin-table--left-offset">--</td> : null}
<td className="admin-table--left-offset">--</td>
<td className="text-right" style={{width: `${USERS_TABLE.colDelete}px`}}>
<ConfirmOrCancel
item={user}
onConfirm={onSave}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</td>
</tr>
)
export default UserRowEdit

View File

@ -16,9 +16,7 @@
.tab {
font-weight: 500 !important;
border-radius: $radius $radius 0 0 !important;
transition:
background-color 0.25s ease,
color 0.25s ease !important;
transition: background-color 0.25s ease, color 0.25s ease !important;
border: 0 !important;
text-align: left;
height: 60px !important;
@ -41,16 +39,24 @@
}
}
.admin-tabs--content {
.panel {border-top-left-radius: 0;}
.panel-heading {height: 60px;}
.panel {
border-top-left-radius: 0;
}
.panel-heading {
height: 60px;
}
.panel-title {
font-size: 17px;
font-weight: 400 !important;
color: $g12-forge;
padding: 6px 0;
}
.panel-body {min-height: 300px;}
.panel-heading + .panel-body {padding-top: 0;}
.panel-body {
min-height: 300px;
}
.panel-heading + .panel-body {
padding-top: 0;
}
}
/*
@ -69,7 +75,9 @@
border-radius: $radius 0 0 $radius !important;
padding: 0 0 0 16px !important;
}
& + div {padding-left: 0;}
& + div {
padding-left: 0;
}
}
}
@ -82,8 +90,13 @@
font-weight: 600;
color: $g14-chromium;
transition: none !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.caret {opacity: 0;}
.caret {
opacity: 0;
}
}
.admin-table--multi-select-empty .dropdown-toggle {
color: $g8-storm;
@ -92,7 +105,9 @@
color: $g20-white !important;
background-color: $c-pool;
.caret {opacity: 1;}
.caret {
opacity: 1;
}
&:hover {
transition: background-color 0.25s ease;
@ -100,7 +115,9 @@
}
}
table > tbody > tr > td.admin-table--left-offset,
table > thead > tr > th.admin-table--left-offset {padding-left: 15px;}
table > thead > tr > th.admin-table--left-offset {
padding-left: 15px;
}
table > tbody > tr.admin-table--edit-row,
table > tbody > tr.admin-table--edit-row:hover,
@ -141,9 +158,15 @@ pre.admin-table--query {
.db-manager {
margin-bottom: 8px;
&:last-child {margin-bottom: 0;}
.db-manager-header--actions {visibility: hidden;}
&:hover .db-manager-header--actions {visibility: visible;}
&:last-child {
margin-bottom: 0;
}
.db-manager-header--actions {
visibility: hidden;
}
&:hover .db-manager-header--actions {
visibility: visible;
}
}
.db-manager-header {
padding: 0 11px;
@ -182,7 +205,9 @@ pre.admin-table--query {
padding: 9px 11px;
border-radius: 0 0 $radius-small $radius-small;
.table-highlight > tbody > tr:hover {background-color: $g5-pepper;}
.table-highlight > tbody > tr:hover {
background-color: $g5-pepper;
}
}
/*

View File

@ -0,0 +1,14 @@
export interface UserRole {
name: string
}
interface UserPermission {
name: string
}
export interface User {
name: string
roles: UserRole[]
permissions: UserPermission[]
password: string
}