Home Reference Source

application/components/admin/group.js

  1. import _ from 'lodash';
  2. import React, {Component} from 'react';
  3. import Immutable from 'immutable';
  4.  
  5. import generatePassword from 'password-generator';
  6.  
  7. import {List, ListItem} from 'material-ui/List';
  8. import Subheader from 'material-ui/Subheader';
  9. import Avatar from 'material-ui/Avatar';
  10. import Divider from 'material-ui/Divider';
  11. import Checkbox from 'material-ui/Checkbox';
  12. import LinearProgress from 'material-ui/LinearProgress';
  13. import PersonIcon from 'material-ui/svg-icons/social/person-outline';
  14. import ReloadIcon from 'material-ui/svg-icons/action/autorenew';
  15.  
  16. import Dialog from 'material-ui/Dialog';
  17. import FlatButton from 'material-ui/FlatButton';
  18. import RaisedButton from 'material-ui/RaisedButton';
  19. import IconButton from 'material-ui/IconButton';
  20. import TextField from 'material-ui/TextField';
  21.  
  22. import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
  23. import {Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle} from 'material-ui/Toolbar';
  24.  
  25. import {connect} from 'react-redux';
  26. import ExpandingCard from './../expanding-card';
  27. import ReduxPaginator from './../common/redux-paginator';
  28. import DeleteButton from './../common/delete-button';
  29.  
  30. import CurrentUser from './../../current-user';
  31.  
  32. import DescriptiveAcls from './../../data/descriptive-acls';
  33.  
  34. import {
  35. aclsMaybeFetch,
  36. aclsFetch,
  37. aclsFetchOne,
  38. aclSetSelected,
  39. aclsFetchResources,
  40. aclUpdate,
  41. aclsRemove
  42. } from './../../redux/actions/acl-actions';
  43.  
  44. import {
  45. snackbarShowMessage
  46. } from './../../redux/actions/snackbar-actions';
  47.  
  48. import {Row, Col} from './../flexbox';
  49.  
  50. class GroupCard extends Component {
  51. constructor(props) {
  52. super(props);
  53.  
  54. this.state = {
  55. hasMore: false,
  56. hasLoaded: false,
  57. modalOpen: false,
  58. selected: []
  59. };
  60. }
  61.  
  62. componentWillReceiveProps(nextProps) {
  63. if (nextProps.openCard == 'group' && nextProps.openCard != this.props.openCard) {
  64. this.handleToggle(true);
  65. }
  66. }
  67.  
  68. handleToggle(expanded) {
  69. if (this.props.onToggle) {
  70. this.props.onToggle('group', expanded);
  71. }
  72.  
  73. if (expanded && !this.state.hasLoaded) {
  74. const {dispatch} = this.props;
  75. dispatch(aclsMaybeFetch())
  76. .then(() => this.setState({hasLoaded: true}));
  77. }
  78. }
  79.  
  80. handleAclClick(acl, e) {
  81. const {dispatch} = this.props;
  82.  
  83. dispatch(aclsFetchOne(acl.get('name')))
  84. .then(() => dispatch(aclsFetchResources()))
  85. .then(() => dispatch(aclSetSelected(acl.get('name'))))
  86. .then(() => {
  87. this.setState({
  88. 'modalOpen': true
  89. });
  90. });
  91.  
  92. }
  93.  
  94. handleNewAcl(e) {
  95. const {dispatch} = this.props;
  96. dispatch(aclsFetchResources())
  97. .then(() => dispatch(aclSetSelected(false)))
  98. .then(() => this.setState({'modalOpen': true}));
  99. }
  100.  
  101. handleModalClose(e) {
  102. this.setState({'modalOpen': false, 'selected': [], 'selectedItems': []});
  103. this.tableBody.setState({selectedRows: []});
  104. }
  105.  
  106. handleRowSelection(selection) {
  107. let selectedItems = [];
  108.  
  109. if (selection == 'all') {
  110. selectedItems = this.props.acls.items.map((item) => item.get('name'));
  111. } else {
  112. const items = this.props.acls.items.toJS();
  113. selectedItems = selection.map((i) =>items[i].name);
  114. }
  115.  
  116. this.setState({
  117. 'selectedItems': selectedItems,
  118. 'selected': selection
  119. });
  120. }
  121.  
  122. handlePagination(page) {
  123. if (this.props.acls.pagination.current == page) {
  124. return;
  125. }
  126.  
  127. const {dispatch} = this.props;
  128. dispatch(aclsFetch({page: page}));
  129. }
  130.  
  131. handleDelete(next) {
  132. const names = this.state.selectedItems.join(',');
  133. const {dispatch} = this.props;
  134.  
  135. dispatch(aclsRemove(names))
  136. .then(() => dispatch(aclsFetch()))
  137. .then(() => this.tableBody.setState({selectedRows: []}))
  138. .then(() => next());
  139.  
  140. return true;
  141. }
  142.  
  143. render() {
  144. let items = this.props.acls.items.map((item, i) => {
  145.  
  146. return (
  147. <TableRow
  148. key={i}
  149. selected={this.state.selected == 'all' || this.state.selected.indexOf(i) != -1 ? true : false}
  150. selectable={item.get('name') != 'Administrator' ? true : false}
  151. >
  152. <TableRowColumn>{item.get('name') != 'Administrator' ? (<a onClick={this.handleAclClick.bind(this, item)}>{item.get('name')}</a>) : item.get('name')}</TableRowColumn>
  153. <TableRowColumn></TableRowColumn>
  154. </TableRow>
  155. );
  156. });
  157.  
  158. return (
  159. <ExpandingCard
  160. onToggle={this.handleToggle.bind(this)}
  161. title="Groups"
  162. subtitle="Manage group and ACL permissions"
  163. expanded={this.props.openCard === 'group'}
  164. >
  165.  
  166. <Row middle='xs'>
  167. <Col start='xs'>
  168. <DeleteButton
  169. disabled={this.state.selectedItems && this.state.selectedItems.length ? false : true}
  170. onDelete={this.handleDelete.bind(this)}
  171. />
  172. <RaisedButton
  173. primary={true}
  174. label="Add Group"
  175. onClick={this.handleNewAcl.bind(this)}
  176. />
  177. </Col>
  178. </Row>
  179.  
  180.  
  181. <Table
  182. fixedHeader={true}
  183. selectable={true}
  184. multiSelectable={true}
  185. onRowSelection={this.handleRowSelection.bind(this)}
  186. >
  187. <TableHeader>
  188. <TableRow>
  189. <TableHeaderColumn>Name</TableHeaderColumn>
  190. <TableHeaderColumn></TableHeaderColumn>
  191. </TableRow>
  192. </TableHeader>
  193. <TableBody
  194. ref={tableBody => this.tableBody = tableBody}
  195. deselectOnClickaway={false}
  196. >
  197. {items}
  198. </TableBody>
  199. </Table>
  200.  
  201. {
  202. this.state.modalOpen
  203. ? <AclModal onClose={this.handleModalClose.bind(this)} open={this.state.modalOpen} />
  204. : ''
  205. }
  206. </ExpandingCard>
  207. );
  208. }
  209. }
  210.  
  211. let mapStateToProps = (state) => {
  212. return {
  213. acls: state.acls
  214. };
  215. }
  216.  
  217. export default connect(mapStateToProps)(GroupCard);
  218.  
  219. class UnwrappedAclModal extends Component {
  220. constructor(props) {
  221. super(props);
  222.  
  223. this.state = {
  224. loaded: false,
  225. acl: Immutable.fromJS({
  226. 'name': false,
  227. 'description': false,
  228. 'accesses': {}
  229. }),
  230. descriptiveAcls: Immutable.fromJS(DescriptiveAcls)
  231. };
  232.  
  233. }
  234.  
  235. componentWillMount() {
  236. const acl = this.props.acls.items.find((item) => {
  237. return item.get('name') == this.props.acls.selected;
  238. });
  239.  
  240. if (acl) {
  241. this.setState({'acl': acl, 'loaded': true});
  242. } else {
  243. this.setState({'acl': Immutable.fromJS({
  244. 'name': false,
  245. 'description': false,
  246. 'accesses': {},
  247. }), 'loaded': true});
  248. }
  249. }
  250.  
  251. handleClose(e) {
  252. this.props.onClose(e);
  253. }
  254.  
  255. handleSave(e) {
  256. let acl = this.state.acl;
  257.  
  258. const {dispatch} = this.props;
  259.  
  260. let data = {
  261. 'name': acl.get('name'),
  262. 'description': acl.get('description'),
  263. 'accesses': acl.get('accesses').toJS()
  264. };
  265.  
  266. dispatch(aclUpdate(data))
  267. .then(() => dispatch(aclsFetch()))
  268. .then(() => dispatch(snackbarShowMessage('Group updated')))
  269. .then(() => {
  270. this.props.onClose(e);
  271. });
  272. }
  273.  
  274. handleFieldUpdate(e, val) {
  275. const prop = e.target.name;
  276. let acl = this.state.acl;
  277.  
  278. this.setState({'acl': acl.set(prop, val)});
  279. }
  280.  
  281. handleCheck(e, checked) {
  282.  
  283. let currentAcls = this.state.acl.get('accesses');
  284. let groupLabel = e.target.name.substr(0, e.target.name.indexOf(' :: '));
  285. let actionLabel = e.target.name.substr(e.target.name.indexOf(' :: ')+4);
  286.  
  287. // first find the acls for the targeted action
  288. const allAcls = this.state.descriptiveAcls;
  289. const group = allAcls.find((item) => item.get('label') == groupLabel);
  290. const action = group.get('actions').find((item) => item.get('label') == actionLabel);
  291.  
  292. // if it's empty, set the default list
  293. if (!currentAcls || !currentAcls.size) {
  294. currentAcls = allAcls.find((item) => item.get('label') == 'defaultSet').get('acls');
  295. }
  296.  
  297. // convert to a javascript object to make this easier to work with
  298. currentAcls = currentAcls.toJS();
  299.  
  300. if (checked) {
  301. // first grab the ACLs for the new items
  302. const newAcls = action.get('acls').toJS();
  303.  
  304. for (let i in newAcls) {
  305. let newAclGroup = newAcls[i];
  306. if (!_.has(currentAcls, newAclGroup.group)) {
  307. currentAcls[newAclGroup.group] = newAclGroup.actions;
  308. continue;
  309. }
  310.  
  311. for (let j in newAclGroup.actions) {
  312. let newAction = newAclGroup.actions[j];
  313. currentAcls[newAclGroup.group].push(newAction);
  314. }
  315. currentAcls[newAclGroup.group] = _.uniq(currentAcls[newAclGroup.group]);
  316. }
  317. } else {
  318. // sorta the opposite.
  319. const removeAcls = action.get('acls').toJS();
  320.  
  321. for (let i in removeAcls) {
  322. let removeAclGroup = removeAcls[i];
  323. // doesn't exist, which is odd...
  324. if (!_.has(currentAcls, removeAclGroup.group)) {
  325. currentAcls[removeAclGroup.group] = removeAclGroup.actions;
  326. continue;
  327. }
  328.  
  329. for (let j in removeAclGroup.actions) {
  330. let existingAction = removeAclGroup.actions[j];
  331. for (let k in currentAcls[removeAclGroup.group]) {
  332. console.log(currentAcls[removeAclGroup.group][k].action, existingAction.action);
  333. if (currentAcls[removeAclGroup.group][k].action == existingAction.action) {
  334. currentAcls[removeAclGroup.group][k].allowed = "0";
  335. }
  336. }
  337. }
  338. currentAcls[removeAclGroup.group] = _.uniq(currentAcls[removeAclGroup.group]);
  339. }
  340. }
  341.  
  342. this.setState({'acl': this.state.acl.set('accesses', Immutable.fromJS(currentAcls))});
  343. return;
  344. }
  345.  
  346. render() {
  347.  
  348. const actions = [
  349. <FlatButton
  350. label="Cancel"
  351. primary={false}
  352. onClick={this.handleClose.bind(this)}
  353. />,
  354. <FlatButton
  355. label="Save"
  356. primary={true}
  357. keyboardFocused={true}
  358. onClick={this.handleSave.bind(this)}
  359. />
  360. ];
  361.  
  362. let resources = [];
  363. const accesses = this.state.acl.get('accesses').toJS();
  364. const allAcls = this.state.descriptiveAcls.toJS();
  365.  
  366. // seems more complicated than it is.
  367. // looping through the descriptive acls.
  368. for (let i in allAcls) {
  369. let accessGroup = allAcls[i];
  370.  
  371. // skip the default set
  372. if (accessGroup.label == 'defaultSet') {
  373. continue;
  374. }
  375.  
  376. let options = [];
  377. for (let j in accessGroup.actions) {
  378. let accessAction = accessGroup.actions[j];
  379.  
  380. // assume it's unchecked
  381. let isChecked = false;
  382. for (let k in accessAction.acls) {
  383. let aclSet = accessAction.acls[k];
  384.  
  385. const currentAcls = accesses[aclSet.group];
  386. if (!currentAcls) {
  387. continue;
  388. }
  389.  
  390. const total = aclSet.actions.length;
  391. let count = 0;
  392. _.find(currentAcls, (acl) => {
  393. if (acl.action == '*' && acl.allowed == "1") {
  394. count = total;
  395. return true;
  396. }
  397. for (let i in aclSet.actions) {
  398. if (acl.action == aclSet.actions[i].action
  399. && acl.allowed == aclSet.actions[i].allowed) {
  400. count++;
  401. }
  402. }
  403. });
  404.  
  405. if (count == total) {
  406. isChecked = true;
  407. }
  408. }
  409.  
  410. options.push(
  411. <Checkbox label={accessAction.label} name={accessGroup.label + ' :: ' + accessAction.label} defaultChecked={isChecked} onCheck={this.handleCheck.bind(this)} />
  412. );
  413. }
  414.  
  415. resources.push(
  416. <div>
  417. <h4>{accessGroup.label}</h4>
  418. {options}
  419. </div>
  420. );
  421. }
  422.  
  423. return (
  424. <Dialog
  425. title="Edit Group"
  426. actions={actions}
  427. modal={false}
  428. open={this.props.open}
  429. onRequestClose={this.handleClose.bind(this)}
  430. autoScrollBodyContent={true}
  431. >
  432.  
  433. <TextField
  434. floatingLabelText="Name"
  435. value={this.state.acl.get('name') ? this.state.acl.get('name') : ''}
  436. name='name'
  437. disabled={this.props.acls.selected ? true : false}
  438. fullWidth={true}
  439. onChange={this.handleFieldUpdate.bind(this)}
  440. />
  441.  
  442. <TextField
  443. floatingLabelText="Description"
  444. value={this.state.acl.get('description') ? this.state.acl.get('description') : ''}
  445. name='description'
  446. multiline={true}
  447. fullWidth={true}
  448. onChange={this.handleFieldUpdate.bind(this)}
  449. />
  450.  
  451. <Divider />
  452. <h3>Resources</h3>
  453. {resources}
  454. <p className='align-center small'>
  455. If your publication requires more granular access controls, please <a href="mailto:support@getsnworks.com">contact support</a>.
  456. </p>
  457. </Dialog>
  458. );
  459. }
  460. }
  461.  
  462. const AclModal = connect(mapStateToProps)(UnwrappedAclModal);