diff --git a/server/sources.go b/server/sources.go index b6af66ce03..ddb7eac76b 100644 --- a/server/sources.go +++ b/server/sources.go @@ -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) diff --git a/ui/src/admin/components/UserPermissionsDropdown.tsx b/ui/src/admin/components/UserPermissionsDropdown.tsx new file mode 100644 index 0000000000..d3345341ef --- /dev/null +++ b/ui/src/admin/components/UserPermissionsDropdown.tsx @@ -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 { + public render() { + return ( + + ) + } + + 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 diff --git a/ui/src/admin/components/UserRoleDropdown.tsx b/ui/src/admin/components/UserRoleDropdown.tsx new file mode 100644 index 0000000000..e287934051 --- /dev/null +++ b/ui/src/admin/components/UserRoleDropdown.tsx @@ -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 { + public render() { + const {allRoles} = this.props + + return ( + + ) + } + + 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 diff --git a/ui/src/admin/components/UserRow.js b/ui/src/admin/components/UserRow.js deleted file mode 100644 index fa6a744cc3..0000000000 --- a/ui/src/admin/components/UserRow.js +++ /dev/null @@ -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 ( - - - - {hasRoles ? -- : null} - -- - - - - - ) - } - - return ( - - {name} - - - - {hasRoles ? ( - - ({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} - /> - - ) : null} - - {allPermissions && allPermissions.length ? ( - ({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} - - - - - - ) -} - -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 diff --git a/ui/src/admin/components/UserRow.tsx b/ui/src/admin/components/UserRow.tsx new file mode 100644 index 0000000000..a2e908c6c6 --- /dev/null +++ b/ui/src/admin/components/UserRow.tsx @@ -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 { + public render() { + const { + user, + allRoles, + allPermissions, + hasRoles, + isNew, + isEditing, + onEdit, + onSave, + onCancel, + onUpdatePermissions, + onUpdateRoles, + } = this.props + + if (isEditing) { + return ( + + ) + } + + return ( + + {user.name} + + + + {hasRoles && ( + + + + )} + + {this.hasPermissions && ( + + )} + + + + + + ) + } + + 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 diff --git a/ui/src/admin/components/UserRowEdit.tsx b/ui/src/admin/components/UserRowEdit.tsx new file mode 100644 index 0000000000..60d8516038 --- /dev/null +++ b/ui/src/admin/components/UserRowEdit.tsx @@ -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 = ({ + user, + onEdit, + onSave, + onCancel, + isNew, + hasRoles, +}) => ( + + + + {hasRoles ? -- : null} + -- + + + + +) + +export default UserRowEdit diff --git a/ui/src/style/pages/admin.scss b/ui/src/style/pages/admin.scss index ab3e7d56ab..1731246c4f 100644 --- a/ui/src/style/pages/admin.scss +++ b/ui/src/style/pages/admin.scss @@ -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; + } } /* diff --git a/ui/src/types/influxAdmin.ts b/ui/src/types/influxAdmin.ts new file mode 100644 index 0000000000..a9255be652 --- /dev/null +++ b/ui/src/types/influxAdmin.ts @@ -0,0 +1,14 @@ +export interface UserRole { + name: string +} + +interface UserPermission { + name: string +} + +export interface User { + name: string + roles: UserRole[] + permissions: UserPermission[] + password: string +}