Merge pull request #3112 from influxdata/bugfix/influxdb-delete-user-role
Bugfix: Delete UserRole and handle providing no roles or permissionspull/10616/head
commit
8137ed998f
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
export interface UserRole {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface UserPermission {
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface User {
|
||||
name: string
|
||||
roles: UserRole[]
|
||||
permissions: UserPermission[]
|
||||
password: string
|
||||
}
|
Loading…
Reference in New Issue