Resolve conflicts to merge 'feature/934-ew-admin' into 'feature/934-ew-admin_add-user'
parent
05da325d7c
commit
2081ab96bc
|
@ -0,0 +1,95 @@
|
|||
import reducer from 'src/admin/reducers/admin'
|
||||
|
||||
import {
|
||||
loadRoles,
|
||||
deleteRole,
|
||||
deleteUser,
|
||||
filterRoles,
|
||||
filterUsers,
|
||||
} from 'src/admin/actions'
|
||||
|
||||
let state = undefined
|
||||
const r1 = {name: 'role1'}
|
||||
const r2 = {name: 'role2'}
|
||||
const roles = [r1, r2]
|
||||
|
||||
const u1 = {name: 'user1'}
|
||||
const u2 = {name: 'user2'}
|
||||
const users = [u1, u2]
|
||||
|
||||
describe('Admin.Reducers', () => {
|
||||
it('it can load the roles', () => {
|
||||
const actual = reducer(state, loadRoles({roles}))
|
||||
const expected = {
|
||||
roles,
|
||||
}
|
||||
|
||||
expect(actual.roles).to.deep.equal(expected.roles)
|
||||
})
|
||||
|
||||
it('it can delete a role', () => {
|
||||
state = {
|
||||
roles: [
|
||||
r1,
|
||||
]
|
||||
}
|
||||
|
||||
const actual = reducer(state, deleteRole(r1))
|
||||
const expected = {
|
||||
roles: [],
|
||||
}
|
||||
|
||||
expect(actual.roles).to.deep.equal(expected.roles)
|
||||
})
|
||||
|
||||
it('it can delete a user', () => {
|
||||
state = {
|
||||
users: [
|
||||
u1,
|
||||
]
|
||||
}
|
||||
|
||||
const actual = reducer(state, deleteUser(u1))
|
||||
const expected = {
|
||||
users: [],
|
||||
}
|
||||
|
||||
expect(actual.users).to.deep.equal(expected.users)
|
||||
})
|
||||
|
||||
it('can filter roles w/ "1" text', () => {
|
||||
state = {
|
||||
roles,
|
||||
}
|
||||
|
||||
const text = '1'
|
||||
|
||||
const actual = reducer(state, filterRoles(text))
|
||||
const expected = {
|
||||
roles: [
|
||||
{...r1, hidden: false},
|
||||
{...r2, hidden: true},
|
||||
],
|
||||
}
|
||||
|
||||
expect(actual.roles).to.deep.equal(expected.roles)
|
||||
})
|
||||
|
||||
it('can filter users w/ "2" text', () => {
|
||||
state = {
|
||||
users,
|
||||
}
|
||||
|
||||
const text = '2'
|
||||
|
||||
const actual = reducer(state, filterUsers(text))
|
||||
const expected = {
|
||||
users: [
|
||||
{...u1, hidden: true},
|
||||
{...u2, hidden: false},
|
||||
],
|
||||
}
|
||||
|
||||
expect(actual.users).to.deep.equal(expected.users)
|
||||
})
|
||||
})
|
|
@ -1,9 +1,12 @@
|
|||
import {
|
||||
getUsers as getUsersAPI,
|
||||
getRoles as getRolesAPI,
|
||||
createUser as createUserAPI,
|
||||
getUsers as getUsersAJAX,
|
||||
getRoles as getRolesAJAX,
|
||||
createUser as createUserAJAX,
|
||||
deleteRole as deleteRoleAJAX,
|
||||
deleteUser as deleteUserAJAX,
|
||||
} 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'
|
||||
|
@ -29,8 +32,8 @@ export const addUser = (user) => ({
|
|||
},
|
||||
})
|
||||
|
||||
export const errorAddUser = (user) => ({
|
||||
type: 'ERROR_ADD_USER',
|
||||
export const removeAddedUser = (user) => ({
|
||||
type: 'REMOVE_ADDED_USER',
|
||||
payload: {
|
||||
user,
|
||||
},
|
||||
|
@ -57,28 +60,56 @@ export const loadQueries = (queries) => ({
|
|||
},
|
||||
})
|
||||
|
||||
export const deleteRole = (role) => ({
|
||||
type: 'DELETE_ROLE',
|
||||
payload: {
|
||||
role,
|
||||
},
|
||||
})
|
||||
|
||||
export const deleteUser = (user) => ({
|
||||
type: 'DELETE_USER',
|
||||
payload: {
|
||||
user,
|
||||
},
|
||||
})
|
||||
|
||||
export const filterRoles = (text) => ({
|
||||
type: 'FILTER_ROLES',
|
||||
payload: {
|
||||
text,
|
||||
},
|
||||
})
|
||||
|
||||
export const filterUsers = (text) => ({
|
||||
type: 'FILTER_USERS',
|
||||
payload: {
|
||||
text,
|
||||
},
|
||||
})
|
||||
|
||||
// async actions
|
||||
export const loadUsersAsync = (url) => async (dispatch) => {
|
||||
const {data} = await getUsersAPI(url)
|
||||
const {data} = await getUsersAJAX(url)
|
||||
dispatch(loadUsers(data))
|
||||
}
|
||||
|
||||
export const loadRolesAsync = (url) => async (dispatch) => {
|
||||
const {data} = await getRolesAPI(url)
|
||||
const {data} = await getRolesAJAX(url)
|
||||
dispatch(loadRoles(data))
|
||||
}
|
||||
|
||||
export const addUserAsync = (url, user) => async (dispatch) => {
|
||||
// optimistically update
|
||||
// optimistic update
|
||||
dispatch(addUser(user))
|
||||
|
||||
try {
|
||||
await createUserAPI(url, user)
|
||||
await createUserAJAX(url, user)
|
||||
dispatch(publishNotification('success', 'User created successfully'))
|
||||
} catch (error) {
|
||||
// undo optimistic update
|
||||
dispatch(publishNotification('error', `Failed to create user: ${error.data.message}`))
|
||||
setTimeout(() => dispatch(errorAddUser(user)), ADMIN_NOTIFICATION_DELAY)
|
||||
setTimeout(() => dispatch(removeAddedUser(user)), ADMIN_NOTIFICATION_DELAY)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,3 +121,19 @@ export const killQueryAsync = (source, queryID) => (dispatch) => {
|
|||
// kill query on server
|
||||
killQueryProxy(source, queryID)
|
||||
}
|
||||
|
||||
export const deleteRoleAsync = (role, addFlashMessage) => (dispatch) => {
|
||||
// optimistic update
|
||||
dispatch(deleteRole(role))
|
||||
|
||||
// delete role on server
|
||||
deleteRoleAJAX(role.links.self, addFlashMessage, role.name)
|
||||
}
|
||||
|
||||
export const deleteUserAsync = (user, addFlashMessage) => (dispatch) => {
|
||||
// optimistic update
|
||||
dispatch(deleteUser(user))
|
||||
|
||||
// delete user on server
|
||||
deleteUserAJAX(user.links.self, addFlashMessage, user.name)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ export const getRoles = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export const createUser = async (url, user) => {
|
||||
try {
|
||||
return await AJAX({
|
||||
|
@ -32,4 +33,44 @@ export const createUser = async (url, user) => {
|
|||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteRole = async (url, addFlashMessage, rolename) => {
|
||||
try {
|
||||
const response = await AJAX({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
})
|
||||
addFlashMessage({
|
||||
type: 'success',
|
||||
text: `${rolename} successfully deleted.`,
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: `Error deleting: ${rolename}.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteUser = async (url, addFlashMessage, username) => {
|
||||
try {
|
||||
const response = await AJAX({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
})
|
||||
addFlashMessage({
|
||||
type: 'success',
|
||||
text: `${username} successfully deleted.`,
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line no-console
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: `Error deleting: ${username}.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,62 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import React, {PropTypes} from 'react'
|
||||
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'src/shared/components/Tabs';
|
||||
import UsersTable from 'src/admin/components/UsersTable'
|
||||
import RolesTable from 'src/admin/components/RolesTable'
|
||||
import QueriesPage from 'src/admin/containers/QueriesPage'
|
||||
|
||||
const TABS = ['Users', 'Roles', 'Queries'];
|
||||
const AdminTabs = ({
|
||||
users,
|
||||
roles,
|
||||
source,
|
||||
onAddUser,
|
||||
addFlashMessage,
|
||||
onDeleteRole,
|
||||
onDeleteUser,
|
||||
onFilterRoles,
|
||||
onFilterUsers,
|
||||
}) => {
|
||||
const hasRoles = !!source.links.roles
|
||||
|
||||
class AdminTabs extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
let tabs = [
|
||||
{
|
||||
type: 'Users',
|
||||
component: (<UsersTable
|
||||
users={users}
|
||||
hasRoles={hasRoles}
|
||||
onAdd={onAddUser}
|
||||
addFlashMessage={addFlashMessage}
|
||||
onDelete={onDeleteUser}
|
||||
onFilter={onFilterUsers}
|
||||
/>),
|
||||
},
|
||||
{
|
||||
type: 'Roles',
|
||||
component: (<RolesTable roles={roles} onDelete={onDeleteRole} onFilter={onFilterRoles} />),
|
||||
},
|
||||
{
|
||||
type: 'Queries',
|
||||
component: (<QueriesPage source={source} />),
|
||||
},
|
||||
]
|
||||
|
||||
this.state = {
|
||||
activeTab: TABS[0],
|
||||
}
|
||||
|
||||
this.handleActivateTab = ::this.handleActivateTab
|
||||
if (!hasRoles) {
|
||||
tabs = tabs.filter(t => t.type !== 'Roles')
|
||||
}
|
||||
|
||||
handleActivateTab(activeIndex) {
|
||||
this.setState({activeTab: TABS[activeIndex]})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {users, roles, source, addUser, addFlashMessage} = this.props
|
||||
|
||||
return (
|
||||
<Tabs onSelect={this.handleActivateTab}>
|
||||
<TabList>
|
||||
<Tab>{TABS[0]}</Tab>
|
||||
<Tab>{TABS[1]}</Tab>
|
||||
<Tab>{TABS[2]}</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<UsersTable
|
||||
source={source}
|
||||
users={users}
|
||||
addUser={addUser}
|
||||
addFlashMessage={addFlashMessage}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<RolesTable
|
||||
roles={roles}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<QueriesPage source={source} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Tabs>
|
||||
<TabList>
|
||||
{
|
||||
tabs.map((t, i) => (<Tab key={tabs[i].type}>{tabs[i].type}</Tab>))
|
||||
}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{
|
||||
tabs.map((t, i) => (<TabPanel key={tabs[i].type}>{t.component}</TabPanel>))
|
||||
}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -70,8 +75,12 @@ AdminTabs.propTypes = {
|
|||
})),
|
||||
source: shape(),
|
||||
roles: arrayOf(shape()),
|
||||
addUser: func.isRequired,
|
||||
onAddUser: func.isRequired,
|
||||
addFlashMessage: func.isRequired,
|
||||
onDeleteRole: func.isRequired,
|
||||
onDeleteUser: func.isRequired,
|
||||
onFilterRoles: func.isRequired,
|
||||
onFilterUsers: func.isRequired,
|
||||
}
|
||||
|
||||
export default AdminTabs
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import React, {PropTypes, Component} from 'react'
|
||||
import OnClickOutside from 'shared/components/OnClickOutside'
|
||||
|
||||
const DeleteButton = ({onConfirm}) => (
|
||||
<button
|
||||
className="btn btn-xs btn-danger admin-table--delete"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
)
|
||||
|
||||
const ConfirmButtons = ({onDelete, item, onCancel}) => (
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-xs btn-primary"
|
||||
onClick={() => onDelete(item)}
|
||||
>
|
||||
<span className="icon checkmark"></span>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-xs btn-default"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<span className="icon remove"></span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
class DeleteRow extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isConfirmed: false,
|
||||
}
|
||||
this.handleConfirm = ::this.handleConfirm
|
||||
this.handleCancel = ::this.handleCancel
|
||||
}
|
||||
|
||||
handleConfirm() {
|
||||
this.setState({isConfirmed: true})
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
this.setState({isConfirmed: false})
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.setState({isConfirmed: false})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {onDelete, item} = this.props
|
||||
const {isConfirmed} = this.state
|
||||
|
||||
if (isConfirmed) {
|
||||
return (
|
||||
<ConfirmButtons
|
||||
onDelete={onDelete}
|
||||
item={item}
|
||||
onCancel={this.handleCancel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DeleteButton onConfirm={this.handleConfirm} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
} = PropTypes
|
||||
|
||||
DeleteButton.propTypes = {
|
||||
onConfirm: func.isRequired,
|
||||
}
|
||||
|
||||
ConfirmButtons.propTypes = {
|
||||
onDelete: func.isRequired,
|
||||
item: shape({}).isRequired,
|
||||
onCancel: func.isRequired,
|
||||
}
|
||||
|
||||
DeleteRow.propTypes = {
|
||||
item: shape({}),
|
||||
onDelete: func.isRequired,
|
||||
}
|
||||
|
||||
export default OnClickOutside(DeleteRow)
|
|
@ -0,0 +1,57 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
|
||||
class FilterBar extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
filterText: '',
|
||||
}
|
||||
|
||||
this.handleText = ::this.handleText
|
||||
}
|
||||
|
||||
handleText(e) {
|
||||
this.setState(
|
||||
{filterText: e.target.value},
|
||||
this.props.onFilter(e.target.value)
|
||||
)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onFilter('')
|
||||
}
|
||||
|
||||
render() {
|
||||
const {name, onClickCreate} = this.props
|
||||
return (
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<div className="users__search-widget input-group admin__search-widget">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={`Filter ${name}...`}
|
||||
value={this.state.filterText}
|
||||
onChange={this.handleText}
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" className="btn btn-primary" onClick={onClickCreate}>Create {name}</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
||||
FilterBar.propTypes = {
|
||||
onFilter: func.isRequired,
|
||||
name: string,
|
||||
onClickCreate: func,
|
||||
}
|
||||
|
||||
export default FilterBar
|
|
@ -2,6 +2,7 @@ import React, {PropTypes} from 'react'
|
|||
import _ from 'lodash'
|
||||
|
||||
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
|
||||
import DeleteRow from 'src/admin/components/DeleteRow'
|
||||
|
||||
const PERMISSIONS = [
|
||||
"NoPermissions",
|
||||
|
@ -25,7 +26,7 @@ const PERMISSIONS = [
|
|||
"KapacitorConfigAPI",
|
||||
]
|
||||
|
||||
const RoleRow = ({role: {name, permissions, users}}) => (
|
||||
const RoleRow = ({role: {name, permissions, users}, role, onDelete}) => (
|
||||
<tr>
|
||||
<td>{name}</td>
|
||||
<td>
|
||||
|
@ -43,21 +44,22 @@ const RoleRow = ({role: {name, permissions, users}}) => (
|
|||
{
|
||||
users && users.length ?
|
||||
<MultiSelectDropdown
|
||||
items={users.map((role) => role.name)}
|
||||
items={users.map((r) => r.name)}
|
||||
selectedItems={[]}
|
||||
label={'Select Users'}
|
||||
onApply={() => '//TODO'}
|
||||
/> : '\u2014'
|
||||
}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<button className="btn btn-xs btn-danger admin-table--delete">Delete</button>
|
||||
<td className="text-right" style={{width: "85px"}}>
|
||||
<DeleteRow onDelete={onDelete} item={role} />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
@ -72,6 +74,7 @@ RoleRow.propTypes = {
|
|||
name: string,
|
||||
})),
|
||||
}).isRequired,
|
||||
onDelete: func.isRequired,
|
||||
}
|
||||
|
||||
export default RoleRow
|
||||
|
|
|
@ -1,22 +1,11 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import RoleRow from 'src/admin/components/RoleRow'
|
||||
import EmptyRow from 'src/admin/components/EmptyRow'
|
||||
import FilterBar from 'src/admin/components/FilterBar'
|
||||
|
||||
const RolesTable = ({roles}) => (
|
||||
const RolesTable = ({roles, onDelete, onFilter}) => (
|
||||
<div className="panel panel-info">
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<div className="users__search-widget input-group admin__search-widget">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Filter Role..."
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" className="btn btn-primary">Create Role</a>
|
||||
</div>
|
||||
<FilterBar name="Roles" onFilter={onFilter} />
|
||||
<div className="panel-body">
|
||||
<table className="table v-center admin-table">
|
||||
<thead>
|
||||
|
@ -30,8 +19,8 @@ const RolesTable = ({roles}) => (
|
|||
<tbody>
|
||||
{
|
||||
roles.length ?
|
||||
roles.map((role) =>
|
||||
<RoleRow key={role.name} role={role} />
|
||||
roles.filter(r => !r.hidden).map((role) =>
|
||||
<RoleRow key={role.name} role={role} onDelete={onDelete} />
|
||||
) : <EmptyRow tableName={'Roles'} />
|
||||
}
|
||||
</tbody>
|
||||
|
@ -42,6 +31,7 @@ const RolesTable = ({roles}) => (
|
|||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
@ -57,6 +47,8 @@ RolesTable.propTypes = {
|
|||
name: string,
|
||||
})),
|
||||
})),
|
||||
onDelete: func.isRequired,
|
||||
onFilter: func,
|
||||
}
|
||||
|
||||
export default RolesTable
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
|
||||
import DeleteRow from 'src/admin/components/DeleteRow'
|
||||
|
||||
const UserRow = ({
|
||||
user: {name, roles, permissions},
|
||||
user,
|
||||
isEditing,
|
||||
onCancel,
|
||||
onSave,
|
||||
onInputChange,
|
||||
onInputKeyPress,
|
||||
onDelete,
|
||||
}) => (
|
||||
<tr>
|
||||
{
|
||||
|
@ -57,12 +59,14 @@ const UserRow = ({
|
|||
{
|
||||
isEditing ?
|
||||
<div>
|
||||
<button onClick={onCancel}>Cancel</button>
|
||||
<button onClick={onSave}>Save</button>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</td>
|
||||
<td className="text-right" style={{width: "85px"}}>
|
||||
<DeleteRow onDelete={onDelete} item={user} />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
|
@ -85,10 +89,10 @@ UserRow.propTypes = {
|
|||
})),
|
||||
}).isRequired,
|
||||
isEditing: bool,
|
||||
onCancel: func,
|
||||
onSave: func,
|
||||
onInputChange: func,
|
||||
onInputKeyPress: func,
|
||||
onDelete: func.isRequired,
|
||||
}
|
||||
|
||||
export default UserRow
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, {Component, PropTypes} from 'react'
|
|||
|
||||
import UserRow from 'src/admin/components/UserRow'
|
||||
import EmptyRow from 'src/admin/components/EmptyRow'
|
||||
import FilterBar from 'src/admin/components/FilterBar'
|
||||
|
||||
const newDefaultUser = {
|
||||
name: '',
|
||||
|
@ -24,17 +25,22 @@ class UsersTable extends Component {
|
|||
newUser: {...newDefaultUser},
|
||||
}
|
||||
|
||||
this.handleClickCreate = ::this.handleClickCreate
|
||||
this.handleClearNewUser = ::this.handleClearNewUser
|
||||
this.handleSubmitNewUser = ::this.handleSubmitNewUser
|
||||
this.handleInputChange = ::this.handleInputChange
|
||||
this.handleInputKeyPress = ::this.handleInputKeyPress
|
||||
}
|
||||
|
||||
handleClickCreate() {
|
||||
this.setState({isAddingUser: true})
|
||||
}
|
||||
|
||||
handleSubmitNewUser() {
|
||||
const {source, addUser, addFlashMessage} = this.props
|
||||
const {onAdd, addFlashMessage} = this.props
|
||||
const {newUser} = this.state
|
||||
if (isValid(newUser)) {
|
||||
addUser(source.links.users, newUser)
|
||||
onAdd(newUser)
|
||||
this.handleClearNewUser()
|
||||
} else {
|
||||
addFlashMessage({type: 'error', text: 'Username and/or password too short'})
|
||||
|
@ -61,34 +67,20 @@ class UsersTable extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {users} = this.props
|
||||
const {users, hasRoles, onDelete, onFilter} = this.props
|
||||
const {isAddingUser} = this.state
|
||||
|
||||
return (
|
||||
<div className="panel panel-info">
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<div className="users__search-widget input-group admin__search-widget">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Filter Role..."
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => this.setState({isAddingUser: true})}
|
||||
>Create User</button>
|
||||
</div>
|
||||
<FilterBar name="Users" onFilter={onFilter} onClickCreate={this.handleClickCreate} />
|
||||
<div className="panel-body">
|
||||
<table className="table v-center">
|
||||
<table className="table v-center admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Roles</th>
|
||||
{hasRoles && <th>Roles</th>}
|
||||
<th>Permissions</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -97,7 +89,7 @@ class UsersTable extends Component {
|
|||
<UserRow
|
||||
user={this.state.newUser}
|
||||
isEditing={true}
|
||||
onCancel={this.handleClearNewUser}
|
||||
onDelete={this.handleClearNewUser}
|
||||
onSave={this.handleSubmitNewUser}
|
||||
onInputChange={this.handleInputChange}
|
||||
onInputKeyPress={this.handleInputKeyPress}
|
||||
|
@ -106,11 +98,8 @@ class UsersTable extends Component {
|
|||
}
|
||||
{
|
||||
users.length ?
|
||||
users.map((user, i) =>
|
||||
<UserRow
|
||||
key={i}
|
||||
user={user}
|
||||
/>
|
||||
users.filter(u => !u.hidden).map((user, i) =>
|
||||
<UserRow key={i} user={user} onDelete={onDelete} />
|
||||
) : <EmptyRow tableName={'Users'} />
|
||||
}
|
||||
</tbody>
|
||||
|
@ -123,6 +112,7 @@ class UsersTable extends Component {
|
|||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
|
@ -139,9 +129,11 @@ UsersTable.propTypes = {
|
|||
scope: string.isRequired,
|
||||
})),
|
||||
})),
|
||||
source: shape(),
|
||||
addUser: func.isRequired,
|
||||
onAdd: func.isRequired,
|
||||
addFlashMessage: func.isRequired,
|
||||
hasRoles: bool.isRequired,
|
||||
onDelete: func.isRequired,
|
||||
onFilter: func,
|
||||
}
|
||||
|
||||
export default UsersTable
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {loadUsersAsync, loadRolesAsync, addUserAsync} from 'src/admin/actions'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import {
|
||||
loadUsersAsync,
|
||||
loadRolesAsync,
|
||||
addUserAsync,
|
||||
deleteRoleAsync,
|
||||
deleteUserAsync,
|
||||
filterRoles as filterRolesAction,
|
||||
filterUsers as filterUsersAction,
|
||||
} from 'src/admin/actions'
|
||||
import AdminTabs from 'src/admin/components/AdminTabs'
|
||||
|
||||
class AdminPage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.handleAddUser = ::this.handleAddUser
|
||||
this.handleDeleteRole = ::this.handleDeleteRole
|
||||
this.handleDeleteUser = ::this.handleDeleteUser
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -18,8 +29,20 @@ class AdminPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleAddUser(user) {
|
||||
this.props.addUser(this.props.source.links.users, user, this.props.addFlashMessage)
|
||||
}
|
||||
|
||||
handleDeleteRole(role) {
|
||||
this.props.deleteRole(role, this.props.addFlashMessage)
|
||||
}
|
||||
|
||||
handleDeleteUser(user) {
|
||||
this.props.deleteUser(user, this.props.addFlashMessage)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {users, roles, source, addUser, addFlashMessage} = this.props
|
||||
const {users, roles, source, filterUsers, filterRoles, addFlashMessage} = this.props
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
|
@ -36,15 +59,21 @@ class AdminPage extends Component {
|
|||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
{users.length ?
|
||||
{
|
||||
users.length ?
|
||||
<AdminTabs
|
||||
users={users}
|
||||
roles={roles}
|
||||
source={source}
|
||||
addUser={addUser}
|
||||
onAddUser={this.handleAddUser}
|
||||
onDeleteRole={this.handleDeleteRole}
|
||||
onDeleteUser={this.handleDeleteUser}
|
||||
onFilterUsers={filterUsers}
|
||||
onFilterRoles={filterRoles}
|
||||
addFlashMessage={addFlashMessage}
|
||||
/>
|
||||
: <span>Loading...</span>}
|
||||
/> :
|
||||
<span>Loading...</span>}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,7 +102,11 @@ AdminPage.propTypes = {
|
|||
loadUsers: func,
|
||||
loadRoles: func,
|
||||
addUser: func,
|
||||
addFlashMessage: func.isRequired,
|
||||
deleteRole: func,
|
||||
deleteUser: func,
|
||||
addFlashMessage: func,
|
||||
filterRoles: func,
|
||||
filterUsers: func,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({admin}) => ({
|
||||
|
@ -85,6 +118,10 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
loadUsers: bindActionCreators(loadUsersAsync, dispatch),
|
||||
loadRoles: bindActionCreators(loadRolesAsync, dispatch),
|
||||
addUser: bindActionCreators(addUserAsync, dispatch),
|
||||
deleteRole: bindActionCreators(deleteRoleAsync, dispatch),
|
||||
deleteUser: bindActionCreators(deleteUserAsync, dispatch),
|
||||
filterRoles: bindActionCreators(filterRolesAction, dispatch),
|
||||
filterUsers: bindActionCreators(filterUsersAction, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AdminPage);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AdminPage)
|
||||
|
|
|
@ -27,7 +27,7 @@ export default function admin(state = initialState, action) {
|
|||
return newState
|
||||
}
|
||||
|
||||
case 'ERROR_ADD_USER': {
|
||||
case 'REMOVE_ADDED_USER': {
|
||||
const newUsers = [...state.users]
|
||||
|
||||
// find and remove first user in list with name
|
||||
|
@ -40,10 +40,52 @@ export default function admin(state = initialState, action) {
|
|||
return newState
|
||||
}
|
||||
|
||||
case 'DELETE_ROLE': {
|
||||
const {role} = action.payload
|
||||
const newState = {
|
||||
roles: state.roles.filter(r => r.name !== role.name),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
||||
case 'DELETE_USER': {
|
||||
const {user} = action.payload
|
||||
const newState = {
|
||||
users: state.users.filter(u => u.name !== user.name),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
||||
case 'LOAD_QUERIES': {
|
||||
return {...state, ...action.payload}
|
||||
}
|
||||
|
||||
case 'FILTER_ROLES': {
|
||||
const {text} = action.payload
|
||||
const newState = {
|
||||
roles: state.roles.map(r => {
|
||||
r.hidden = !r.name.toLowerCase().includes(text)
|
||||
return r
|
||||
}),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
||||
case 'FILTER_USERS': {
|
||||
const {text} = action.payload
|
||||
const newState = {
|
||||
users: state.users.map(u => {
|
||||
u.hidden = !u.name.toLowerCase().includes(text)
|
||||
return u
|
||||
}),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
||||
case 'KILL_QUERY': {
|
||||
const {queryID} = action.payload
|
||||
const nextState = {
|
||||
|
|
Loading…
Reference in New Issue