application/components/admin/user.js
- import React, {Component} from 'react';
- import Immutable from 'immutable';
- import {browserHistory} from 'react-router';
-
- import generatePassword from 'password-generator';
-
- import {List, ListItem} from 'material-ui/List';
- import Subheader from 'material-ui/Subheader';
- import Avatar from 'material-ui/Avatar';
- import FontIcon from 'material-ui/FontIcon';
- import Divider from 'material-ui/Divider';
- import Checkbox from 'material-ui/Checkbox';
- import LinearProgress from 'material-ui/LinearProgress';
-
- import PersonIcon from 'material-ui/svg-icons/social/person-outline';
- import ReloadIcon from 'material-ui/svg-icons/action/autorenew';
-
- import Dialog from 'material-ui/Dialog';
- import FlatButton from 'material-ui/FlatButton';
- import RaisedButton from 'material-ui/RaisedButton';
- import IconButton from 'material-ui/IconButton';
- import TextField from 'material-ui/TextField';
-
- import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
- import {Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle} from 'material-ui/Toolbar';
-
- import {connect} from 'react-redux';
- import ExpandingCard from './../expanding-card';
- import ReduxPaginator from './../common/redux-paginator';
- import DeleteButton from './../common/delete-button';
-
- import CurrentUser from './../../current-user';
-
- import {
- usersMaybeFetch,
- usersFetchOne,
- usersFetch,
- usersRemove,
- userSetSelected,
- userUpdate,
- userCreate
- } from './../../redux/actions/user-actions';
-
- import {
- aclsMaybeFetch
- } from './../../redux/actions/acl-actions';
-
- import {
- snackbarShowMessage
- } from './../../redux/actions/snackbar-actions';
- import {
- alertShowMessage
- } from './../../redux/actions/alert-actions';
-
- import {Row, Col} from './../flexbox';
- import request from './../../util/request';
- import {ENTER_KEY} from './../../util/key-codes';
-
- class UserCard extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- expanded: false,
- hasMore: false,
- hasLoaded: false,
- modalOpen: false,
- selected: []
- };
-
- this.loadScrollPage.bind(this);
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.openCard == 'user' && nextProps.openCard != this.props.openCard) {
- this.handleToggle(true);
- }
- }
-
- handleToggle(expanded) {
- if (this.props.onToggle) {
- this.props.onToggle('user', expanded);
- }
-
- if (expanded && !this.state.hasLoaded) {
- const {dispatch} = this.props;
- dispatch(usersMaybeFetch())
- .then(() => dispatch(aclsMaybeFetch()))
- .then(() => this.setState({hasLoaded: true}));
- }
- }
-
- loadScrollPage(page = 0) {
- const {dispatch} = this.props;
- dispatch(usersMaybeFetch({page: page}));
- }
-
- handleUserClick(user, e) {
- const {dispatch} = this.props;
-
- dispatch(usersFetchOne(user.get('uuid')))
- .then(() => dispatch(userSetSelected(user.get('uuid'))))
- .then(() => {
- this.setState({
- 'modalOpen': true
- });
- });
-
- }
-
- handleNewUser(e) {
- const {dispatch} = this.props;
- dispatch(userSetSelected(false));
- this.setState({'modalOpen': true});
- }
-
- handleModalClose(e) {
- const {dispatch} = this.props;
- dispatch(userSetSelected(false));
- this.setState({'modalOpen': false, 'selected': [], 'selectedItems': []});
- // fixes selection state in 0.15.6.1
- this.tableBody.setState({selectedRows: []});
- }
-
- handleRowSelection(selection) {
- let selectedItems = [];
-
- if (selection == 'all') {
- selectedItems = this.props.users.items.map((item) => item.get('uuid'));
- } else {
- const items = this.props.users.items.toJS();
- selectedItems = selection.map((i) =>items[i].uuid);
- }
-
- this.setState({
- 'selectedItems': selectedItems,
- 'selected': selection
- });
- }
-
- handlePagination(page) {
- if (this.props.users.pagination.current == page) {
- return;
- }
- const keyword = (this.props.location && this.props.location.query.keyword)
- ? decodeURIComponent(this.props.location.query.keyword)
- : null;
- const q = request.setQuery({
- 'page': page
- });
- const this_url = request.getPath() + '?' + q;
- browserHistory.push(this_url);
-
- let params = {'page': page};
- if (keyword) {
- params['keyword'] = keyword;
- params['includesnw'] = 1;
- }
-
- const {dispatch} = this.props;
- dispatch(usersFetch(params));
- this.setState({'selected': [], 'selectedItems': []});
- // fixes selection state in 0.15.6.1
- this.tableBody.setState({selectedRows: []});
- }
-
- handleKeyDown(e) {
- const val = e.target.value;
- if (e.keyCode !== ENTER_KEY) {
- return;
- }
-
- const {dispatch} = this.props;
-
- const q = request.setQuery({
- 'keyword': encodeURIComponent(val),
- 'page': 1
- });
- const this_url = request.getPath() + '?' + q;
- browserHistory.push(this_url);
-
- dispatch(usersFetch({page: 1, keyword: val, includesnw: 1}));
- }
-
- handleSearchReset() {
- const {dispatch} = this.props;
- const q = request.setQuery({
- 'keyword': '',
- 'page': 1
- });
- const this_url = request.getPath() + '?' + q;
- browserHistory.push(this_url);
- this.quickSearch.value = '';
-
- dispatch(usersFetch({page: 1}));
- }
-
- handleDelete(next) {
- const uuids = this.state.selectedItems.join(',');
- const {dispatch} = this.props;
-
- dispatch(usersRemove(uuids))
- .then(() => dispatch(usersFetch()))
- .then(() => { this.setState({'selected': [], 'selectedItems': []}) })
- .then(() => { this.tableBody.setState({selectedRows: []}) })
- .then(() => next());
-
- return true;
- }
-
- render() {
- let items = this.props.users.items.map((item, i) => {
- let snw = false;
- if (parseInt(item.get('is_snworks'))) {
- snw = true;
- }
-
- const groups = item.get('roles').map((role, i) => {
- return <span key={i}>{role.get('name')}{(i < item.get('roles').size-1) ? ', ' : ''}</span>
- })
-
- return (
- <TableRow
- key={i}
- selected={this.state.selected == 'all' || this.state.selected.indexOf(i) != -1 ? true : false}
- selectable={(!CurrentUser.isSnworks() && parseInt(item.get('is_snworks'))) ? false : true}
- >
- <TableRowColumn>
- <Row middle={['xs']}>
- <Col xs={1} start={['xs']}>
- {snw ? <img style={{'maxWidth': '16px'}} src="/assets/img/cube-flat.png" /> : ''}
- {parseInt(item.get('is_service')) ? <FontIcon tooltip='Service User' style={{'fontSize': '1em'}} className='fa fa-cog' /> : ''}
- </Col>
- <Col xs={11}>
- {
- (!CurrentUser.isSnworks() && parseInt(item.get('is_snworks')))
- ? (
- <span>
- {item.get('name')}
- <br />
- {item.get('email')}
- </span>
- )
- : (
- <a
- onClick={this.handleUserClick.bind(this, item)}
- >
- {item.get('name')}
- <br />
- {item.get('email')}
- </a>
-
- )
- }
- </Col>
- </Row>
- </TableRowColumn>
- <TableRowColumn>{groups}</TableRowColumn>
- </TableRow>
- );
- });
-
- return (
- <ExpandingCard
- onToggle={this.handleToggle.bind(this)}
- title="Users"
- subtitle="Manage user accounts"
- expanded={this.props.openCard === 'user'}
- >
-
- <Row middle='xs'>
- <Col xs={3} autoBox={false}>
- <div className="box toolbar-search-wrap">
- <FontIcon className='mui-icons'>search</FontIcon>
- <input
- ref={quickSearch => this.quickSearch = quickSearch}
- className="search"
- type="text"
- placeholder="Search Users"
- onKeyDown={this.handleKeyDown.bind(this)}
- defaultValue={this.props.location.query.keyword ? decodeURIComponent(this.props.location.query.keyword) : ''}
- />
- <FontIcon className='mui-icons clear-search' onClick={this.handleSearchReset.bind(this)}>close</FontIcon>
- </div>
- </Col>
- <Col xs={4}>
- <DeleteButton
- disabled={this.state.selectedItems && this.state.selectedItems.length ? false : true}
- onDelete={this.handleDelete.bind(this)}
- />
- <RaisedButton
- primary={true}
- label="Add User"
- onClick={this.handleNewUser.bind(this)}
- />
- </Col>
- <Col xs={5} end='xs'>
- <ReduxPaginator
- pagination={this.props.users.pagination}
- onPaginate={this.handlePagination.bind(this)}
- />
- </Col>
- </Row>
-
- <Table
- fixedHeader={true}
- selectable={true}
- multiSelectable={true}
- onRowSelection={this.handleRowSelection.bind(this)}
- >
- <TableHeader>
- <TableRow>
- <TableHeaderColumn>
- <Row>
- <Col xs={11} offsetXs={1}>
- Name
- </Col>
- </Row>
- </TableHeaderColumn>
- <TableHeaderColumn>Groups</TableHeaderColumn>
- </TableRow>
- </TableHeader>
- <TableBody
- ref={tableBody => this.tableBody = tableBody}
- deselectOnClickaway={false}
- >
- {items}
- </TableBody>
- </Table>
-
- <UserModal onClose={this.handleModalClose.bind(this)} open={this.state.modalOpen} />
- </ExpandingCard>
- );
- }
- }
-
- let mapStateToProps = (state) => {
- return {
- users: state.users,
- acls: state.acls
- };
- }
-
- export default connect(mapStateToProps)(UserCard);
-
- class UnwrappedUserModal extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- loaded: false,
- changedPassword: false,
- changedRoles: false,
- passwordStrength: 0,
- didUpdatePassword: false,
- email_updated_password: false,
- create_as_author: false,
- user: {
- 'name': false,
- 'email': false,
- 'phone': false,
- 'roles': [],
- 'is_snworks': 0
- }
- };
-
- }
-
- // componentWillUpdate(nextProps, nextState) {
- componentWillReceiveProps(nextProps) {
- // this.setState({'user': {
- // 'name': false,
- // 'email': false,
- // 'phone': false,
- // 'roles': [],
- // 'is_snworks': 0
- // }});
-
- if (!this.state.loaded || nextProps.users.selected != this.props.users.selected) {
- const user = nextProps.users.items.find((item) => {
- return item.get('uuid') == nextProps.users.selected;
- });
-
- if (user) {
- console.log('SET USER');
- this.setState({'user': user.toJS()});
- }
-
- this.setState({
- 'loaded': true,
- 'changedPassword': false,
- 'passwordStrength': 0,
- 'changedRoles': false,
- didUpdatePassword: false,
- email_updated_password: false,
- create_as_author: false
- })
- }
- }
-
- handleClose(e) {
- this.setState({'user': {
- 'name': false,
- 'email': false,
- 'phone': false,
- 'roles': [],
- 'is_snworks': 0,
- 'is_service': 0
- }});
- this.props.onClose(e);
- }
-
- handleSave(e) {
- let user = this.state.user;
-
- const {dispatch} = this.props;
-
- let data = {
- 'name': user.name,
- 'email': user.email,
- 'phone': user.phone,
- 'is_service': user.is_service
- };
-
- if (CurrentUser.isSnworks()) {
- data.is_snworks = user.is_snworks;
- }
-
-
- if (this.state.changedRoles) {
- data.roles = [];
-
- user.roles.map((item, i) => {
- data.roles.push(item.name);
- });
- }
-
- if (this.state.didUpdatePassword) {
- data['password'] = this.state.changedPassword;
- }
-
- if (this.state.didUpdatePassword && this.state.email_updated_password) {
- data['email_updated_password'] = 1;
- }
-
- if (!user.uuid && this.state.create_as_author) {
- data['create_as_author'] = 1;
- }
-
- if (user.uuid) {
- dispatch(userUpdate(user.uuid, data))
- .then(() => dispatch(usersFetch()))
- .then(() => dispatch(snackbarShowMessage('User updated')))
- .then(() => dispatch(userSetSelected(false)))
- .then(() => {
- this.handleClose(e);
- })
- .catch((e) => {
- e.response.json().then((json) => {
- if (json.message.indexOf('email is already registered') !== -1) {
- dispatch(alertShowMessage({
- 'title': 'Oops',
- 'message': 'A user with that email address already exists'
- }));
- } else {
- dispatch(alertShowMessage({
- 'title': 'Oops',
- 'message': json.message
- }));
- }
- });
- });
- } else {
- dispatch(userCreate(data))
- .then((resp) => {
- const u = resp.payload.user;
- if (u.get('is_service') && u.get('service_key')) {
- dispatch(alertShowMessage({
- 'title': 'New Service User',
- 'message': (
- <div>
- <p>
- Please copy this service user's access keys and store it in a safe place. <strong>This is the only time it will be shown.</strong>
- </p>
- <TextField
- floatingLabelText='Public Key'
- readOnly={true}
- value={u.get('public_key')}
- fullWidth={true}
- />
- <TextField
- floatingLabelText='Private Key'
- readOnly={true}
- value={u.get('service_key')}
- fullWidth={true}
- />
- </div>
- )
- }));
- }
- })
- .then(() => dispatch(usersFetch()))
- .then(() => dispatch(snackbarShowMessage('User created')))
- .then(() => dispatch(userSetSelected(false)))
- .then(() => {
- this.handleClose(e);
- })
- .catch((e) => {
- e.response.json().then((json) => {
- if (json.message.indexOf('email is already registered') !== -1) {
- dispatch(alertShowMessage({
- 'title': 'Oops',
- 'message': 'A user with that email address already exists'
- }));
- } else {
- dispatch(alertShowMessage({
- 'title': 'Oops',
- 'message': json.message
- }));
- }
- });
- });
- }
- }
-
- handleFieldUpdate(e, val) {
- const prop = e.target.name;
- let user = this.state.user;
-
- user[prop] = val;
-
- this.setState({'user': user});
- }
-
- handleSnworksUpdate(e, checked) {
- let user = this.state.user;
- user.is_snworks = checked ? 1 : 0;
-
- this.setState({'user': user});
- }
-
- handleServiceUpdate(e, checked) {
- let user = this.state.user;
- user.is_service = checked ? 1 : 0;
-
- this.setState({'user': user});
- }
-
- handleEmailPassUpdate(e, checked) {
- this.setState({'email_updated_password': checked ? true : false});
- }
-
- handleCreateAsAuthor(e, checked) {
- this.setState({'create_as_author': checked ? true : false});
- }
-
- handleRoleUpdate(e, checked) {
- let user = this.state.user;
- let role = e.target.name;
-
- if (checked) {
- user.roles.push({'name': role});
- } else {
- let roles = user.roles.map((r, i) => {
- if (r.name != role) {
- return r;
- }
-
- return false;
- });
-
- user.roles = roles;
- }
-
- this.setState({'user': user, 'changedRoles': true});
- }
-
- handlePasswordChange(e) {
- this.setState({
- 'changedPassword': e.target.value,
- 'didUpdatePassword': true,
- 'passwordStrength': zxcvbn(e.target.value).score + 1
- });
- }
-
- handleGeneratePassword() {
- const p = generatePassword(12, false, /[\w\d\?\-]/);
- this.setState({
- 'changedPassword': p,
- 'didUpdatePassword': true,
- 'passwordStrength': zxcvbn(p).score + 1,
- 'email_updated_password': true
- });
- }
-
-
- getStrengthColor() {
- let strengthColor = 'red';
- switch (this.state.passwordStrength) {
- case 0:
- case 1:
- strengthColor = 'red';
- break;
- case 2:
- strengthColor = 'orange';
- break;
- case 3:
- strengthColor = 'yellow';
- break;
- case 4:
- case 5:
- strengthColor = 'green';
- break;
-
- }
-
- return strengthColor;
- }
-
- render() {
- const actions = [
- <FlatButton
- label="Cancel"
- primary={false}
- onClick={this.handleClose.bind(this)}
- />,
- <FlatButton
- label="Save"
- primary={true}
- keyboardFocused={true}
- onClick={this.handleSave.bind(this)}
- />
- ];
-
- return (
- <Dialog
- title="Edit User"
- actions={actions}
- modal={false}
- open={this.props.open}
- onRequestClose={this.handleClose.bind(this)}
- autoScrollBodyContent={true}
- >
- <p className='small'>
- <strong>Do you need to a lot of users?</strong> Contact <a href="mailto:support@getsnworks.com">support@getsnworks.com</a> and we can help you out!
- </p>
- <TextField
- floatingLabelText="Name"
- value={this.state.user.name ? this.state.user.name : ''}
- name='name'
- fullWidth={true}
- onChange={this.handleFieldUpdate.bind(this)}
- />
-
- <TextField
- floatingLabelText="Email"
- value={this.state.user.email ? this.state.user.email : ''}
- disabled={this.state.user.is_service ? true : false}
- name='email'
- fullWidth={true}
- onChange={this.handleFieldUpdate.bind(this)}
- />
-
- <TextField
- floatingLabelText="Phone"
- value={this.state.user.phone ? this.state.user.phone : ''}
- disabled={this.state.user.is_service ? true : false}
- name='phone'
- fullWidth={true}
- onChange={this.handleFieldUpdate.bind(this)}
- className='clear-bottom'
- />
-
- {
- CurrentUser.isSnworks()
- ? (<Checkbox
- label="SNworks User"
- labelPosition="right"
- name='snw'
- disabled={this.state.user.is_service ? true : false}
- checked={parseInt(this.state.user.is_snworks) ? true : false}
- onCheck={this.handleSnworksUpdate.bind(this)}
- />)
- : ''
- }
- {
- !this.state.user.uuid
- ? (<Checkbox
- label="Also create author"
- labelPosition="right"
- name='create_as_author'
- disabled={this.state.user.is_service ? true : false}
- checked={this.state.create_as_author}
- onCheck={this.handleCreateAsAuthor.bind(this)}
- />)
- : ''
- }
- <Checkbox
- label="Service User"
- labelPosition="right"
- name='is_service'
- checked={parseInt(this.state.user.is_service) ? true : false}
- onCheck={this.handleServiceUpdate.bind(this)}
- />
-
- <h3>Roles</h3>
-
- {this.props.acls.items.map((item, i) => {
- let checked = false;
- this.state.user.roles.map((r, i) => {
- if (r.name == item.get('name')) {
- checked = true;
- }
- });
- return <Checkbox
- label={item.get('name')}
- labelPosition="right"
- name={item.get('name')}
- key={i}
- defaultChecked={checked}
- onCheck={this.handleRoleUpdate.bind(this)}
- />;
- })}
-
-
- <Row bottom={['xs']}>
- <Col xs={8}>
- <h3>Password</h3>
- <TextField
- floatingLabelText="Enter a new password"
- value={this.state.changedPassword ? this.state.changedPassword : ''}
- fullWidth={true}
- disabled={parseInt(this.state.user.is_service) ? true : false}
- onChange={this.handlePasswordChange.bind(this)}
- />
- <LinearProgress
- mode="determinate"
- value={this.state.passwordStrength}
- color={this.getStrengthColor()}
- max={5}
- style={{backgroundColor: 'white'}}
- />
- </Col>
- <Col xs={4}>
- <RaisedButton
- onClick={this.handleGeneratePassword.bind(this)}
- label='Generate a password'
- disabled={parseInt(this.state.user.is_service) ? true : false}
- secondary={true}
- />
- </Col>
- </Row>
- <Row top={['xs']}>
- <Col xs={12}>
- <Checkbox
- label='Send user new password notification'
- labelPosition="right"
- checked={this.state.email_updated_password}
- onCheck={this.handleEmailPassUpdate.bind(this)}
- disabled={!this.state.didUpdatePassword}
- />
- </Col>
- </Row>
- </Dialog>
- );
- }
- }
-
- const UserModal = connect(mapStateToProps)(UnwrappedUserModal);