Home Reference Source

application/components/admin/group.js

import _ from 'lodash';
import React, {Component} from 'react';
import Immutable from 'immutable';

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 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 DescriptiveAcls from './../../data/descriptive-acls';

import {
    aclsMaybeFetch,
    aclsFetch,
    aclsFetchOne,
    aclSetSelected,
    aclsFetchResources,
    aclUpdate,
    aclsRemove
} from './../../redux/actions/acl-actions';

import {
    snackbarShowMessage
} from './../../redux/actions/snackbar-actions';

import {Row, Col} from './../flexbox';

class GroupCard extends Component {
    constructor(props) {
        super(props);

        this.state = {
            hasMore: false,
            hasLoaded: false,
            modalOpen: false,
            selected: []
        };
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.openCard == 'group' && nextProps.openCard != this.props.openCard) {
            this.handleToggle(true);
        }
    }

    handleToggle(expanded) {
        if (this.props.onToggle) {
            this.props.onToggle('group', expanded);
        }

        if (expanded && !this.state.hasLoaded) {
            const {dispatch} = this.props;
            dispatch(aclsMaybeFetch())
                .then(() => this.setState({hasLoaded: true}));
        }
    }

    handleAclClick(acl, e) {
        const {dispatch} = this.props;

        dispatch(aclsFetchOne(acl.get('name')))
            .then(() => dispatch(aclsFetchResources()))
            .then(() => dispatch(aclSetSelected(acl.get('name'))))
            .then(() => {
                this.setState({
                    'modalOpen': true
                });
            });

    }

    handleNewAcl(e) {
        const {dispatch} = this.props;
        dispatch(aclsFetchResources())
            .then(() => dispatch(aclSetSelected(false)))
            .then(() => this.setState({'modalOpen': true}));
    }

    handleModalClose(e) {
        this.setState({'modalOpen': false, 'selected': [], 'selectedItems': []});
        this.tableBody.setState({selectedRows: []});
    }

    handleRowSelection(selection) {
        let selectedItems = [];

        if (selection == 'all') {
            selectedItems = this.props.acls.items.map((item) => item.get('name'));
        } else {
            const items = this.props.acls.items.toJS();
            selectedItems = selection.map((i) =>items[i].name);
        }

        this.setState({
            'selectedItems': selectedItems,
            'selected': selection
        });
    }

    handlePagination(page) {
        if (this.props.acls.pagination.current == page) {
            return;
        }

        const {dispatch} = this.props;
        dispatch(aclsFetch({page: page}));
    }

    handleDelete(next) {
        const names = this.state.selectedItems.join(',');
        const {dispatch} = this.props;

        dispatch(aclsRemove(names))
            .then(() => dispatch(aclsFetch()))
            .then(() => this.tableBody.setState({selectedRows: []}))
            .then(() => next());

        return true;
    }

    render() {
        let items = this.props.acls.items.map((item, i) => {

            return (
                <TableRow
                    key={i}
                    selected={this.state.selected == 'all' || this.state.selected.indexOf(i) != -1 ? true : false}
                    selectable={item.get('name') != 'Administrator' ? true : false}
                    >
                    <TableRowColumn>{item.get('name') != 'Administrator' ? (<a onClick={this.handleAclClick.bind(this, item)}>{item.get('name')}</a>) : item.get('name')}</TableRowColumn>
                    <TableRowColumn></TableRowColumn>
                </TableRow>
            );
        });

        return (
            <ExpandingCard
                onToggle={this.handleToggle.bind(this)}
                title="Groups"
                subtitle="Manage group and ACL permissions"
                expanded={this.props.openCard === 'group'}
                >

                <Row middle='xs'>
                    <Col start='xs'>
                        <DeleteButton
                            disabled={this.state.selectedItems && this.state.selectedItems.length ? false : true}
                            onDelete={this.handleDelete.bind(this)}
                            />
                        <RaisedButton
                            primary={true}
                            label="Add Group"
                            onClick={this.handleNewAcl.bind(this)}
                            />
                    </Col>
                </Row>


                <Table
                    fixedHeader={true}
                    selectable={true}
                    multiSelectable={true}
                    onRowSelection={this.handleRowSelection.bind(this)}
                    >
                    <TableHeader>
                        <TableRow>
                            <TableHeaderColumn>Name</TableHeaderColumn>
                            <TableHeaderColumn></TableHeaderColumn>
                        </TableRow>
                    </TableHeader>
                    <TableBody
                        ref={tableBody => this.tableBody = tableBody}
                        deselectOnClickaway={false}
                        >
                        {items}
                    </TableBody>
                </Table>

                {
                    this.state.modalOpen
                    ? <AclModal onClose={this.handleModalClose.bind(this)} open={this.state.modalOpen} />
                    : ''
                }
            </ExpandingCard>
        );
    }
}

let mapStateToProps = (state) => {
    return {
        acls: state.acls
    };
}

export default connect(mapStateToProps)(GroupCard);

class UnwrappedAclModal extends Component {
    constructor(props) {
        super(props);

        this.state = {
            loaded: false,
            acl: Immutable.fromJS({
                'name': false,
                'description': false,
                'accesses': {}
            }),
            descriptiveAcls: Immutable.fromJS(DescriptiveAcls)
        };

    }

    componentWillMount() {
        const acl = this.props.acls.items.find((item) => {
            return item.get('name') == this.props.acls.selected;
        });

        if (acl) {
            this.setState({'acl': acl, 'loaded': true});
        } else {
            this.setState({'acl': Immutable.fromJS({
                'name': false,
                'description': false,
                'accesses': {},
            }), 'loaded': true});
        }
    }

    handleClose(e) {
        this.props.onClose(e);
    }

    handleSave(e) {
        let acl = this.state.acl;

        const {dispatch} = this.props;

        let data = {
            'name':           acl.get('name'),
            'description':    acl.get('description'),
            'accesses':       acl.get('accesses').toJS()
        };

        dispatch(aclUpdate(data))
            .then(() => dispatch(aclsFetch()))
            .then(() => dispatch(snackbarShowMessage('Group updated')))
            .then(() => {
                this.props.onClose(e);
            });
    }

    handleFieldUpdate(e, val) {
        const prop = e.target.name;
        let acl = this.state.acl;

        this.setState({'acl': acl.set(prop, val)});
    }

    handleCheck(e, checked) {

        let currentAcls = this.state.acl.get('accesses');
        let groupLabel = e.target.name.substr(0, e.target.name.indexOf(' :: '));
        let actionLabel = e.target.name.substr(e.target.name.indexOf(' :: ')+4);

        // first find the acls for the targeted action
        const allAcls = this.state.descriptiveAcls;
        const group = allAcls.find((item) => item.get('label') == groupLabel);
        const action = group.get('actions').find((item) => item.get('label') == actionLabel);

        // if it's empty, set the default list
        if (!currentAcls || !currentAcls.size) {
            currentAcls = allAcls.find((item) => item.get('label') == 'defaultSet').get('acls');
        }

        // convert to a javascript object to make this easier to work with
        currentAcls = currentAcls.toJS();

        if (checked) {
            // first grab the ACLs for the new items
            const newAcls = action.get('acls').toJS();

            for (let i in newAcls) {
                let newAclGroup = newAcls[i];
                if (!_.has(currentAcls, newAclGroup.group)) {
                    currentAcls[newAclGroup.group] = newAclGroup.actions;
                    continue;
                }

                for (let j in newAclGroup.actions) {
                    let newAction = newAclGroup.actions[j];
                    currentAcls[newAclGroup.group].push(newAction);
                }
                currentAcls[newAclGroup.group] = _.uniq(currentAcls[newAclGroup.group]);
            }
        } else {
            // sorta the opposite.
            const removeAcls = action.get('acls').toJS();

            for (let i in removeAcls) {
                let removeAclGroup = removeAcls[i];
                // doesn't exist, which is odd...
                if (!_.has(currentAcls, removeAclGroup.group)) {
                    currentAcls[removeAclGroup.group] = removeAclGroup.actions;
                    continue;
                }

                for (let j in removeAclGroup.actions) {
                    let existingAction = removeAclGroup.actions[j];
                    for (let k in currentAcls[removeAclGroup.group]) {
                        console.log(currentAcls[removeAclGroup.group][k].action, existingAction.action);
                        if (currentAcls[removeAclGroup.group][k].action == existingAction.action) {
                            currentAcls[removeAclGroup.group][k].allowed = "0";
                        }
                    }
                }
                currentAcls[removeAclGroup.group] = _.uniq(currentAcls[removeAclGroup.group]);
            }
        }

        this.setState({'acl': this.state.acl.set('accesses', Immutable.fromJS(currentAcls))});
        return;
    }

    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)}
                />
        ];

        let resources = [];
        const accesses = this.state.acl.get('accesses').toJS();
        const allAcls = this.state.descriptiveAcls.toJS();

        // seems more complicated than it is.
        // looping through the descriptive acls.
        for (let i in allAcls) {
            let accessGroup = allAcls[i];

            // skip the default set
            if (accessGroup.label == 'defaultSet') {
                continue;
            }

            let options = [];
            for (let j in accessGroup.actions) {
                let accessAction = accessGroup.actions[j];

                // assume it's unchecked
                let isChecked = false;
                for (let k in accessAction.acls) {
                    let aclSet = accessAction.acls[k];

                    const currentAcls = accesses[aclSet.group];
                    if (!currentAcls) {
                        continue;
                    }

                    const total = aclSet.actions.length;
                    let count = 0;
                    _.find(currentAcls, (acl) => {
                        if (acl.action == '*' && acl.allowed == "1") {
                            count = total;
                            return true;
                        }
                        for (let i in aclSet.actions) {
                            if (acl.action == aclSet.actions[i].action
                                && acl.allowed == aclSet.actions[i].allowed) {
                                count++;
                            }
                        }
                    });

                    if (count == total) {
                        isChecked = true;
                    }
                }

                options.push(
                    <Checkbox label={accessAction.label} name={accessGroup.label + ' :: ' + accessAction.label} defaultChecked={isChecked} onCheck={this.handleCheck.bind(this)} />
                );
            }

            resources.push(
                <div>
                    <h4>{accessGroup.label}</h4>
                    {options}
                </div>
            );
        }

        return (
            <Dialog
                title="Edit Group"
                actions={actions}
                modal={false}
                open={this.props.open}
                onRequestClose={this.handleClose.bind(this)}
                autoScrollBodyContent={true}
                >

                <TextField
                    floatingLabelText="Name"
                    value={this.state.acl.get('name') ? this.state.acl.get('name') : ''}
                    name='name'
                    disabled={this.props.acls.selected ? true : false}
                    fullWidth={true}
                    onChange={this.handleFieldUpdate.bind(this)}
                    />

                <TextField
                    floatingLabelText="Description"
                    value={this.state.acl.get('description') ? this.state.acl.get('description') : ''}
                    name='description'
                    multiline={true}
                    fullWidth={true}
                    onChange={this.handleFieldUpdate.bind(this)}
                    />

                <Divider />
                <h3>Resources</h3>
                {resources}
                <p className='align-center small'>
                    If your publication requires more granular access controls, please <a href="mailto:support@getsnworks.com">contact support</a>.
                </p>
            </Dialog>
        );
    }
}

const AclModal = connect(mapStateToProps)(UnwrappedAclModal);