parent
26504005ca
commit
5b4c1ca568
|
@ -14,7 +14,6 @@ import DataExplorer from 'src/chronograf';
|
|||
import DatabaseManager from 'src/database_manager';
|
||||
import SignUp from 'src/sign_up';
|
||||
import SelectSourcePage from 'src/select_source';
|
||||
import {UsersPage, UserEditPage} from 'src/web_users';
|
||||
import {ClusterAccountsPage, ClusterAccountPage} from 'src/cluster_accounts';
|
||||
import {RolesPageContainer, RolePageContainer} from 'src/access_control';
|
||||
import NotFound from 'src/shared/components/NotFound';
|
||||
|
@ -136,8 +135,6 @@ const Root = React.createClass({
|
|||
<Route path="hosts" component={HostsPage} />
|
||||
<Route path="hosts/:hostID" component={HostPage} />
|
||||
</Route>
|
||||
<Route path="users" component={UsersPage} />
|
||||
<Route path="users/:userID" component={UserEditPage} />
|
||||
<Route path="tasks" component={TasksPage} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Route>
|
||||
|
|
|
@ -17,10 +17,6 @@ const SideNav = React.createClass({
|
|||
<div className="sidebar__logo">
|
||||
<span className="icon cubo-uniform"></span>
|
||||
</div>
|
||||
<NavBlock matcher={'users'} icon={"access-key"} link={`${sourcePrefix}/users`}>
|
||||
<NavHeader link={`${sourcePrefix}/users`} title="Web Admin" />
|
||||
<NavListItem matcher={'users'} link={`${sourcePrefix}/users`}>Users</NavListItem>
|
||||
</NavBlock>
|
||||
<NavBlock matcher="overview" icon="crown" link={`${sourcePrefix}/overview`}>
|
||||
<NavHeader link={`${sourcePrefix}/overview`} title="Sources" />
|
||||
<NavListItem matcher="overview" link={`${sourcePrefix}/overview`}>Overview</NavListItem>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const {string} = PropTypes;
|
||||
const Header = React.createClass({
|
||||
propTypes: {
|
||||
activeCluster: string.isRequired,
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="user-index-page" className="js-user-index">
|
||||
<div className="enterprise-header">
|
||||
<div className="enterprise-header__container">
|
||||
<div className="enterprise-header__left">
|
||||
<h1>
|
||||
Web Users
|
||||
</h1>
|
||||
</div>
|
||||
<div className="enterprise-header__right">
|
||||
<button className="btn btn-sm btn-primary" data-toggle="modal" data-target="#createUserModal">Invite User</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default Header;
|
|
@ -1,75 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import AddClusterAccounts from 'src/shared/components/AddClusterAccounts';
|
||||
import {getClusters} from 'shared/apis';
|
||||
|
||||
const {func, shape, string} = PropTypes;
|
||||
const AddClusterLinksModal = React.createClass({
|
||||
propTypes: {
|
||||
user: shape({name: string.isRequired}).isRequired,
|
||||
onAddClusterAccount: func.isRequired,
|
||||
onCreateClusterLinks: func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
clusters: [],
|
||||
clusterLinks: {},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
getClusters().then(({data: clusters}) => {
|
||||
this.setState({clusters});
|
||||
});
|
||||
},
|
||||
|
||||
handleSelectClusterAccount({clusterID, accountName}) {
|
||||
const clusterLinks = Object.assign({}, this.state.clusterLinks, {
|
||||
[clusterID]: accountName,
|
||||
});
|
||||
this.setState({clusterLinks});
|
||||
this.props.onAddClusterAccount(clusterLinks);
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
$('#addClusterAccountModal').modal('hide'); // eslint-disable-line no-undef
|
||||
this.props.onCreateClusterLinks();
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="modal fade in" id="addClusterAccountModal" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Add Cluster Accounts to <strong>{this.props.user.name}</strong></h4>
|
||||
</div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="alert alert-info">
|
||||
<span className="icon star"></span>
|
||||
<p><strong>NOTE:</strong> Pairing a Web User with a Cluster Account causes the Web User to inherit all the Permissions and Roles associated with the Cluster Account.</p>
|
||||
</div>
|
||||
<AddClusterAccounts
|
||||
clusters={this.state.clusters}
|
||||
onSelectClusterAccount={this.handleSelectClusterAccount}
|
||||
headerText={''}
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<input disabled={!Object.keys(this.state.clusterLinks).length} className="btn btn-success" type="submit" value="Confirm" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default AddClusterLinksModal;
|
|
@ -1,39 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const {func, string, number, shape} = PropTypes;
|
||||
const DeleteUsersModal = React.createClass({
|
||||
propTypes: {
|
||||
handleConfirmDeleteUser: func.isRequired,
|
||||
userToDelete: shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
onConfirmDeleteUser() {
|
||||
this.props.handleConfirmDeleteUser(this.props.userToDelete.id);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="modal fade" id="deleteUsersModal" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">{`Are you sure you want to delete ${this.props.userToDelete.name}?`}</h4>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-default" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button className="btn btn-danger js-delete-users" onClick={this.onConfirmDeleteUser} type="button" data-dismiss="modal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default DeleteUsersModal;
|
|
@ -1,43 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
|
||||
const {func, string, number, shape} = PropTypes;
|
||||
const DissociateUserClusterLink = React.createClass({
|
||||
propTypes: {
|
||||
handleDissociate: func.isRequired,
|
||||
clusterLink: shape({
|
||||
id: number,
|
||||
name: string,
|
||||
cluster_user: string,
|
||||
}).isRequired,
|
||||
user: shape({
|
||||
name: string,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
onConfirmDissociate() {
|
||||
this.props.handleDissociate(this.props.clusterLink);
|
||||
},
|
||||
|
||||
render() {
|
||||
const {clusterLink, user} = this.props;
|
||||
return (
|
||||
<div className="modal fade" id="dissociateLink" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
Are you sure you want to unlink <strong>{clusterLink.cluster_user}</strong> from <strong>{user.name}</strong>?
|
||||
</h4>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-default" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button className="btn btn-danger js-delete-users" onClick={this.onConfirmDissociate} type="button" data-dismiss="modal">Dissociate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default DissociateUserClusterLink;
|
|
@ -1,111 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import {getClusters} from 'shared/apis';
|
||||
import FlashMessages from 'shared/components/FlashMessages';
|
||||
import ClusterAccounts from 'shared/components/AddClusterAccounts';
|
||||
import $ from 'jquery';
|
||||
|
||||
const {string, func} = PropTypes;
|
||||
const InviteUserModal = React.createClass({
|
||||
propTypes: {
|
||||
clusterID: string.isRequired,
|
||||
addFlashMessage: func.isRequired,
|
||||
onCreateWebUser: func.isRequired,
|
||||
onSelectClusterAccount: func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
errors: null,
|
||||
clusters: [],
|
||||
clusterLinks: {},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
getClusters().then(({data}) => {
|
||||
this.setState({clusters: data});
|
||||
});
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const firstName = this.firstName.value;
|
||||
const lastName = this.lastName.value;
|
||||
const email = this.email.value;
|
||||
const password = this.password.value;
|
||||
|
||||
this.props.onCreateWebUser({clusterID: this.props.clusterID, firstName, lastName, email, password});
|
||||
this.cleanup();
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="modal fade" id="createUserModal" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">Invite User</h4>
|
||||
</div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="form-grid">
|
||||
<div className="form-group col-md-12">
|
||||
<div className="alert alert-info">
|
||||
<span className="icon star"></span>
|
||||
Please enter first and last name followed by their email they will use to login
|
||||
</div>
|
||||
</div>
|
||||
{this.state.errors ?
|
||||
<div className="form-group col-md-12">
|
||||
<ul className="alert alert-danger">
|
||||
{this.state.errors.map((error, i) => <li key={i}>{error}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div className="form-group col-md-6">
|
||||
<label htmlFor="firstName">First Name</label>
|
||||
<input ref={(ref) => this.firstName = ref} type="text" className="form-control" id="firstName" placeholder="Marty" required={true} />
|
||||
</div>
|
||||
<div className="form-group col-md-6">
|
||||
<label htmlFor="lastName">Last Name</label>
|
||||
<input ref={(ref) => this.lastName = ref} type="text" className="form-control" id="lastName" placeholder="McFly" required={true} />
|
||||
</div>
|
||||
<div className="form-group col-md-12">
|
||||
<label htmlFor="userEmail">Email</label>
|
||||
<input ref={(ref) => this.email = ref} type="email" className="form-control" id="userEmail" placeholder="marty@thepinheadsrock.com" required={true} />
|
||||
</div>
|
||||
<div className="form-group col-md-12">
|
||||
<label htmlFor="userPassword">Password</label>
|
||||
<input ref={(ref) => this.password = ref} type="password" className="form-control" id="userPassword" placeholder="Optional Password"></input>
|
||||
</div>
|
||||
</div>
|
||||
<ClusterAccounts onSelectClusterAccount={this.props.onSelectClusterAccount} clusters={this.state.clusters}/>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-default" type="button" data-dismiss="modal">Cancel</button>
|
||||
<input className="btn btn-success" type="submit" value="Invite"></input>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
$('.modal').hide();
|
||||
$('.modal-backdrop').hide();
|
||||
|
||||
// reset form values
|
||||
this.firstName.value = '';
|
||||
this.lastName.value = '';
|
||||
this.email.value = '';
|
||||
this.password.value = '';
|
||||
},
|
||||
});
|
||||
|
||||
export default FlashMessages(InviteUserModal);
|
|
@ -1,250 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import FlashMessages from 'shared/components/FlashMessages';
|
||||
import DissociateModal from '../components/modals/DissociateUserClusterLink';
|
||||
import AddClusterLinksModal from '../components/modals/AddClusterLinksModal';
|
||||
import {
|
||||
showUser,
|
||||
updateUser,
|
||||
deleteUserClusterLink,
|
||||
batchCreateUserClusterLink,
|
||||
} from 'shared/apis';
|
||||
|
||||
const {func, string, shape} = PropTypes;
|
||||
const WebUserEdit = React.createClass({
|
||||
propTypes: {
|
||||
addFlashMessage: func.isRequired,
|
||||
params: shape({
|
||||
userID: string,
|
||||
clusterID: string,
|
||||
}),
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
linkToDissociate: {},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.getPageData();
|
||||
},
|
||||
|
||||
getPageData() {
|
||||
const {userID} = this.props.params;
|
||||
showUser(userID).then(({data}) => {
|
||||
this.setState({user: data});
|
||||
});
|
||||
},
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const email = this.email.value;
|
||||
const lastName = this.lastName.value;
|
||||
const firstName = this.firstName.value;
|
||||
const admin = this.admin.checked;
|
||||
const password = this.newPassword.value;
|
||||
const confirmation = this.newPasswordConfirmation.value;
|
||||
const newUser = Object.assign({}, this.state.user, {lastName, firstName, email, admin, password, confirmation});
|
||||
|
||||
updateUser(this.props.params.userID, newUser).then(() => {
|
||||
this.props.addFlashMessage({
|
||||
text: 'User updated successfully',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
this.newPassword.value = '';
|
||||
this.newPasswordConfirmation.value = '';
|
||||
}).catch((err) => {
|
||||
this.props.addFlashMessage({
|
||||
text: err.toString(),
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleAddClusterLinks(clusterLinks) {
|
||||
this.setState({clusterLinks});
|
||||
},
|
||||
|
||||
handleCreateClusterLinks() {
|
||||
const {user} = this.state;
|
||||
const clusterLinks = this.getClusterLinks();
|
||||
batchCreateUserClusterLink(
|
||||
user.id,
|
||||
clusterLinks,
|
||||
).then(() => {
|
||||
this.props.addFlashMessage({
|
||||
type: 'success',
|
||||
text: `Cluster account added to ${user.name}`,
|
||||
});
|
||||
this.getPageData();
|
||||
}).catch((err) => {
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'Something went wrong while trying to add cluster account',
|
||||
});
|
||||
console.error(err); // eslint-disable-line no-console
|
||||
});
|
||||
},
|
||||
|
||||
handleDissociation() {
|
||||
const {linkToDissociate, user} = this.state;
|
||||
const {params: {clusterID}} = this.props;
|
||||
deleteUserClusterLink(clusterID, linkToDissociate.id).then(() => {
|
||||
this.props.addFlashMessage({
|
||||
text: `${user.name} dissociated from ${linkToDissociate.cluster_user}`,
|
||||
type: 'success',
|
||||
});
|
||||
this.getPageData();
|
||||
}).catch(() => {
|
||||
this.props.addFlashMessage({
|
||||
text: `Something went wrong`,
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleLinkToDissociate(linkToDissociate) {
|
||||
this.setState({linkToDissociate});
|
||||
},
|
||||
|
||||
getClusterLinks() {
|
||||
return Object.keys(this.state.clusterLinks).map((clusterID) => {
|
||||
return {
|
||||
cluster_id: clusterID,
|
||||
cluster_user: this.state.clusterLinks[clusterID],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
const {user, linkToDissociate} = this.state;
|
||||
if (!user) {
|
||||
return null; // or something else, idk
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="enterprise-header">
|
||||
<div className="enterprise-header__container">
|
||||
<div className="enterprise-header__left">
|
||||
<h1>
|
||||
{user.name}
|
||||
<span className="label label-primary">Web User</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div className="enterprise-header__right">
|
||||
<button data-toggle="modal" data-target="#addClusterAccountModal" className="btn btn-sm btn-primary">Link Cluster Accounts</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading">
|
||||
<h2 className="panel-title">Account Details</h2>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className="form-group users-header__select-all">
|
||||
<input ref={(ref) => this.admin = ref} type="checkbox" id="users-header__select-all" defaultChecked={user.admin} />
|
||||
<label htmlFor="users-header__select-all" id="select-all-subtext">Admin</label>
|
||||
</div>
|
||||
<div className="form-group col-sm-6">
|
||||
<label htmlFor="account-first-name">First Name</label>
|
||||
<input ref={(ref) => this.firstName = ref} id="account-first-name" type="text" className="form-control" defaultValue={user.first_name} />
|
||||
</div>
|
||||
<div className="form-group col-sm-6">
|
||||
<label htmlFor="account-last-name">Last Name</label>
|
||||
<input ref={(ref) => this.lastName = ref} id="account-last-name" type="text" className="form-control" defaultValue={user.last_name} />
|
||||
</div>
|
||||
<div className="form-group col-sm-12">
|
||||
<label htmlFor="account-email">Email Address</label>
|
||||
<input ref={(ref) => this.email = ref} id="account-email" type="text" className="form-control" defaultValue={user.email} placeholder="Enter an email address" name="email" />
|
||||
</div>
|
||||
<div className="form-group col-sm-6">
|
||||
<label htmlFor="account-new-password">New Password</label>
|
||||
<input ref={(ref) => this.newPassword = ref} id="account-new-password" type="password" className="form-control" name="password" />
|
||||
</div>
|
||||
<div className="form-group col-sm-6">
|
||||
<label htmlFor="account-confirm-new-password">Confirm New Password</label>
|
||||
<input ref={(ref) => this.newPasswordConfirmation = ref} id="account-confirm-new-password" type="password" className="form-control" name="confirmation" />
|
||||
</div>
|
||||
<div className="form-group col-sm-12 u-flex u-jc-center">
|
||||
<input type="submit" className="btn btn-primary btn-sm" value="Update User" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-jc-space-between u-ai-center">
|
||||
<h2 className="panel-title">Linked Cluster Accounts</h2>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
{this.renderClusterLinks()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AddClusterLinksModal user={user} onCreateClusterLinks={this.handleCreateClusterLinks} onAddClusterAccount={this.handleAddClusterLinks} />
|
||||
<DissociateModal user={user} clusterLink={linkToDissociate} handleDissociate={this.handleDissociation} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderClusterLinks() {
|
||||
const {user: {cluster_links}} = this.state;
|
||||
if (!cluster_links.length) {
|
||||
return (
|
||||
<div className="generic-empty-state">
|
||||
<span className="icon user-outline"/>
|
||||
<h4>No Cluster Accounts linked to this user</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="table v-center users-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Account</th>
|
||||
<th>Cluster</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{
|
||||
cluster_links.map((link) => {
|
||||
return (
|
||||
<tr key={link.id}>
|
||||
<td></td>
|
||||
<td>{link.cluster_user}</td>
|
||||
<td>{link.cluster_id}</td>
|
||||
<td>
|
||||
<button
|
||||
onClick={() => this.handleLinkToDissociate(link)}
|
||||
type="button"
|
||||
className="btn btn-sm btn-link-danger"
|
||||
data-toggle="modal"
|
||||
data-target="#dissociateLink"
|
||||
>
|
||||
Unlink
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default FlashMessages(WebUserEdit);
|
|
@ -1,186 +0,0 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import _ from 'lodash';
|
||||
import PageHeader from '../components/PageHeader';
|
||||
import UsersTable from 'shared/components/UsersTable';
|
||||
import InviteUserModal from '../components/modals/InviteUser';
|
||||
import FlashMessages from 'shared/components/FlashMessages';
|
||||
import DeleteUserModal from '../components/modals/DeleteUsers';
|
||||
import {
|
||||
getWebUsers,
|
||||
deleteWebUsers,
|
||||
meShow,
|
||||
createWebUser,
|
||||
batchCreateUserClusterLink,
|
||||
} from 'shared/apis/index';
|
||||
|
||||
const {string, func} = PropTypes;
|
||||
const UsersPage = React.createClass({
|
||||
propTypes: {
|
||||
params: PropTypes.shape({
|
||||
clusterID: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
addFlashMessage: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
filterText: '',
|
||||
users: [],
|
||||
userToDelete: {
|
||||
id: 0,
|
||||
name: '',
|
||||
},
|
||||
me: {
|
||||
id: 0,
|
||||
},
|
||||
clusterLinks: {},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.refreshUsers();
|
||||
meShow().then(({data}) => {
|
||||
this.setState({me: data});
|
||||
});
|
||||
},
|
||||
|
||||
handleCreateWebUser(user) {
|
||||
const {addFlashMessage} = this.props;
|
||||
|
||||
createWebUser(user).then(({data}) => {
|
||||
batchCreateUserClusterLink(data.id, this.getClusterLinks()).then(() => {
|
||||
this.refreshUsers();
|
||||
addFlashMessage({
|
||||
type: 'success',
|
||||
text: 'User added successfully',
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.handleFormError(err, 'Problem adding user');
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.handleFormError(err, 'Problem adding cluster account');
|
||||
});
|
||||
},
|
||||
|
||||
handleSelectClusterAccount({clusterID, accountName}) {
|
||||
const clusterLinks = Object.assign({}, this.state.clusterLinks, {
|
||||
[clusterID]: accountName,
|
||||
});
|
||||
this.setState({clusterLinks});
|
||||
},
|
||||
|
||||
handleUserInput(filterText) {
|
||||
this.setState({filterText});
|
||||
},
|
||||
|
||||
handleUserToDelete(userToDelete) {
|
||||
this.setState({userToDelete});
|
||||
},
|
||||
|
||||
handleConfirmDeleteUser(userID) {
|
||||
deleteWebUsers(userID).then(() => {
|
||||
this.refreshUsers();
|
||||
this.props.addFlashMessage({
|
||||
type: 'success',
|
||||
text: 'User successfully deleted!',
|
||||
});
|
||||
}).catch(() => {
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was a problem deleting the user...',
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getClusterLinks() {
|
||||
return Object.keys(this.state.clusterLinks).map((clusterID) => {
|
||||
return {
|
||||
cluster_id: clusterID,
|
||||
cluster_user: this.state.clusterLinks[clusterID],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refreshUsers() {
|
||||
getWebUsers(this.props.params.clusterID).then(({data}) => {
|
||||
this.setState({users: data});
|
||||
});
|
||||
},
|
||||
|
||||
handleFormError(err, text) {
|
||||
const formErrors = _.get(err, 'response.data.errors', null);
|
||||
const messages = formErrors ? Object.keys(formErrors).map(k => formErrors[k]) : text;
|
||||
console.error(messages); // eslint-disable-line no-console
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: messages,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
const {users, filterText, me} = this.state;
|
||||
const {params: {clusterID}} = this.props;
|
||||
const filteredUsers = users.filter((user) => user.name.toLowerCase().indexOf(filterText.toLowerCase()) > -1);
|
||||
|
||||
return (
|
||||
<div id="user-index-page" className="js-user-index" data-cluster-id={clusterID}>
|
||||
<PageHeader activeCluster={clusterID} />
|
||||
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
|
||||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-jc-space-between u-ai-center">
|
||||
<h2 className="panel-title"></h2>
|
||||
<SearchBar filterText={this.state.filterText} onUserInput={this.handleUserInput} />
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
<UsersTable
|
||||
me={me}
|
||||
users={filteredUsers}
|
||||
activeCluster={clusterID}
|
||||
onUserToDelete={this.handleUserToDelete}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<InviteUserModal clusterID={clusterID} onSelectClusterAccount={this.handleSelectClusterAccount} onCreateWebUser={this.handleCreateWebUser} />
|
||||
<DeleteUserModal handleConfirmDeleteUser={this.handleConfirmDeleteUser} userToDelete={this.state.userToDelete}/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const SearchBar = React.createClass({
|
||||
propTypes: {
|
||||
onUserInput: func.isRequired,
|
||||
filterText: string.isRequired,
|
||||
},
|
||||
|
||||
handleChange() {
|
||||
this.props.onUserInput(this._filterText.value);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="users__search-widget input-group">
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Filter Web Users"
|
||||
value={this.props.filterText}
|
||||
ref={(ref) => this._filterText = ref}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default FlashMessages(UsersPage);
|
|
@ -1,3 +0,0 @@
|
|||
import UsersPage from './containers/UsersPage';
|
||||
import UserEditPage from './containers/UserEditPage';
|
||||
export {UsersPage, UserEditPage};
|
Loading…
Reference in New Issue