From 7a461e3cea37e5c12c30a3fdd936d618d89051d3 Mon Sep 17 00:00:00 2001
From: pkupczyk <piotr.kupczyk@id.ethz.ch>
Date: Tue, 22 Jan 2019 20:09:56 +0100
Subject: [PATCH] Initial version of: - SSDM-7559 : NEW openBIS UI -
 User/Groups - Navigation Tree - Visualization - SSDM-7566 : NEW openBIS UI -
 Types - Navigation Tree - Visualization

---
 openbis_ng_ui/.eslintrc.js                    |   6 +-
 openbis_ng_ui/src/components/App.jsx          | 120 +++----
 openbis_ng_ui/src/components/Browser.jsx      |  61 ++--
 .../src/components/BrowserButtons.jsx         |  50 +--
 .../src/components/BrowserFilter.jsx          |  38 +--
 openbis_ng_ui/src/components/BrowserList.jsx  | 110 +++----
 openbis_ng_ui/src/components/ErrorDialog.jsx  |  92 +++---
 openbis_ng_ui/src/components/ModeBar.jsx      |  65 ++--
 openbis_ng_ui/src/components/TabContainer.jsx | 142 ++++-----
 openbis_ng_ui/src/components/TabContent.jsx   |  16 +-
 openbis_ng_ui/src/components/TabPanel.jsx     |  88 +++---
 openbis_ng_ui/src/components/TopBar.jsx       | 122 ++++----
 openbis_ng_ui/src/components/WithError.jsx    |  42 +--
 openbis_ng_ui/src/components/WithLoader.jsx   |  64 ++--
 openbis_ng_ui/src/components/WithLogin.jsx    | 200 ++++++------
 .../src/components/database/EntityDetails.jsx | 210 ++++++-------
 .../src/components/database/OpenBISTable.jsx  | 173 +++++------
 .../components/database/OpenBISTableRow.jsx   | 116 +++----
 openbis_ng_ui/src/index.js                    |  48 +--
 openbis_ng_ui/src/profile.js                  |   2 +-
 openbis_ng_ui/src/reducer/actions.js          | 173 ++++++-----
 openbis_ng_ui/src/reducer/database/reducer.js | 215 ++++++-------
 openbis_ng_ui/src/reducer/initialstate.js     | 108 ++++---
 openbis_ng_ui/src/reducer/reducer.js          | 292 ++++++++++--------
 openbis_ng_ui/src/reducer/sagas.js            | 139 ++++++---
 openbis_ng_ui/src/reducer/types/reducer.js    | 109 +++++++
 openbis_ng_ui/src/reducer/users/reducer.js    | 161 ++++++++++
 openbis_ng_ui/src/services/openbis.js         | 169 +++++++---
 28 files changed, 1806 insertions(+), 1325 deletions(-)
 create mode 100644 openbis_ng_ui/src/reducer/types/reducer.js
 create mode 100644 openbis_ng_ui/src/reducer/users/reducer.js

diff --git a/openbis_ng_ui/.eslintrc.js b/openbis_ng_ui/.eslintrc.js
index c7098787dad..1b5c2d8298b 100644
--- a/openbis_ng_ui/.eslintrc.js
+++ b/openbis_ng_ui/.eslintrc.js
@@ -24,13 +24,13 @@ module.exports = {
       pragma: "React",
       version: "16.4.2"
     },
-    propWrapperFunctions: [ "forbidExtraProps" ]
+    propWrapperFunctions: ["forbidExtraProps"]
   },
   rules: {
     "react/jsx-uses-react": "error",
     "react/jsx-uses-vars": "error",
 
-    "indent": ["error", 2],
+    "indent": ["error", 4, {"SwitchCase": 1}],
     "linebreak-style": ["error", "unix"],
     "quotes": ["error", "single"],
     "semi": ["error", "never"],
@@ -41,4 +41,4 @@ module.exports = {
     // override default options for rules from base configurations
     "no-cond-assign": ["error", "always"],
   }
-}
+};
diff --git a/openbis_ng_ui/src/components/App.jsx b/openbis_ng_ui/src/components/App.jsx
index bebfcaaf7e7..55a4111599c 100644
--- a/openbis_ng_ui/src/components/App.jsx
+++ b/openbis_ng_ui/src/components/App.jsx
@@ -1,7 +1,7 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
+import {withStyles} from '@material-ui/core/styles'
 import HTML5Backend from 'react-dnd-html5-backend'
-import { DragDropContext } from 'react-dnd'
+import {DragDropContext} from 'react-dnd'
 import flow from 'lodash/flow'
 
 import Hidden from '@material-ui/core/Hidden'
@@ -17,75 +17,75 @@ import TopBar from './TopBar.jsx'
 const drawerWidth = 400
 
 const styles = {
-  right: {
-    width: `calc(100% - ${drawerWidth + 4 + 4 + 1}px)`,
-    paddingLeft: 4,
-    marginLeft: drawerWidth + 5,
-  },
-  
-  left: {
-    float: 'left',
-    width: drawerWidth,
-    paddingRight: 4,
-    borderRight: '1px dotted',
-    borderColor: '#e3e5ea',
-    height: '100%',
-    position: 'absolute',
-  },
+    right: {
+        width: `calc(100% - ${drawerWidth + 4 + 4 + 1}px)`,
+        paddingLeft: 4,
+        marginLeft: drawerWidth + 5,
+    },
 
-  browser: {
-    height: 'calc(100% - 160px)',
-    overflow: 'auto'
-  },
+    left: {
+        float: 'left',
+        width: drawerWidth,
+        paddingRight: 4,
+        borderRight: '1px dotted',
+        borderColor: '#e3e5ea',
+        height: '100%',
+        position: 'absolute',
+    },
 
-  topMargin: {
-    marginTop: 8
-  },
+    browser: {
+        height: 'calc(100% - 160px)',
+        overflow: 'auto'
+    },
+
+    topMargin: {
+        marginTop: 8
+    },
 }
 
 class App extends React.Component {
 
-  render() {
-    const classes = this.props.classes
+    render() {
+        const classes = this.props.classes
 
-    return (
-      <div>
+        return (
+            <div>
 
-        <Hidden mdUp>
-          <TopBar/>
-          <div className={classes.topMargin}>
-            <ModeBar/>
-          </div>
-          <BrowserFilter/>
-          <Browser/>
-          <BrowserButtons />
-          <div className={classes.topMargin}>
-            <TabPanel />
-          </div>
-        </Hidden>
+                <Hidden mdUp>
+                    <TopBar/>
+                    <div className={classes.topMargin}>
+                        <ModeBar/>
+                    </div>
+                    <BrowserFilter/>
+                    <Browser/>
+                    <BrowserButtons/>
+                    <div className={classes.topMargin}>
+                        <TabPanel/>
+                    </div>
+                </Hidden>
 
-        <Hidden smDown>
-          <div className={classes.left}>
-            <ModeBar/>
-            <BrowserFilter/>
-            <div className={classes.browser}>
-              <Browser />
-            </div>
-            <BrowserButtons />
-          </div>
-          <div className={classes.right}>
-            <TopBar />
-            <div className={classes.topMargin}>
-              <TabPanel />
+                <Hidden smDown>
+                    <div className={classes.left}>
+                        <ModeBar/>
+                        <BrowserFilter/>
+                        <div className={classes.browser}>
+                            <Browser/>
+                        </div>
+                        <BrowserButtons/>
+                    </div>
+                    <div className={classes.right}>
+                        <TopBar/>
+                        <div className={classes.topMargin}>
+                            <TabPanel/>
+                        </div>
+                    </div>
+                </Hidden>
             </div>
-          </div>
-        </Hidden>
-      </div>
-    )
-  }
+        )
+    }
 }
 
 export default flow(
-  withStyles(styles),
-  DragDropContext(HTML5Backend)
+    withStyles(styles),
+    DragDropContext(HTML5Backend)
 )(App)
diff --git a/openbis_ng_ui/src/components/Browser.jsx b/openbis_ng_ui/src/components/Browser.jsx
index 86f0406b139..a34c9f05f51 100644
--- a/openbis_ng_ui/src/components/Browser.jsx
+++ b/openbis_ng_ui/src/components/Browser.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { connect } from 'react-redux'
+import {connect} from 'react-redux'
 import ListItemText from '@material-ui/core/ListItemText'
 
 import BrowserList from './BrowserList.jsx'
@@ -7,34 +7,55 @@ import actions from '../reducer/actions.js'
 
 
 function mapDispatchToProps(dispatch) {
-  return {
-    selectEntity: permId => dispatch(actions.selectEntity(permId)),
-  }
+    return {
+        selectNode: permId => dispatch(actions.selectEntity(permId)),
+    }
 }
 
 
 function mapStateToProps(state) {
-  // TODO stack tree nodes here when the final tree model is done
-  return {
-    databaseTreeNodes: state.databaseTreeNodes,
-    selectedEntity: state.openEntities.selectedEntity,
-  }
+    // TODO stack tree nodes here when the final tree model is done
+    if (state.mode === 'DATABASE') {
+        return {
+            nodes: state.databaseTreeNodes,
+            selectedNodeId: state.openEntities.selectedEntity,
+        }
+    } else if (state.mode === 'USERS') {
+        return {
+            nodes: state.users.browser.nodes,
+            selectedNodeId: state.users.browser.selectedNodeId
+        }
+    } else if (state.mode === 'TYPES') {
+        return {
+            nodes: state.types.browser.nodes,
+            selectedNodeId: state.types.browser.selectedNodeId
+        }
+    } else {
+        return {
+            nodes: [],
+            selectedNodeId: null
+        }
+    }
 }
 
 
 class Browser extends React.Component {
 
-  render() {
-    return (
-      <BrowserList 
-        nodes={ this.props.databaseTreeNodes } 
-        level={ 0 } 
-        selectedNodeId={ this.props.selectedEntity } 
-        onSelect={ node => { if (node.type === 'as.dto.space.Space') this.props.selectEntity(node.id) } }
-        renderNode={ node => { return (<ListItemText secondary={node.id} />)} }
-      />
-    )
-  }
+    render() {
+        return (
+            <BrowserList
+                nodes={this.props.nodes}
+                level={0}
+                selectedNodeId={this.props.selectedNodeId}
+                onSelect={node => {
+                    if (node.type === 'as.dto.space.Space') this.props.selectNode(node.id)
+                }}
+                renderNode={node => {
+                    return (<ListItemText inset secondary={node.id}/>)
+                }}
+            />
+        )
+    }
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(Browser)
diff --git a/openbis_ng_ui/src/components/BrowserButtons.jsx b/openbis_ng_ui/src/components/BrowserButtons.jsx
index 1996ecab692..1834851a8ae 100644
--- a/openbis_ng_ui/src/components/BrowserButtons.jsx
+++ b/openbis_ng_ui/src/components/BrowserButtons.jsx
@@ -8,31 +8,31 @@ import AddIcon from '@material-ui/icons/Add'
 
 class BrowserButtons extends React.Component {
 
-  render() {
-    return (
-      <AppBar position='static'>
-        <Toolbar>
-          <Grid container alignItems='center'>
-            <Grid item xs={2}>
-              <Button 
-                variant="contained" 
-                color="primary">
-                <RemoveIcon />
-              </Button>
-            </Grid> 
-            <Grid item xs={8} />
-            <Grid item xs={2}>
-              <Button 
-                variant="contained"
-                color="primary">
-                <AddIcon />
-              </Button>
-            </Grid> 
-          </Grid>
-        </Toolbar>
-      </AppBar>
-    )
-  }
+    render() {
+        return (
+            <AppBar position='static'>
+                <Toolbar>
+                    <Grid container alignItems='center'>
+                        <Grid item xs={2}>
+                            <Button
+                                variant="contained"
+                                color="primary">
+                                <RemoveIcon/>
+                            </Button>
+                        </Grid>
+                        <Grid item xs={8}/>
+                        <Grid item xs={2}>
+                            <Button
+                                variant="contained"
+                                color="primary">
+                                <AddIcon/>
+                            </Button>
+                        </Grid>
+                    </Grid>
+                </Toolbar>
+            </AppBar>
+        )
+    }
 }
 
 export default BrowserButtons
diff --git a/openbis_ng_ui/src/components/BrowserFilter.jsx b/openbis_ng_ui/src/components/BrowserFilter.jsx
index 9ca605b39d7..55771780fda 100644
--- a/openbis_ng_ui/src/components/BrowserFilter.jsx
+++ b/openbis_ng_ui/src/components/BrowserFilter.jsx
@@ -1,34 +1,34 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
+import {withStyles} from '@material-ui/core/styles'
 import InputAdornment from '@material-ui/core/InputAdornment'
 import TextField from '@material-ui/core/TextField'
 import FilterIcon from '@material-ui/icons/FilterList'
 
 /*eslint-disable-next-line no-unused-vars*/
 const styles = theme => ({
-  browserFilter: {
-    width: '100%'
-  }
+    browserFilter: {
+        width: '100%'
+    }
 })
 
 class BrowserFilter extends React.Component {
 
-  render() {
-    const classes = this.props.classes
+    render() {
+        const classes = this.props.classes
 
-    return (
-      <TextField
-        className = { classes.browserFilter }
-        placeholder = "Filter"
-        InputProps={{
-          startAdornment: (
-            <InputAdornment position="start">
-              <FilterIcon />
-            </InputAdornment>
-          ),
-        }}/>
-    )
-  }
+        return (
+            <TextField
+                className={classes.browserFilter}
+                placeholder="Filter"
+                InputProps={{
+                    startAdornment: (
+                        <InputAdornment position="start">
+                            <FilterIcon/>
+                        </InputAdornment>
+                    ),
+                }}/>
+        )
+    }
 }
 
 export default withStyles(styles)(BrowserFilter)
diff --git a/openbis_ng_ui/src/components/BrowserList.jsx b/openbis_ng_ui/src/components/BrowserList.jsx
index 8b17fa4a438..32823f9f5e4 100644
--- a/openbis_ng_ui/src/components/BrowserList.jsx
+++ b/openbis_ng_ui/src/components/BrowserList.jsx
@@ -1,12 +1,13 @@
 import React from 'react'
-import { connect } from 'react-redux'
-import { withStyles } from '@material-ui/core/styles'
+import {connect} from 'react-redux'
+import {withStyles} from '@material-ui/core/styles'
 import List from '@material-ui/core/List'
 import ListItem from '@material-ui/core/ListItem'
 import ListItemIcon from '@material-ui/core/ListItemIcon'
 import ChevronRightIcon from '@material-ui/icons/ChevronRight'
 import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
 import HourglassEmptyIcon from '@material-ui/icons/HourglassEmpty'
+import Collapse from '@material-ui/core/Collapse';
 import PropTypes from 'prop-types'
 
 
@@ -15,74 +16,73 @@ import actions from '../reducer/actions.js'
 
 /*eslint-disable-next-line no-unused-vars*/
 const styles = theme => ({
-  noPadding: {
-    paddingTop: '0',
-    paddingBottom: '0',
-  },
+    noPadding: {
+        paddingTop: '0',
+        paddingBottom: '0',
+    },
 })
 
 
 function mapDispatchToProps(dispatch) {
-  return {
-    expandNode: (e, node) => {
-      e.stopPropagation()
-      dispatch(actions.expandNode(node))
-    },
-    collapseNode: (e, node) => {
-      e.stopPropagation()
-      dispatch(actions.collapseNode(node))
-    },
-  }
+    return {
+        expandNode: (e, node) => {
+            e.stopPropagation()
+            dispatch(actions.expandNode(node))
+        },
+        collapseNode: (e, node) => {
+            e.stopPropagation()
+            dispatch(actions.collapseNode(node))
+        },
+    }
 }
 
 class BrowserList extends React.Component {
 
-  icon(node) {
-    if (node.expanded) {
-      return (<ExpandMoreIcon  onClick={ (e) => this.props.collapseNode(e, node) }/>)
-    } else {
-      return (<ChevronRightIcon  onClick={ (e) => this.props.expandNode(e, node) }/>)
+    icon(node) {
+        if (node.expanded) {
+            return (<ListItemIcon><ExpandMoreIcon onClick={(e) => this.props.collapseNode(e, node)}/></ListItemIcon>)
+        } else if (node.loaded === false || (node.children !== null && node.children.length > 0)) {
+            return (<ListItemIcon><ChevronRightIcon onClick={(e) => this.props.expandNode(e, node)}/></ListItemIcon>)
+        } else {
+            return null
+        }
     }
-  }
 
-  render() {
-    const classes = this.props.classes
+    render() {
+        const classes = this.props.classes
 
-    return (
-      <List className={classes.noPadding}>
-        {
-          this.props.nodes.map(node => 
-            <div key={node.id}>
-              <ListItem 
-                button
-                selected={this.props.selectedNodeId ===  node.id}
-                key={node.id}
-                onClick = {() => this.props.onSelect(node)}
-                style={{ paddingLeft: '' + this.props.level * 20 + 'px' }}>
-                <ListItemIcon>
-                  {this.icon(node)}
-                </ListItemIcon>
-                { node.loading &&
-                <ListItemIcon>
-                  <HourglassEmptyIcon/>
-                </ListItemIcon>
+        return (
+            <List className={classes.noPadding}>
+                {
+                    this.props.nodes.map(node =>
+                        <div key={node.id}>
+                            <ListItem
+                                button
+                                selected={this.props.selectedNodeId === node.id}
+                                key={node.id}
+                                onClick={() => this.props.onSelect(node)}
+                                style={{paddingLeft: '' + this.props.level * 20 + 'px'}}>
+                                {this.icon(node)}
+                                {node.loading &&
+                                <ListItemIcon>
+                                    <HourglassEmptyIcon/>
+                                </ListItemIcon>
+                                }
+                                {this.props.renderNode(node)}
+                            </ListItem>
+                            <Collapse key={node.id + '-collapse'} in={node.expanded}>
+                                <BrowserList {...this.props} nodes={node.children} level={this.props.level + 1}/>
+                            </Collapse>
+                        </div>
+                    )
                 }
-                { this.props.renderNode(node) }
-              </ListItem>
-              {
-                node.expanded &&
-                <BrowserList {...this.props} nodes={node.children} level={this.props.level+1}/>
-              }
-            </div>
-          )
-        }
-      </List>
-    )
-  }
+            </List>
+        )
+    }
 }
 
 BrowserList.propTypes = {
-  renderNode: PropTypes.func.isRequired,
+    renderNode: PropTypes.func.isRequired,
 }
 
 export default connect(null, mapDispatchToProps)(withStyles(styles)(BrowserList))
diff --git a/openbis_ng_ui/src/components/ErrorDialog.jsx b/openbis_ng_ui/src/components/ErrorDialog.jsx
index dffa90bee31..539144f5299 100644
--- a/openbis_ng_ui/src/components/ErrorDialog.jsx
+++ b/openbis_ng_ui/src/components/ErrorDialog.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
+import {withStyles} from '@material-ui/core/styles'
 import PropTypes from 'prop-types'
 
 import Button from '@material-ui/core/Button'
@@ -13,61 +13,61 @@ import profile from '../profile.js'
 
 
 const dialogStyles = {
-  paper: {
-    backgroundColor: '#ffd2d2',
-  },
+    paper: {
+        backgroundColor: '#ffd2d2',
+    },
 }
 
 const StyledDialog = withStyles(dialogStyles)(Dialog)
 
 class ErrorDialog extends React.Component {
 
-  getErrorMailtoHref() {
-    let report = 
-      'agent: ' + navigator.userAgent + '%0D%0A' +
-      'domain: ' + location.hostname + '%0D%0A' +
-      'timestamp: ' + new Date() + '%0D%0A' +
-      'href: ' + location.href.replace(new RegExp('&', 'g'), ' - ') + '%0D%0A' +
-      'error: ' + JSON.stringify(this.props.exception['data'])
-    
-    let href = 
-      'mailto:' + profile.devEmail +
-      '?subject=openBIS Error Report [' + location.hostname + ']' +
-      '&body=' + report
-    return href
-  }
+    getErrorMailtoHref() {
+        let report =
+            'agent: ' + navigator.userAgent + '%0D%0A' +
+            'domain: ' + location.hostname + '%0D%0A' +
+            'timestamp: ' + new Date() + '%0D%0A' +
+            'href: ' + location.href.replace(new RegExp('&', 'g'), ' - ') + '%0D%0A' +
+            'error: ' + JSON.stringify(this.props.exception['data'])
 
-  render() {
-    return (
-      <StyledDialog
-        open={ true }
-        onClose={ this.props.closeError }
-        scroll="paper"
-        aria-labelledby="error-dialog-title"
-        fullWidth={ true }
-        maxWidth="md"
-      >
-        <DialogTitle id="error-dialog-title">Error</DialogTitle>
-        <DialogContent>
-          <DialogContentText>
-            { this.props.exception.message }
-          </DialogContentText>
-        </DialogContent>
-        <DialogActions>
-          <Button onClick={ this.props.onClose } color="primary">
-            Dismiss
-          </Button>
-          <Button color="primary" href={this.getErrorMailtoHref()}>
-            Send error report
-          </Button>
-        </DialogActions>
-      </StyledDialog>
-    )
-  }
+        let href =
+            'mailto:' + profile.devEmail +
+            '?subject=openBIS Error Report [' + location.hostname + ']' +
+            '&body=' + report
+        return href
+    }
+
+    render() {
+        return (
+            <StyledDialog
+                open={true}
+                onClose={this.props.closeError}
+                scroll="paper"
+                aria-labelledby="error-dialog-title"
+                fullWidth={true}
+                maxWidth="md"
+            >
+                <DialogTitle id="error-dialog-title">Error</DialogTitle>
+                <DialogContent>
+                    <DialogContentText>
+                        {this.props.exception.message}
+                    </DialogContentText>
+                </DialogContent>
+                <DialogActions>
+                    <Button onClick={this.props.onClose} color="primary">
+                        Dismiss
+                    </Button>
+                    <Button color="primary" href={this.getErrorMailtoHref()}>
+                        Send error report
+                    </Button>
+                </DialogActions>
+            </StyledDialog>
+        )
+    }
 }
 
 ErrorDialog.propTypes = {
-  exception: PropTypes.any.isRequired,
+    exception: PropTypes.any.isRequired,
 }
 
 export default ErrorDialog
diff --git a/openbis_ng_ui/src/components/ModeBar.jsx b/openbis_ng_ui/src/components/ModeBar.jsx
index 1a198dd0565..f306999d24d 100644
--- a/openbis_ng_ui/src/components/ModeBar.jsx
+++ b/openbis_ng_ui/src/components/ModeBar.jsx
@@ -1,39 +1,56 @@
 import React from 'react'
+import {connect} from 'react-redux'
 import AppBar from '@material-ui/core/AppBar'
 import Toolbar from '@material-ui/core/Toolbar'
 import Tabs from '@material-ui/core/Tabs'
 import Tab from '@material-ui/core/Tab'
-import { withStyles } from '@material-ui/core/styles'
+import {withStyles} from '@material-ui/core/styles'
+import actions from '../reducer/actions.js'
 
 /*eslint-disable-next-line no-unused-vars*/
 const styles = theme => ({
-  browserTabs: {
-    width: '100%'
-  },
-  browserTab: {
-    minWidth: '50px'
-  }
+    browserTabs: {
+        width: '100%'
+    },
+    browserTab: {
+        minWidth: '50px'
+    }
 })
 
+function mapStateToProps(state) {
+    return {
+        mode: state.mode
+    }
+}
+
+function mapDispatchToProps(dispatch) {
+    return {
+        setMode: (event, value) => {
+            dispatch(actions.setMode(value))
+        }
+    }
+}
+
 class ModeBar extends React.Component {
 
-  render() {
-    const classes = this.props.classes
+    render() {
+        const classes = this.props.classes
 
-    return (
-      <AppBar position="static">
-        <Toolbar>
-          <Tabs value={0} fullWidth className = { classes.browserTabs }>
-            <Tab className = { classes.browserTab } label="Database" />
-            <Tab className = { classes.browserTab } label="Types" />
-            <Tab className = { classes.browserTab } label="Users" />
-            <Tab className = { classes.browserTab } label="Favourites" />
-            <Tab className = { classes.browserTab } label="Tools" />
-          </Tabs>
-        </Toolbar>
-      </AppBar>
-    )
-  }
+        return (
+            <AppBar position="static">
+                <Toolbar>
+                    <Tabs value={this.props.mode} onChange={this.props.setMode} fullWidth
+                          className={classes.browserTabs}>
+                        <Tab value="DATABASE" className={classes.browserTab} label="Database"/>
+                        <Tab value="TYPES" className={classes.browserTab} label="Types"/>
+                        <Tab value="USERS" className={classes.browserTab} label="Users"/>
+                        <Tab value="FAVOURITES" className={classes.browserTab} label="Favourites"/>
+                        <Tab value="TOOLS" className={classes.browserTab} label="Tools"/>
+                    </Tabs>
+                </Toolbar>
+            </AppBar>
+        )
+    }
 }
 
-export default withStyles(styles)(ModeBar)
+export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ModeBar))
diff --git a/openbis_ng_ui/src/components/TabContainer.jsx b/openbis_ng_ui/src/components/TabContainer.jsx
index 30a983e16b4..2104c967099 100644
--- a/openbis_ng_ui/src/components/TabContainer.jsx
+++ b/openbis_ng_ui/src/components/TabContainer.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
+import {withStyles} from '@material-ui/core/styles'
 import AppBar from '@material-ui/core/AppBar'
 import CloseIcon from '@material-ui/icons/Close'
 import IconButton from '@material-ui/core/IconButton'
@@ -13,84 +13,84 @@ import TabContent from './TabContent.jsx'
 
 /* eslint-disable-next-line no-unused-vars */
 const styles = theme => ({
-  entityTabs: {
-    width: '100%'
-  },
-  inlineElement: {
-    display: 'inline-block'
-  },
-  hidden: {
-    display: 'none',
-  }
+    entityTabs: {
+        width: '100%'
+    },
+    inlineElement: {
+        display: 'inline-block'
+    },
+    hidden: {
+        display: 'none',
+    }
 })
 
 class TabContainer extends React.Component {
 
-  render() {
-    const classes = this.props.classes
-    const selectedKey = this.props.selectedKey
-    const selectedTabIndex = this.props.children.findIndex(child => child.key === selectedKey)
-    return (
-      <div>
-        <div>
-          <AppBar position="static">
-            <Toolbar>
-              <Tabs 
-                value={selectedTabIndex}
-                scrollable={ React.Children.count(this.props.children) > 0 }
-                scrollButtons="auto"
-                className = { classes.entityTabs }>
-                {
-                  React.Children.map(this.props.children, child =>
-                    <Tab 
-                      component="div" 
-                      label={
-                        <div>
-                          <div className = { classes.inlineElement }>{child.props.name}</div>
-                          { child.props.dirty &&
-                            <div className = { classes.inlineElement }>*</div>
-                          }
-                          <div className = { classes.inlineElement }>
-                            <IconButton onClick={ (e) => child.props.onClose(e) } >
-                              <CloseIcon/>
-                            </IconButton>
-                          </div>
-                        </div>}
-                      onClick = {child.props.onSelect}/>
-                  )
-                }
-              </Tabs>
-            </Toolbar>
-          </AppBar>
-        </div>
-        <div>
-          {
-            React.Children.map(this.props.children, child => {
-              return (
-                <div className={ selectedKey === child.key ? {} : classes.hidden }>
-                  {child}
+    render() {
+        const classes = this.props.classes
+        const selectedKey = this.props.selectedKey
+        const selectedTabIndex = this.props.children.findIndex(child => child.key === selectedKey)
+        return (
+            <div>
+                <div>
+                    <AppBar position="static">
+                        <Toolbar>
+                            <Tabs
+                                value={selectedTabIndex}
+                                scrollable={React.Children.count(this.props.children) > 0}
+                                scrollButtons="auto"
+                                className={classes.entityTabs}>
+                                {
+                                    React.Children.map(this.props.children, child =>
+                                        <Tab
+                                            component="div"
+                                            label={
+                                                <div>
+                                                    <div className={classes.inlineElement}>{child.props.name}</div>
+                                                    {child.props.dirty &&
+                                                    <div className={classes.inlineElement}>*</div>
+                                                    }
+                                                    <div className={classes.inlineElement}>
+                                                        <IconButton onClick={(e) => child.props.onClose(e)}>
+                                                            <CloseIcon/>
+                                                        </IconButton>
+                                                    </div>
+                                                </div>}
+                                            onClick={child.props.onSelect}/>
+                                    )
+                                }
+                            </Tabs>
+                        </Toolbar>
+                    </AppBar>
                 </div>
-              )
-            })
-          }
-        </div>
-      </div>
-    )
-  }
+                <div>
+                    {
+                        React.Children.map(this.props.children, child => {
+                            return (
+                                <div className={selectedKey === child.key ? {} : classes.hidden}>
+                                    {child}
+                                </div>
+                            )
+                        })
+                    }
+                </div>
+            </div>
+        )
+    }
 }
 
 TabContainer.propTypes = {
-  selectedKey: PropTypes.string.isRequired,
-  children: function (props, propName, componentName) {
-    const prop = props[propName]
-    let error = null
-    React.Children.forEach(prop, function (child) {
-      if (child.type !== TabContent) {
-        error = new Error('`' + componentName + '` children should be of type `TabContent`.')
-      }
-    })
-    return error
-  }
+    selectedKey: PropTypes.string.isRequired,
+    children: function (props, propName, componentName) {
+        const prop = props[propName]
+        let error = null
+        React.Children.forEach(prop, function (child) {
+            if (child.type !== TabContent) {
+                error = new Error('`' + componentName + '` children should be of type `TabContent`.')
+            }
+        })
+        return error
+    }
 }
 
 export default withStyles(styles)(TabContainer)
diff --git a/openbis_ng_ui/src/components/TabContent.jsx b/openbis_ng_ui/src/components/TabContent.jsx
index d1a925010af..34ae06c7915 100644
--- a/openbis_ng_ui/src/components/TabContent.jsx
+++ b/openbis_ng_ui/src/components/TabContent.jsx
@@ -3,16 +3,16 @@ import PropTypes from 'prop-types'
 
 
 class TabContent extends React.Component {
-  render() {
-    return <div>{this.props.children}</div>
-  }
+    render() {
+        return <div>{this.props.children}</div>
+    }
 }
 
-TabContent.propTypes ={
-  name: PropTypes.string.isRequired,
-  dirty: PropTypes.bool.isRequired,
-  onSelect: PropTypes.func.isRequired,
-  onClose: PropTypes.func.isRequired,
+TabContent.propTypes = {
+    name: PropTypes.string.isRequired,
+    dirty: PropTypes.bool.isRequired,
+    onSelect: PropTypes.func.isRequired,
+    onClose: PropTypes.func.isRequired,
 }
 
 export default TabContent
diff --git a/openbis_ng_ui/src/components/TabPanel.jsx b/openbis_ng_ui/src/components/TabPanel.jsx
index 970e381d19f..55b700b66b2 100644
--- a/openbis_ng_ui/src/components/TabPanel.jsx
+++ b/openbis_ng_ui/src/components/TabPanel.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { connect } from 'react-redux'
+import {connect} from 'react-redux'
 import EntityDetails from './database/EntityDetails.jsx'
 import TabContainer from './TabContainer.jsx'
 import TabContent from './TabContent.jsx'
@@ -8,62 +8,66 @@ import actions from '../reducer/actions.js'
 
 /**
  * This component at the moment only makes tabs for entities.
- * In the future, it should be extended for other kinds of tabs 
+ * In the future, it should be extended for other kinds of tabs
  * (settings forms etc.).
  */
 
 function mapDispatchToProps(dispatch) {
-  return {
-    selectEntity: (entityPermId) => dispatch(actions.selectEntity(entityPermId)),
-    closeEntity: (e, entityPermId) => {
-      e.stopPropagation()
-      dispatch(actions.closeEntity(entityPermId))
+    return {
+        selectEntity: (entityPermId) => dispatch(actions.selectEntity(entityPermId)),
+        closeEntity: (e, entityPermId) => {
+            e.stopPropagation()
+            dispatch(actions.closeEntity(entityPermId))
+        }
     }
-  }
 }
 
 
 function mapStateToProps(state) {
-  const selectedEntity = state.openEntities.selectedEntity
-  const spaces = state.database.spaces
-  return {
-    openEntities: state.openEntities.entities
-      .filter(permId => permId in spaces)
-      .map(permId => spaces[permId]),
-    selectedEntity: selectedEntity,
-    dirtyEntities: state.dirtyEntities,
-  }
+    const selectedEntity = state.openEntities.selectedEntity
+    const spaces = state.database.spaces
+    return {
+        openEntities: state.openEntities.entities
+            .filter(permId => permId in spaces)
+            .map(permId => spaces[permId]),
+        selectedEntity: selectedEntity,
+        dirtyEntities: state.dirtyEntities,
+    }
 }
 
 
 class TabPanel extends React.Component {
 
-  render() {
-    if (this.props.openEntities.length === 0) {
-      return null
-    }
-    return (
-      <TabContainer selectedKey={this.props.selectedEntity}>
-        {
-          this.props.openEntities.map(entity => {
-            return(
-              <TabContent
-                key={entity.permId.permId}
-                name={entity.code}
-                dirty={this.props.dirtyEntities.indexOf(entity.permId.permId) > -1}
-                onSelect={() => {this.props.selectEntity(entity.permId.permId)}}
-                onClose={(e) => {this.props.closeEntity(e, entity.permId.permId)}}
-              >
-                <EntityDetails 
-                  entity={entity} 
-                />
-              </TabContent>
-            )
-          })
+    render() {
+        if (this.props.openEntities.length === 0) {
+            return null
         }
-      </TabContainer>
-    )
-  }
+        return (
+            <TabContainer selectedKey={this.props.selectedEntity}>
+                {
+                    this.props.openEntities.map(entity => {
+                        return (
+                            <TabContent
+                                key={entity.permId.permId}
+                                name={entity.code}
+                                dirty={this.props.dirtyEntities.indexOf(entity.permId.permId) > -1}
+                                onSelect={() => {
+                                    this.props.selectEntity(entity.permId.permId)
+                                }}
+                                onClose={(e) => {
+                                    this.props.closeEntity(e, entity.permId.permId)
+                                }}
+                            >
+                                <EntityDetails
+                                    entity={entity}
+                                />
+                            </TabContent>
+                        )
+                    })
+                }
+            </TabContainer>
+        )
+    }
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(TabPanel)
diff --git a/openbis_ng_ui/src/components/TopBar.jsx b/openbis_ng_ui/src/components/TopBar.jsx
index 86aefdd4bbb..afe525a36a0 100644
--- a/openbis_ng_ui/src/components/TopBar.jsx
+++ b/openbis_ng_ui/src/components/TopBar.jsx
@@ -1,6 +1,6 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
-import { connect } from 'react-redux'
+import {withStyles} from '@material-ui/core/styles'
+import {connect} from 'react-redux'
 import flow from 'lodash/flow'
 
 import Grid from '@material-ui/core/Grid'
@@ -17,79 +17,79 @@ import actions from '../reducer/actions.js'
 
 
 const styles = theme => ({
-  searchField: {
-    backgroundColor: 'white',
-    width: '100%'
-  },
-  leftIcon: {
-    marginRight: theme.spacing.unit,
-  },
-  grid: {
-    minWidth: 0
-  }
+    searchField: {
+        backgroundColor: 'white',
+        width: '100%'
+    },
+    leftIcon: {
+        marginRight: theme.spacing.unit,
+    },
+    grid: {
+        minWidth: 0
+    }
 })
 
 
 function mapDispatchToProps(dispatch) {
-  return {
-    logout: () => dispatch(actions.logout()),
-  }
+    return {
+        logout: () => dispatch(actions.logout()),
+    }
 }
 
 
 class TopBar extends React.Component {
 
-  render() {
-    const classes = this.props.classes
+    render() {
+        const classes = this.props.classes
 
-    return (
-      <AppBar position='static'>
-        <Toolbar>
-          <Grid container alignItems='center' spacing={8}>
+        return (
+            <AppBar position='static'>
+                <Toolbar>
+                    <Grid container alignItems='center' spacing={8}>
 
-            <Grid item xs className={ classes.grid }>
-              <TextField
-                className = { classes.searchField }
-                InputProps={{
-                  startAdornment: (
-                    <InputAdornment position="start">
-                      <SearchIcon />
-                    </InputAdornment>
-                  ),
-                }}/>
-            </Grid> 
+                        <Grid item xs className={classes.grid}>
+                            <TextField
+                                className={classes.searchField}
+                                InputProps={{
+                                    startAdornment: (
+                                        <InputAdornment position="start">
+                                            <SearchIcon/>
+                                        </InputAdornment>
+                                    ),
+                                }}/>
+                        </Grid>
 
-            <Grid item>
-              <Button
-                variant="contained" 
-                color="primary">
-                <SearchIcon className={ classes.leftIcon } />
-                <Hidden mdUp>
-                  Adv.
-                </Hidden>
-                <Hidden smDown>
-                  Adv. search
-                </Hidden>
-              </Button>
-            </Grid>
+                        <Grid item>
+                            <Button
+                                variant="contained"
+                                color="primary">
+                                <SearchIcon className={classes.leftIcon}/>
+                                <Hidden mdUp>
+                                    Adv.
+                                </Hidden>
+                                <Hidden smDown>
+                                    Adv. search
+                                </Hidden>
+                            </Button>
+                        </Grid>
 
-            <Grid item>
-              <Button
-                variant="contained" 
-                color="primary"
-                onClick={ this.props.logout }>
-                <LogoutIcon />
-              </Button>
-            </Grid>
-  
-          </Grid>
-        </Toolbar>
-      </AppBar>
-    )
-  }
+                        <Grid item>
+                            <Button
+                                variant="contained"
+                                color="primary"
+                                onClick={this.props.logout}>
+                                <LogoutIcon/>
+                            </Button>
+                        </Grid>
+
+                    </Grid>
+                </Toolbar>
+            </AppBar>
+        )
+    }
 }
 
 export default flow(
-  connect(null, mapDispatchToProps),
-  withStyles(styles)
+    connect(null, mapDispatchToProps),
+    withStyles(styles)
 )(TopBar)
diff --git a/openbis_ng_ui/src/components/WithError.jsx b/openbis_ng_ui/src/components/WithError.jsx
index b3ff70d6f09..3f042f8ff1b 100644
--- a/openbis_ng_ui/src/components/WithError.jsx
+++ b/openbis_ng_ui/src/components/WithError.jsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import { connect } from 'react-redux'
+import {connect} from 'react-redux'
 import flow from 'lodash/flow'
 
 import ErrorDialog from './ErrorDialog.jsx'
@@ -7,35 +7,35 @@ import actions from '../reducer/actions.js'
 
 
 function mapStateToProps(state) {
-  return {
-    exception: state.exceptions.length > 0 ? state.exceptions[0] : null
-  }
+    return {
+        exception: state.exceptions.length > 0 ? state.exceptions[0] : null
+    }
 }
 
 function mapDispatchToProps(dispatch) {
-  return {
-    closeError: () => dispatch(actions.closeError()),
-  }
+    return {
+        closeError: () => dispatch(actions.closeError()),
+    }
 }
-  
+
 
 class WithError extends React.Component {
 
-  render() {
-
-    return (
-      <div>
-        {
-          this.props.exception &&
-          <ErrorDialog exception={this.props.exception} onClose={this.props.closeError} />
-        }
-        {this.props.children}
-      </div>
-    )
-  }
+    render() {
+
+        return (
+            <div>
+                {
+                    this.props.exception &&
+                    <ErrorDialog exception={this.props.exception} onClose={this.props.closeError}/>
+                }
+                {this.props.children}
+            </div>
+        )
+    }
 }
 
 export default flow(
-  connect(mapStateToProps, mapDispatchToProps),
+    connect(mapStateToProps, mapDispatchToProps),
 )(WithError)
   
\ No newline at end of file
diff --git a/openbis_ng_ui/src/components/WithLoader.jsx b/openbis_ng_ui/src/components/WithLoader.jsx
index 3edfb7855e9..9f8bf234674 100644
--- a/openbis_ng_ui/src/components/WithLoader.jsx
+++ b/openbis_ng_ui/src/components/WithLoader.jsx
@@ -1,49 +1,49 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
-import { connect } from 'react-redux'
+import {withStyles} from '@material-ui/core/styles'
+import {connect} from 'react-redux'
 import CircularProgress from '@material-ui/core/CircularProgress'
 import flow from 'lodash/flow'
 
 const styles = {
-  loader: { 
-    position: 'absolute',
-    paddingTop: '15%',
-    width: '100%',
-    height: '100%',
-    zIndex: 1000,
-    backgroundColor: '#000000',
-    opacity: 0.5,
-    textAlign: 'center',
-  }
+    loader: {
+        position: 'absolute',
+        paddingTop: '15%',
+        width: '100%',
+        height: '100%',
+        zIndex: 1000,
+        backgroundColor: '#000000',
+        opacity: 0.5,
+        textAlign: 'center',
+    }
 }
 
 function mapStateToProps(state) {
-  return {
-    loading: state.loading,
-    exception: state.exceptions.length > 0 ? state.exceptions[0] : null
-  }
+    return {
+        loading: state.loading,
+        exception: state.exceptions.length > 0 ? state.exceptions[0] : null
+    }
 }
 
 class WithLoader extends React.Component {
 
-  render() {
-    const classes = this.props.classes
+    render() {
+        const classes = this.props.classes
 
-    return (
-      <div>
-        {
-          this.props.loading &&
-          <div className={classes.loader}>
-            <CircularProgress className={classes.progress} />
-          </div>
-        }
-        {this.props.children}
-      </div>
-    )
-  }
+        return (
+            <div>
+                {
+                    this.props.loading &&
+                    <div className={classes.loader}>
+                        <CircularProgress className={classes.progress}/>
+                    </div>
+                }
+                {this.props.children}
+            </div>
+        )
+    }
 }
 
 export default flow(
-  connect(mapStateToProps),
-  withStyles(styles),
+    connect(mapStateToProps),
+    withStyles(styles),
 )(WithLoader)
diff --git a/openbis_ng_ui/src/components/WithLogin.jsx b/openbis_ng_ui/src/components/WithLogin.jsx
index 58310c0c476..5e3a0779bd2 100644
--- a/openbis_ng_ui/src/components/WithLogin.jsx
+++ b/openbis_ng_ui/src/components/WithLogin.jsx
@@ -1,6 +1,6 @@
 import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
-import { connect } from 'react-redux'
+import {withStyles} from '@material-ui/core/styles'
+import {connect} from 'react-redux'
 import flow from 'lodash/flow'
 
 import Button from '@material-ui/core/Button'
@@ -13,117 +13,121 @@ import actions from '../reducer/actions.js'
 
 
 const styles = {
-  card: {
-    marginTop: '10%',
-    marginBottom: '10em',
-    width: '30em',
-    margin: '0 auto',
-  },
-  title: {
-    fontSize: 24,
-  },
-  textField: {
-    width: '100%',
-  },
-  button: {
-    marginTop: '1em',
-  },
-  container: {
-    width: '100%',
-    height: '100%',
-    overflow: 'auto',
-  },
+    card: {
+        marginTop: '10%',
+        marginBottom: '10em',
+        width: '30em',
+        margin: '0 auto',
+    },
+    title: {
+        fontSize: 24,
+    },
+    textField: {
+        width: '100%',
+    },
+    button: {
+        marginTop: '1em',
+    },
+    container: {
+        width: '100%',
+        height: '100%',
+        overflow: 'auto',
+    },
 }
 
 function mapStateToProps(state) {
-  return {
-    sessionActive: state.sessionActive,
-  }
+    return {
+        sessionActive: state.sessionActive,
+    }
 }
 
 function mapDispatchToProps(dispatch) {
-  return {
-    login: (user, password) => dispatch(actions.login(user, password)),
-  }
+    return {
+        login: (user, password) => dispatch(actions.login(user, password)),
+    }
 }
 
 class WithLogin extends React.Component {
 
-  state = {}
-
-  handleChange = name => event => {
-    this.setState({
-      [name]: event.target.value,
-    })
-  }
+    state = {}
 
-  keyPress(e){
-    if(e.key === 'Enter'){
-      this.login()
+    handleChange = name => event => {
+        this.setState({
+            [name]: event.target.value,
+        })
     }
-  }
-
-  login = () => {
-    this.props.login(this.state.user, this.state.password)
-  }
-
-  render() {
-    const classes = this.props.classes
-
-    return (
-      <div>
-        {
-          this.props.sessionActive &&
-          <div>
-            {this.props.children}
-          </div>
-        }
-        {
-          !this.props.sessionActive &&
-          <div className={classes.container}>
-            <Card className={classes.card}>
-              <CardContent>
-                <Typography className={classes.title}>
-                  Login
-                </Typography>
-                <TextField
-                  id="standard-name"
-                  label="User"
-                  className={classes.textField}
-                  margin="normal"
-                  autoFocus={true}
-                  onKeyPress={ (e) => { this.keyPress(e) }}
-                  onChange={this.handleChange('user')}
-                />
-                <TextField
-                  id="standard-password-input"
-                  label="Password"
-                  className={classes.textField}
-                  type="password"
-                  autoComplete="current-password"
-                  margin="normal"
-                  onKeyPress={ (e) => { this.keyPress(e) }}
-                  onChange={this.handleChange('password')}
-                />
-                <Button 
-                  onClick={ this.login }
-                  color="primary"
-                  className={classes.button}
-                  variant="contained">
-                  Login
-                </Button>
-
-              </CardContent>
-            </Card>
-          </div>
+
+    keyPress(e) {
+        if (e.key === 'Enter') {
+            this.login()
         }
+    }
+
+    login = () => {
+        this.props.login(this.state.user, this.state.password)
+    }
 
-      </div>
-    )
-  }
+    render() {
+        const classes = this.props.classes
+
+        return (
+            <div>
+                {
+                    this.props.sessionActive &&
+                    <div>
+                        {this.props.children}
+                    </div>
+                }
+                {
+                    !this.props.sessionActive &&
+                    <div className={classes.container}>
+                        <Card className={classes.card}>
+                            <CardContent>
+                                <Typography className={classes.title}>
+                                    Login
+                                </Typography>
+                                <TextField
+                                    id="standard-name"
+                                    label="User"
+                                    className={classes.textField}
+                                    margin="normal"
+                                    autoFocus={true}
+                                    onKeyPress={(e) => {
+                                        this.keyPress(e)
+                                    }}
+                                    onChange={this.handleChange('user')}
+                                />
+                                <TextField
+                                    id="standard-password-input"
+                                    label="Password"
+                                    className={classes.textField}
+                                    type="password"
+                                    autoComplete="current-password"
+                                    margin="normal"
+                                    onKeyPress={(e) => {
+                                        this.keyPress(e)
+                                    }}
+                                    onChange={this.handleChange('password')}
+                                />
+                                <Button
+                                    onClick={this.login}
+                                    color="primary"
+                                    className={classes.button}
+                                    variant="contained">
+                                    Login
+                                </Button>
+
+                            </CardContent>
+                        </Card>
+                    </div>
+                }
+
+            </div>
+        )
+    }
 }
 
 export default flow(
-  connect(mapStateToProps, mapDispatchToProps),
-  withStyles(styles)
+    connect(mapStateToProps, mapDispatchToProps),
+    withStyles(styles)
 )(WithLogin)
diff --git a/openbis_ng_ui/src/components/database/EntityDetails.jsx b/openbis_ng_ui/src/components/database/EntityDetails.jsx
index 0a7e97e6419..36c88fe3a76 100644
--- a/openbis_ng_ui/src/components/database/EntityDetails.jsx
+++ b/openbis_ng_ui/src/components/database/EntityDetails.jsx
@@ -1,6 +1,6 @@
 import React from 'react'
-import { connect } from 'react-redux'
-import { withStyles } from '@material-ui/core/styles'
+import {connect} from 'react-redux'
+import {withStyles} from '@material-ui/core/styles'
 import Button from '@material-ui/core/Button'
 import Card from '@material-ui/core/Card'
 import CardHeader from '@material-ui/core/CardHeader'
@@ -21,128 +21,128 @@ import actions from '../../reducer/actions.js'
 
 /* eslint-disable-next-line no-unused-vars */
 const styles = theme => ({
-  textField: {
-    width: '100%'
-  }
+    textField: {
+        width: '100%'
+    }
 })
 
 function mapStateToProps(state) {
-  return {
-    dirtyEntities: state.dirtyEntities,    
-  }
+    return {
+        dirtyEntities: state.dirtyEntities,
+    }
 }
 
 function mapDispatchToProps(dispatch) {
-  return {
-    setDirty: (entity, dirty) => dispatch(actions.setDirty(entity.permId.permId, dirty)),
-    saveEntity: (entity) => dispatch(actions.saveEntity(entity)),
-  }
+    return {
+        setDirty: (entity, dirty) => dispatch(actions.setDirty(entity.permId.permId, dirty)),
+        saveEntity: (entity) => dispatch(actions.saveEntity(entity)),
+    }
 }
 
 class EntityDetails extends React.Component {
 
-  constructor(props) {
-    super(props)
-    this.state = {
-      description: this.props.entity ? this.props.entity.description : '',
-      dirty: false
+    constructor(props) {
+        super(props)
+        this.state = {
+            description: this.props.entity ? this.props.entity.description : '',
+            dirty: false
+        }
     }
-  }
 
-  matches(value1, value2) {
-    if (value1 === null || value1.length === 0) {
-      return value2 === null || value2.length === 0
+    matches(value1, value2) {
+        if (value1 === null || value1.length === 0) {
+            return value2 === null || value2.length === 0
+        }
+        return value1 === value2
     }
-    return value1 === value2
-  }
-
-  handleChange(name, e) {
-    const currentlyDirty = !this.matches(e.target.value, this.props.entity.description)
-    const stateDirty = this.props.dirtyEntities.indexOf(this.props.entity.permId.permId) > -1
-    if (currentlyDirty !== stateDirty) {
-      this.props.setDirty(this.props.entity, currentlyDirty)
+
+    handleChange(name, e) {
+        const currentlyDirty = !this.matches(e.target.value, this.props.entity.description)
+        const stateDirty = this.props.dirtyEntities.indexOf(this.props.entity.permId.permId) > -1
+        if (currentlyDirty !== stateDirty) {
+            this.props.setDirty(this.props.entity, currentlyDirty)
+        }
+        this.setState({
+            description: e.target.value,
+            dirty: currentlyDirty
+        })
     }
-    this.setState({
-      description: e.target.value,
-      dirty: currentlyDirty
-    })
-  }
-
-  handleSave(entity) {
-    const entityCopy = _.cloneDeep(entity)
-    entityCopy.description = this.state.description
-    this.props.saveEntity(entityCopy)
-  }
-
-  render() {
-    const { classes } = this.props
-    if (this.props.entity === null) {
-      return (<div />)
+
+    handleSave(entity) {
+        const entityCopy = _.cloneDeep(entity)
+        entityCopy.description = this.state.description
+        this.props.saveEntity(entityCopy)
     }
-    const entity = this.props.entity
-
-    const options = { 
-      weekday: 'long', 
-      year: 'numeric', 
-      month: 'long', 
-      day: 'numeric', 
-      hour: '2-digit',
-      minute: '2-digit',
-      second: '2-digit'
+
+    render() {
+        const {classes} = this.props
+        if (this.props.entity === null) {
+            return (<div/>)
+        }
+        const entity = this.props.entity
+
+        const options = {
+            weekday: 'long',
+            year: 'numeric',
+            month: 'long',
+            day: 'numeric',
+            hour: '2-digit',
+            minute: '2-digit',
+            second: '2-digit'
+        }
+        const created = new Date(entity.registrationDate).toLocaleDateString('en-US', options)
+
+        return (
+            <Card className={classes.card}>
+                <CardHeader
+                    avatar={
+                        <Avatar aria-label="Recipe" className={classes.avatar}>
+                            S
+                        </Avatar>
+                    }
+                    action={
+                        <Button
+                            color="primary"
+                            disabled={!this.state.dirty}
+                            variant="contained"
+                            onClick={() => this.handleSave(this.props.entity)}>
+                            Save
+                        </Button>
+                    }
+                    title={'Space ' + entity.code}
+                    subheader={'Created on ' + created}
+                />
+                <CardContent>
+                    <TextField
+                        label='Description'
+                        className={classes.textField}
+                        value={this.state.description ? this.state.description : ''}
+                        onChange={(e) => {
+                            this.handleChange('description', e)
+                        }}
+                        margin='normal'
+                    />
+                    <OpenBISTable/>
+
+                </CardContent>
+                <CardActions disableActionSpacing>
+                    <IconButton aria-label="Add to favorites">
+                        <FavoriteIcon/>
+                    </IconButton>
+                    <IconButton aria-label="Share">
+                        <ShareIcon/>
+                    </IconButton>
+                    <IconButton>
+                        <ExpandMoreIcon/>
+                    </IconButton>
+                </CardActions>
+            </Card>
+        )
     }
-    const created = new Date(entity.registrationDate).toLocaleDateString('en-US', options)
-
-    return(
-      <Card className={classes.card}>
-        <CardHeader
-          avatar={
-            <Avatar aria-label="Recipe" className={classes.avatar}>
-              S
-            </Avatar>
-          }
-          action={
-            <Button 
-              color="primary" 
-              disabled={!this.state.dirty} 
-              variant="contained"
-              onClick={ () => this.handleSave(this.props.entity) }>
-              Save
-            </Button>
-          }
-          title={ 'Space '+entity.code }
-          subheader={ 'Created on ' + created }
-        />
-        <CardContent>
-          <TextField
-            label='Description'
-            className={classes.textField}
-            value={this.state.description ? this.state.description : ''}
-            onChange={ (e) => {
-              this.handleChange('description', e)
-            }}
-            margin='normal'
-          />
-          <OpenBISTable />
-
-        </CardContent>
-        <CardActions disableActionSpacing>
-          <IconButton aria-label="Add to favorites">
-            <FavoriteIcon />
-          </IconButton>
-          <IconButton aria-label="Share">
-            <ShareIcon />
-          </IconButton>
-          <IconButton>
-            <ExpandMoreIcon />
-          </IconButton>
-        </CardActions>
-      </Card>
-    )
-  }
 }
 
 EntityDetails.propTypes = {
-  entity: PropTypes.any.isRequired,
+    entity: PropTypes.any.isRequired,
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(EntityDetails))
diff --git a/openbis_ng_ui/src/components/database/OpenBISTable.jsx b/openbis_ng_ui/src/components/database/OpenBISTable.jsx
index 7dd715bfc41..25a4d87b912 100644
--- a/openbis_ng_ui/src/components/database/OpenBISTable.jsx
+++ b/openbis_ng_ui/src/components/database/OpenBISTable.jsx
@@ -1,6 +1,6 @@
 import React from 'react'
-import { connect } from 'react-redux'
-import { withStyles } from '@material-ui/core/styles'
+import {connect} from 'react-redux'
+import {withStyles} from '@material-ui/core/styles'
 import Table from '@material-ui/core/Table'
 import TableBody from '@material-ui/core/TableBody'
 import TableCell from '@material-ui/core/TableCell'
@@ -20,100 +20,101 @@ import actions from '../../reducer/actions.js'
 
 /* eslint-disable-next-line no-unused-vars */
 const styles = theme => ({
-  table: {
-    marginTop: 30,
-    overflowX: 'auto'
-  },
-  headerCell: {
-    backgroundColor: theme.palette.grey[300],
-  }
+    table: {
+        marginTop: 30,
+        overflowX: 'auto'
+    },
+    headerCell: {
+        backgroundColor: theme.palette.grey[300],
+    }
 })
 
 function mapStateToProps(state) {
-  const table = state.database.table
-  return {
-    spaces: state.database.spaces,
-    columns: table.columns,
-    data: table.data,
-    page: table.page,
-    sortColumn: table.sortColumn,
-    sortDirection: table.sortDirection,
-    filter: table.filter
-  }
+    const table = state.database.table
+    return {
+        spaces: state.database.spaces,
+        columns: table.columns,
+        data: table.data,
+        page: table.page,
+        sortColumn: table.sortColumn,
+        sortDirection: table.sortDirection,
+        filter: table.filter
+    }
 }
 
 function mapDispatchToProps(dispatch) {
-  return {
-    selectEntity: e => dispatch(actions.selectEntity(e)),
-    changePage: page => dispatch(actions.changePage(page)),
-    sortBy: column => dispatch(actions.sortBy(column)),
-    setFilter: filter => dispatch(actions.setFilter(filter))
-  }
+    return {
+        selectEntity: e => dispatch(actions.selectEntity(e)),
+        changePage: page => dispatch(actions.changePage(page)),
+        sortBy: column => dispatch(actions.sortBy(column)),
+        setFilter: filter => dispatch(actions.setFilter(filter))
+    }
 }
 
 class OpenBISTable extends React.Component {
 
-  render() {
-    const { data, columns, classes, page } = this.props
-    return (
-      <Paper className={classes.table}>
-        <Table padding='checkbox'>
-          <TableHead>
-            <TableRow>
-              <TableCell className={classes.headerCell} variant='head'/>
-              { Object.entries(columns).map(([column, type]) => 
-                <TableCell key={column} className={classes.headerCell} numeric={type === 'int'} variant='head'>
-                  <TableSortLabel
-                    className={classes.headerCell}
-                    active={ this.props.sortColumn === column }
-                    direction={ this.props.sortDirection }
-                    onClick={() => this.props.sortBy(column)} >
-                    { column }
-                  </TableSortLabel>
-                </TableCell>
-              )}
-            </TableRow>
-          </TableHead>
-          <TableBody>
-            {
-              data.slice(page * 10, page * 10 + 10).map(row => 
-                <OpenBISTableRow key = { row.Id } row = { row } columns = { columns } />
-              )
-            }
-          </TableBody>
-          <TableFooter className={classes.headerCell}>
-            <TableRow>
-              <TableCell>
-                <div 
-                  style = {{ cursor: 'pointer' }}>
-                  <SettingsIcon/>
-                </div>
-              </TableCell>
-              <TableCell colSpan={2}>
-                <TextField
-                  onChange = { e => this.props.setFilter(e.target.value) }
-                  placeholder = "filter rows by value"
-                  InputProps={{
-                    startAdornment: (
-                      <InputAdornment position="start" variant="outlined">
-                        <FilterIcon />
-                      </InputAdornment>
-                    ),
-                  }}/>
-              </TableCell>
-              <TablePagination
-                count={data.length}
-                rowsPerPage={10}
-                rowsPerPageOptions={[10]}
-                page={page}
-                onChangePage={(_, page) => this.props.changePage(page)}
-              />
-            </TableRow>
-          </TableFooter>
-        </Table>
-      </Paper>
-    )
-  }  
+    render() {
+        const {data, columns, classes, page} = this.props
+        return (
+            <Paper className={classes.table}>
+                <Table padding='checkbox'>
+                    <TableHead>
+                        <TableRow>
+                            <TableCell className={classes.headerCell} variant='head'/>
+                            {Object.entries(columns).map(([column, type]) =>
+                                <TableCell key={column} className={classes.headerCell} numeric={type === 'int'}
+                                           variant='head'>
+                                    <TableSortLabel
+                                        className={classes.headerCell}
+                                        active={this.props.sortColumn === column}
+                                        direction={this.props.sortDirection}
+                                        onClick={() => this.props.sortBy(column)}>
+                                        {column}
+                                    </TableSortLabel>
+                                </TableCell>
+                            )}
+                        </TableRow>
+                    </TableHead>
+                    <TableBody>
+                        {
+                            data.slice(page * 10, page * 10 + 10).map(row =>
+                                <OpenBISTableRow key={row.Id} row={row} columns={columns}/>
+                            )
+                        }
+                    </TableBody>
+                    <TableFooter className={classes.headerCell}>
+                        <TableRow>
+                            <TableCell>
+                                <div
+                                    style={{cursor: 'pointer'}}>
+                                    <SettingsIcon/>
+                                </div>
+                            </TableCell>
+                            <TableCell colSpan={2}>
+                                <TextField
+                                    onChange={e => this.props.setFilter(e.target.value)}
+                                    placeholder="filter rows by value"
+                                    InputProps={{
+                                        startAdornment: (
+                                            <InputAdornment position="start" variant="outlined">
+                                                <FilterIcon/>
+                                            </InputAdornment>
+                                        ),
+                                    }}/>
+                            </TableCell>
+                            <TablePagination
+                                count={data.length}
+                                rowsPerPage={10}
+                                rowsPerPageOptions={[10]}
+                                page={page}
+                                onChangePage={(_, page) => this.props.changePage(page)}
+                            />
+                        </TableRow>
+                    </TableFooter>
+                </Table>
+            </Paper>
+        )
+    }
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(OpenBISTable))
diff --git a/openbis_ng_ui/src/components/database/OpenBISTableRow.jsx b/openbis_ng_ui/src/components/database/OpenBISTableRow.jsx
index 57cc32ac4ec..4275cfa8ec1 100644
--- a/openbis_ng_ui/src/components/database/OpenBISTableRow.jsx
+++ b/openbis_ng_ui/src/components/database/OpenBISTableRow.jsx
@@ -1,93 +1,93 @@
 import React from 'react'
 import ReactDOM from 'react-dom'
-import { connect } from 'react-redux'
+import {connect} from 'react-redux'
 import TableCell from '@material-ui/core/TableCell'
 import TableRow from '@material-ui/core/TableRow'
-import { DragSource } from 'react-dnd'
-import { DropTarget } from 'react-dnd'
+import {DragSource} from 'react-dnd'
+import {DropTarget} from 'react-dnd'
 import DragHandle from '@material-ui/icons/DragHandle'
 
 import actions from '../../reducer/actions.js'
 
 
 function targetCollect(connect, monitor) {
-  return {
-    connectDropTarget: connect.dropTarget()
-  }
+    return {
+        connectDropTarget: connect.dropTarget()
+    }
 }
 
 function sourceCollect(connect, monitor) {
-  return {
-    connectDragSource: connect.dragSource(),
-    isDragging: monitor.isDragging(),
-    connectDragPreview: connect.dragPreview()
-  }
+    return {
+        connectDragSource: connect.dragSource(),
+        isDragging: monitor.isDragging(),
+        connectDragPreview: connect.dragPreview()
+    }
 }
 
 const source = {
-  beginDrag(props, monitor, component) {
-    return {
-      id: props.row.Id
+    beginDrag(props, monitor, component) {
+        return {
+            id: props.row.Id
+        }
+    },
+    endDrag(props, monitor, component) {
+        monitor.getDropResult().fire()
     }
-  },
-  endDrag(props, monitor, component) {
-    monitor.getDropResult().fire()
-  }
 }
 
 const target = {
-  drop(props, monitor, component) {
-    return {
-      source: monitor.getItem(),
-      target: {
-        id: props.row.Id
-      },
-      fire: () => props.moveEntity(monitor.getItem().id, props.row.Id)
+    drop(props, monitor, component) {
+        return {
+            source: monitor.getItem(),
+            target: {
+                id: props.row.Id
+            },
+            fire: () => props.moveEntity(monitor.getItem().id, props.row.Id)
+        }
     }
-  }
 }
 
 function mapStateToProps(state) {
-  return {
-    spaces: state.database.spaces,
-  }
+    return {
+        spaces: state.database.spaces,
+    }
 }
 
 function mapDispatchToProps(dispatch) {
-  return {
-    selectEntity: e => dispatch(actions.selectEntity(e)),
-    moveEntity: (source, target) => dispatch(actions.moveEntity(source, target))
-  }
+    return {
+        selectEntity: e => dispatch(actions.selectEntity(e)),
+        moveEntity: (source, target) => dispatch(actions.moveEntity(source, target))
+    }
 }
 
 class OpenBISTableRow extends React.Component {
 
-  constructor(props) {
-    super(props)
-  }
+    constructor(props) {
+        super(props)
+    }
 
-  render() {
-    const row = this.props.row
-    return (
-      <TableRow 
-        key ={ row.Id }
-        ref = { instance => {
-          this.props.connectDragPreview(ReactDOM.findDOMNode(instance)) 
-          this.props.connectDropTarget(ReactDOM.findDOMNode(instance)) 
-        }}>
-        <TableCell>
-          <div 
-            style = {{ cursor: 'pointer' }}
-            ref = { instance => this.props.connectDragSource(ReactDOM.findDOMNode(instance)) }>
-            <DragHandle/>
-          </div>
-        </TableCell>
-        { Object.entries(this.props.columns).map(([col, type]) =>
-          <TableCell key={col} numeric={type === 'int'}> { row[col] }</TableCell>
-        )}
-      </TableRow>
-    )
-  }
+    render() {
+        const row = this.props.row
+        return (
+            <TableRow
+                key={row.Id}
+                ref={instance => {
+                    this.props.connectDragPreview(ReactDOM.findDOMNode(instance))
+                    this.props.connectDropTarget(ReactDOM.findDOMNode(instance))
+                }}>
+                <TableCell>
+                    <div
+                        style={{cursor: 'pointer'}}
+                        ref={instance => this.props.connectDragSource(ReactDOM.findDOMNode(instance))}>
+                        <DragHandle/>
+                    </div>
+                </TableCell>
+                {Object.entries(this.props.columns).map(([col, type]) =>
+                    <TableCell key={col} numeric={type === 'int'}> {row[col]}</TableCell>
+                )}
+            </TableRow>
+        )
+    }
 }
 
 export default connect(mapStateToProps, mapDispatchToProps)(DropTarget('row', target, targetCollect)(DragSource('row', source, sourceCollect)(OpenBISTableRow)))
diff --git a/openbis_ng_ui/src/index.js b/openbis_ng_ui/src/index.js
index 2f67930c467..27a9ea7dbc2 100644
--- a/openbis_ng_ui/src/index.js
+++ b/openbis_ng_ui/src/index.js
@@ -2,11 +2,11 @@
 import "regenerator-runtime/runtime"
 import React from 'react'
 import ReactDOM from 'react-dom'
-import { createStore, applyMiddleware, compose } from 'redux'
-import { Provider } from 'react-redux'
+import {createStore, applyMiddleware, compose} from 'redux'
+import {Provider} from 'react-redux'
 import createSagaMiddleware from 'redux-saga'
 import reducer from './reducer/reducer.js'
-import { watchActions } from './reducer/sagas'
+import {watchActions} from './reducer/sagas'
 
 const sagaMiddleware = createSagaMiddleware()
 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
@@ -15,29 +15,29 @@ export const store = createStore(reducer, composeEnhancers(applyMiddleware(sagaM
 sagaMiddleware.run(watchActions)
 
 const render = () => {
-  const App = require('./components/App.jsx').default
-  const WithLogin = require('./components/WithLogin.jsx').default
-  const WithLoader = require('./components/WithLoader.jsx').default
-  const WithError = require('./components/WithError.jsx').default
-  ReactDOM.render(
-    <Provider store = { store }>
-      <WithLoader>
-        <WithError>
-          <WithLogin>
-            <App />
-          </WithLogin>
-        </WithError>
-      </WithLoader>
-    </Provider>,
-    document.getElementById("app")
-  )
+    const App = require('./components/App.jsx').default
+    const WithLogin = require('./components/WithLogin.jsx').default
+    const WithLoader = require('./components/WithLoader.jsx').default
+    const WithError = require('./components/WithError.jsx').default
+    ReactDOM.render(
+        <Provider store={store}>
+            <WithLoader>
+                <WithError>
+                    <WithLogin>
+                        <App/>
+                    </WithLogin>
+                </WithError>
+            </WithLoader>
+        </Provider>,
+        document.getElementById("app")
+    )
 }
 
 if (module.hot) {
-  module.hot.accept('./components/App.jsx', () => setTimeout(render))  
-  module.hot.accept('./reducer/reducer.js', () => {
-    const nextRootReducer = require('./reducer/reducer.js').default
-    store.replaceReducer(nextRootReducer)
-  });
+    module.hot.accept('./components/App.jsx', () => setTimeout(render))
+    module.hot.accept('./reducer/reducer.js', () => {
+        const nextRootReducer = require('./reducer/reducer.js').default
+        store.replaceReducer(nextRootReducer)
+    });
 }
 render()
\ No newline at end of file
diff --git a/openbis_ng_ui/src/profile.js b/openbis_ng_ui/src/profile.js
index cc4eef0850e..278d465d8ba 100644
--- a/openbis_ng_ui/src/profile.js
+++ b/openbis_ng_ui/src/profile.js
@@ -1,5 +1,5 @@
 let profile = {
-  devEmail: 'dummy.address@test.ch'
+    devEmail: 'dummy.address@test.ch'
 }
 
 export default profile
\ No newline at end of file
diff --git a/openbis_ng_ui/src/reducer/actions.js b/openbis_ng_ui/src/reducer/actions.js
index c7934cad243..67ebe161ea6 100644
--- a/openbis_ng_ui/src/reducer/actions.js
+++ b/openbis_ng_ui/src/reducer/actions.js
@@ -1,84 +1,93 @@
 export default {
-  init: () => ({
-    type: 'INIT',
-  }),
-  // TODO setDirty for generic tabs instead of entities
-  setDirty: (entityPermId, dirty) => ({
-    type: 'SET-DIRTY',
-    entityPermId: entityPermId,
-    dirty: dirty
-  }),
-  expandNode: (node) => ({
-    type: 'EXPAND-NODE',
-    node: node
-  }),
-  collapseNode: (node) => ({
-    type: 'COLLAPSE-NODE',
-    node: node
-  }),
-  // database stuff
-  setSpaces: spaces => ({
-    type: 'SET-SPACES',
-    spaces: spaces,
-  }),
-  setProjects: (projects, spacePermId) => ({
-    type: 'SET-PROJECTS',
-    projects: projects,
-    spacePermId: spacePermId
-  }),
-  selectEntity: entityPermId => ({
-    type: 'SELECT-ENTITY',
-    entityPermId: entityPermId
-  }),
-  closeEntity: entityPermId => ({
-    type: 'CLOSE-ENTITY',
-    entityPermId: entityPermId
-  }),
-  changePage: page => ({
-    type: 'CHANGE-PAGE',
-    page: page
-  }),
-  sortBy: column => ({
-    type: 'SORT-BY',
-    column: column
-  }),
-  setFilter: filter => ({
-    type: 'SET-FILTER',
-    value: filter
-  }),
-  moveEntity: (source, target) => ({
-    type: 'MOVE-ENTITY',
-    source: source,
-    target: target
-  }),
-  saveEntity: (entity) => ({
-    type: 'SAVE-ENTITY',
-    entity: entity
-  }),
-  savedEntity: (entity) => ({
-    type: 'SAVED-ENTITY',
-    entity: entity
-  }),
-  error: (exception) => ({
-    type: 'ERROR',
-    exception: exception
-  }),
-  closeError: () => ({
-    type: 'CLOSE-ERROR',
-  }),
-  // session
-  login: (username, password) => ({
-    type: 'LOGIN',
-    username: username,
-    password: password,
-  }),
-  loginDone: () => ({
-    type: 'LOGIN-DONE',
-  }),
-  logout: () => ({
-    type: 'LOGOUT'
-  }),
-  logoutDone: () => ({
-    type: 'LOGOUT-DONE',
-  }),
+    init: () => ({
+        type: 'INIT',
+    }),
+    // TODO setDirty for generic tabs instead of entities
+    setDirty: (entityPermId, dirty) => ({
+        type: 'SET-DIRTY',
+        entityPermId: entityPermId,
+        dirty: dirty
+    }),
+    expandNode: (node) => ({
+        type: 'EXPAND-NODE',
+        node: node
+    }),
+    collapseNode: (node) => ({
+        type: 'COLLAPSE-NODE',
+        node: node
+    }),
+    // database stuff
+    setSpaces: spaces => ({
+        type: 'SET-SPACES',
+        spaces: spaces,
+    }),
+    setProjects: (projects, spacePermId) => ({
+        type: 'SET-PROJECTS',
+        projects: projects,
+        spacePermId: spacePermId
+    }),
+    selectEntity: entityPermId => ({
+        type: 'SELECT-ENTITY',
+        entityPermId: entityPermId
+    }),
+    closeEntity: entityPermId => ({
+        type: 'CLOSE-ENTITY',
+        entityPermId: entityPermId
+    }),
+    changePage: page => ({
+        type: 'CHANGE-PAGE',
+        page: page
+    }),
+    sortBy: column => ({
+        type: 'SORT-BY',
+        column: column
+    }),
+    setFilter: filter => ({
+        type: 'SET-FILTER',
+        value: filter
+    }),
+    setMode: mode => ({
+        type: 'SET-MODE',
+        mode
+    }),
+    setModeDone: (mode, data) => ({
+        type: 'SET-MODE-DONE',
+        mode,
+        data
+    }),
+    moveEntity: (source, target) => ({
+        type: 'MOVE-ENTITY',
+        source: source,
+        target: target
+    }),
+    saveEntity: (entity) => ({
+        type: 'SAVE-ENTITY',
+        entity: entity
+    }),
+    savedEntity: (entity) => ({
+        type: 'SAVED-ENTITY',
+        entity: entity
+    }),
+    error: (exception) => ({
+        type: 'ERROR',
+        exception: exception
+    }),
+    closeError: () => ({
+        type: 'CLOSE-ERROR',
+    }),
+    // session
+    login: (username, password) => ({
+        type: 'LOGIN',
+        username: username,
+        password: password,
+    }),
+    loginDone: () => ({
+        type: 'LOGIN-DONE',
+    }),
+    logout: () => ({
+        type: 'LOGOUT'
+    }),
+    logoutDone: () => ({
+        type: 'LOGOUT-DONE',
+    }),
 }
diff --git a/openbis_ng_ui/src/reducer/database/reducer.js b/openbis_ng_ui/src/reducer/database/reducer.js
index 345a5a270a5..61799136a0a 100644
--- a/openbis_ng_ui/src/reducer/database/reducer.js
+++ b/openbis_ng_ui/src/reducer/database/reducer.js
@@ -2,52 +2,52 @@ import initialState from '../initialstate.js'
 
 
 function filterOf(filter, columns) {
-  if (filter == null || filter.length === 0) {
-    return _ => true
-  } else { 
-    return entity => 
-      Object.keys(columns)
-        .map(col => entity[col])
-        .map(value => value != null && value.toString().includes(filter))
-        .includes(true)
-  }
+    if (filter == null || filter.length === 0) {
+        return _ => true
+    } else {
+        return entity =>
+            Object.keys(columns)
+                .map(col => entity[col])
+                .map(value => value != null && value.toString().includes(filter))
+                .includes(true)
+    }
 }
 
 function sortOf(orderColumn, direction, columns) {
-  return (a, b) => {
-    let valA = a[orderColumn]
-    let valB = b[orderColumn]
-    if (valA == null && valB == null) {
-      return 0
-    }
-    if (valA == null) {
-      return 1
-    }
-    if (valB == null) {
-      return -1
+    return (a, b) => {
+        let valA = a[orderColumn]
+        let valB = b[orderColumn]
+        if (valA == null && valB == null) {
+            return 0
+        }
+        if (valA == null) {
+            return 1
+        }
+        if (valB == null) {
+            return -1
+        }
+        const type = columns[orderColumn]
+        if (type === 'int') {
+            return direction === 'asc' ? valA - valB : valB - valA
+        } else {
+            if (valA < valB) {
+                return direction === 'asc' ? -1 : 1
+            } else if (valA > valB) {
+                return direction === 'asc' ? 1 : -1
+            } else {
+                return 0
+            }
+        }
     }
-    const type = columns[orderColumn]
-    if (type === 'int') {
-      return direction === 'asc' ? valA - valB : valB - valA
-    } else {
-      if (valA < valB){
-        return direction === 'asc' ? -1 : 1
-      } else if (valA > valB) {
-        return direction === 'asc' ? 1 : -1
-      } else {
-        return 0
-      }    
-    }
-  }
 }
 
 function transformData(data, columns, filter, orderColumn, direction) {
-  const filtered = data.filter(filterOf(filter, columns))
-  if (orderColumn in columns) {
-    return filtered.sort(sortOf(orderColumn, direction, columns))
-  } else {
-    return filtered
-  }
+    const filtered = data.filter(filterOf(filter, columns))
+    if (orderColumn in columns) {
+        return filtered.sort(sortOf(orderColumn, direction, columns))
+    } else {
+        return filtered
+    }
 }
 
 const concat = (...arrays) => [].concat(...arrays)
@@ -55,91 +55,94 @@ const take = (array, n) => array.slice(0, n)
 const drop = (array, n) => array.slice(n)
 const remove = (array, index) => [...take(array, index), ...drop(array, index + 1)]
 
-function move (array, oldIndex, newIndex) {
-  const value = array[oldIndex]
-  const removed = remove(array, oldIndex)
-  return concat(take(removed, newIndex), [value], drop(removed, newIndex))
+function move(array, oldIndex, newIndex) {
+    const value = array[oldIndex]
+    const removed = remove(array, oldIndex)
+    return concat(take(removed, newIndex), [value], drop(removed, newIndex))
 }
 
 
 function entitiesByPermId(entities) {
-  const byPermId = {}
-  for (let entity of entities) {
-    byPermId[entity.permId.permId] = entity
-  }
-  return byPermId
+    const byPermId = {}
+    for (let entity of entities) {
+        byPermId[entity.permId.permId] = entity
+    }
+    return byPermId
 }
 
 
 function spaces(spaces = initialState.spaces, action) {
-  switch (action.type) {
-  case 'SET-SPACES': {
-    return entitiesByPermId(action.spaces)
-  }
-  case 'SAVED-ENTITY': {
-    const newSpaces = Object.assign({}, spaces)
-    newSpaces[action.entity.permId.permId] = action.entity
-    return newSpaces
-  }
-  default: {
-    return spaces
-  }}
+    switch (action.type) {
+        case 'SET-SPACES': {
+            return entitiesByPermId(action.spaces)
+        }
+        case 'SAVED-ENTITY': {
+            const newSpaces = Object.assign({}, spaces)
+            newSpaces[action.entity.permId.permId] = action.entity
+            return newSpaces
+        }
+        default: {
+            return spaces
+        }
+    }
 }
 
 
 function projects(projects = initialState.projects, action) {
-  switch (action.type) {
-  case 'SET-PROJECTS': {
-    return  entitiesByPermId(action.projects)
-  }
-  default: {
-    return projects
-  }}
+    switch (action.type) {
+        case 'SET-PROJECTS': {
+            return entitiesByPermId(action.projects)
+        }
+        default: {
+            return projects
+        }
+    }
 }
 
 
 function table(table = initialState.table, action) {
-  switch (action.type) {
-  case 'CHANGE-PAGE': {
-    return Object.assign({}, table, { page: action.page })
-  }  
-  case 'SORT-BY': {
-    const column = action.column
-    const direction = 
-      column === table.sortColumn 
-        ? table.sortDirection === 'asc' ? 'desc' : 'asc' 
-        : 'asc'
-    return Object.assign({}, table, {
-      sortColumn: column,
-      sortDirection: direction,
-      page: 0,
-      data: transformData(table.initialData, table.columns, table.filter, column, direction)
-    })
-  }
-  case 'SET-FILTER': {
-    return Object.assign({}, table, {
-      page: 0,
-      filter: action.value,
-      data: transformData(table.initialData, table.columns, action.value, table.sortColumn, table.sortDirection)
-    })
-  }
-  case 'MOVE-ENTITY': {
-    const sourceIndex = table.data.findIndex(e => e.Id === action.source)
-    const targetIndex = table.data.findIndex(e => e.Id === action.target)
-    return Object.assign({}, table, {
-      data: move(table.data, sourceIndex, targetIndex)
-    })
-  }
-  default: {
-    return table
-  }}
+    switch (action.type) {
+        case 'CHANGE-PAGE': {
+            return Object.assign({}, table, {page: action.page})
+        }
+        case 'SORT-BY': {
+            const column = action.column
+            const direction =
+                column === table.sortColumn
+                    ? table.sortDirection === 'asc' ? 'desc' : 'asc'
+                    : 'asc'
+            return Object.assign({}, table, {
+                sortColumn: column,
+                sortDirection: direction,
+                page: 0,
+                data: transformData(table.initialData, table.columns, table.filter, column, direction)
+            })
+        }
+        case 'SET-FILTER': {
+            return Object.assign({}, table, {
+                page: 0,
+                filter: action.value,
+                data: transformData(table.initialData, table.columns, action.value, table.sortColumn, table.sortDirection)
+            })
+        }
+        case 'MOVE-ENTITY': {
+            const sourceIndex = table.data.findIndex(e => e.Id === action.source)
+            const targetIndex = table.data.findIndex(e => e.Id === action.target)
+            return Object.assign({}, table, {
+                data: move(table.data, sourceIndex, targetIndex)
+            })
+        }
+        default: {
+            return table
+        }
+    }
 }
 
 
 export default function database(database = initialState.database, action) {
-  return {
-    spaces: spaces(database.spaces, action),
-    projects: projects(database.projects, action),
-    table: table(database.table, action),
-  }
+    return {
+        spaces: spaces(database.spaces, action),
+        projects: projects(database.projects, action),
+        table: table(database.table, action),
+    }
 }
diff --git a/openbis_ng_ui/src/reducer/initialstate.js b/openbis_ng_ui/src/reducer/initialstate.js
index f3e4db2a8e1..76c9cba6841 100644
--- a/openbis_ng_ui/src/reducer/initialstate.js
+++ b/openbis_ng_ui/src/reducer/initialstate.js
@@ -1,11 +1,10 @@
-
 function foldLeft(array, acc, reducer) {
-  if (array.length === 0) {
-    return acc
-  } else {
-    const [head, ...tail] = array
-    return foldLeft(tail, reducer(acc, head), reducer)
-  }
+    if (array.length === 0) {
+        return acc
+    } else {
+        const [head, ...tail] = array
+        return foldLeft(tail, reducer(acc, head), reducer)
+    }
 }
 
 const take = (array, n) => array.slice(0, n)
@@ -18,60 +17,73 @@ const intColumns = ['Weight', 'Length', 'Width']
 
 
 function shuffle(arr) {
-  return arr.slice(0).sort((a, b) => Math.random() * 3 - 1.5)
+    return arr.slice(0).sort((a, b) => Math.random() * 3 - 1.5)
 }
 
 function randomString() {
-  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
+    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
 }
 
 function createEntity(id) {
-  const numStrings = Math.random()  * 3
-  const numInts = Math.random()  * 3
-  let entity = { Id: id }
-  entity = foldLeft(take(shuffle(stringColumns), numStrings), entity, (acc, a) => Object.assign({}, acc, { [a] : randomString() }))
-  entity = foldLeft(take(shuffle(intColumns), numInts), entity, (acc, a) => Object.assign({}, acc, { [a] : Math.floor(Math.random() * 100) }))
-  return entity
+    const numStrings = Math.random() * 3
+    const numInts = Math.random() * 3
+    let entity = {Id: id}
+    entity = foldLeft(take(shuffle(stringColumns), numStrings), entity, (acc, a) => Object.assign({}, acc, {[a]: randomString()}))
+    entity = foldLeft(take(shuffle(intColumns), numInts), entity, (acc, a) => Object.assign({}, acc, {[a]: Math.floor(Math.random() * 100)}))
+    return entity
 }
 
 const data = [...Array(10000).keys()].map(i => createEntity(i + 1))
 
-let tableColumns = foldLeft(stringColumns, {}, (acc, a) => Object.assign({}, acc, { [a]: 'string'}))
-tableColumns = foldLeft(intColumns, tableColumns, (acc, a) => Object.assign({}, acc, { [a]: 'int'}))
-tableColumns = foldLeft(shuffle(Object.entries(tableColumns)), { Id: 'int' }, (acc, a) => Object.assign({}, acc, { [a[0]] : a[1] }))
+let tableColumns = foldLeft(stringColumns, {}, (acc, a) => Object.assign({}, acc, {[a]: 'string'}))
+tableColumns = foldLeft(intColumns, tableColumns, (acc, a) => Object.assign({}, acc, {[a]: 'int'}))
+tableColumns = foldLeft(shuffle(Object.entries(tableColumns)), {Id: 'int'}, (acc, a) => Object.assign({}, acc, {[a[0]]: a[1]}))
 
 // TODO split initialstate when it becomes too big
 export default {
 
-  sessionActive: false,
-
-  database: {
-    spaces: {},
-    projects: {},
-    table: {
-      initialData: data,
-      data: data,
-      columns: tableColumns,
-      page: 0,
-      sortColumn: '',
-      sortDirection: 'asc'
+    sessionActive: false,
+    mode: 'DATABASE',
+
+    database: {
+        spaces: {},
+        projects: {},
+        table: {
+            initialData: data,
+            data: data,
+            columns: tableColumns,
+            page: 0,
+            sortColumn: '',
+            sortDirection: 'asc'
+        },
+    },
+
+    types: {
+        browser: {
+            selectedNodeId: null,
+            nodes: []
+        }
+    },
+
+    users: {
+        browser: {
+            selectedNodeId: null,
+            nodes: []
+        }
     },
-  },
-
-  types: {},
-  users: {},
-  favourites: {},
-  tools: {},
-
-  // TODO replace with generic tab description
-  openEntities: {
-    entities: [],
-    selectedEntity: null,
-  },
-  dirtyEntities: [],
-
-  loading: false,
-  // TODO generic tree
-  databaseTreeNodes: [],
-  exceptions: [],
+
+    favourites: {},
+    tools: {},
+
+    // TODO replace with generic tab description
+    openEntities: {
+        entities: [],
+        selectedEntity: null,
+    },
+    dirtyEntities: [],
+
+    loading: false,
+    // TODO generic tree
+    databaseTreeNodes: [],
+    exceptions: [],
 }
\ No newline at end of file
diff --git a/openbis_ng_ui/src/reducer/reducer.js b/openbis_ng_ui/src/reducer/reducer.js
index d68df384ba4..57109574ac1 100644
--- a/openbis_ng_ui/src/reducer/reducer.js
+++ b/openbis_ng_ui/src/reducer/reducer.js
@@ -1,171 +1,197 @@
 import merge from 'lodash/merge'
 import initialState from './initialstate.js'
 import database from './database/reducer.js'
+import users from './users/reducer.js'
+import types from './types/reducer.js'
 
 
 function replaceNode(nodes, newNode) {
-  return nodes.map( node => {
-    if (node.id === newNode.id) {
-      return newNode
-    }
-    return node
-  })
+    return nodes.map(node => {
+        if (node.id === newNode.id) {
+            return newNode
+        }
+        return node
+    })
 }
 
 function asTreeNode(entity) {
-  return {
-    id: entity.permId.permId,
-    type: entity['@type'],
-    expanded: false,
-    loading: false,
-    loaded: false,
-    children: [],
-  }
+    return {
+        id: entity.permId.permId,
+        type: entity['@type'],
+        expanded: false,
+        loading: false,
+        loaded: false,
+        children: [],
+    }
 }
 
 // reducers
 
 function loading(loading = initialState.loading, action) {
-  switch (action.type) {
-  case 'SET-SPACES': {
-    return false
-  }
-  case 'SAVE-ENTITY': {
-    return true
-  }
-  case 'SAVED-ENTITY': {
-    return false
-  }
-  case 'ERROR': {
-    return false
-  }
-  case 'LOGIN': {
-    return true
-  }
-  case 'LOGOUT': {
-    return true
-  }
-  case 'LOGOUT-DONE': {
-    return false
-  }
-  default: {
-    return loading
-  }}
+    switch (action.type) {
+        case 'SET-SPACES': {
+            return false
+        }
+        case 'SAVE-ENTITY': {
+            return true
+        }
+        case 'SAVED-ENTITY': {
+            return false
+        }
+        case 'ERROR': {
+            return false
+        }
+        case 'LOGIN': {
+            return true
+        }
+        case 'LOGOUT': {
+            return true
+        }
+        case 'LOGOUT-DONE': {
+            return false
+        }
+        default: {
+            return loading
+        }
+    }
 }
 
 
 function databaseTreeNodes(databaseTreeNodes = initialState.databaseTreeNodes, action) {
-  switch (action.type) {
-  case 'SET-SPACES': {
-    return action.spaces.map(asTreeNode)
-  }
-  case 'SET-PROJECTS': {
-    const oldNode = databaseTreeNodes.filter( node => node.id === action.spacePermId )[0]
-    const projectNodes = action.projects.map(asTreeNode)
-    const node = merge({}, oldNode, { loading: false, loaded: true, children: projectNodes })
-    return replaceNode(databaseTreeNodes, node)
-  }
-  case 'EXPAND-NODE': {
-    const loading = action.node.loaded === false
-    const node = merge({}, action.node, { expanded: true, loading: loading })
-    return replaceNode(databaseTreeNodes, node)
-  }
-  case 'COLLAPSE-NODE': {
-    const node = merge({}, action.node, { expanded: false })
-    return replaceNode(databaseTreeNodes, node)
-  }
-  default: {
-    return databaseTreeNodes
-  }}
+    switch (action.type) {
+        case 'SET-SPACES': {
+            return action.spaces.map(asTreeNode)
+        }
+        case 'SET-PROJECTS': {
+            const oldNode = databaseTreeNodes.filter(node => node.id === action.spacePermId)[0]
+            const projectNodes = action.projects.map(asTreeNode).map(project => {
+                return merge(project, {loaded: true})
+            })
+            const node = merge({}, oldNode, {loading: false, loaded: true, children: projectNodes})
+            return replaceNode(databaseTreeNodes, node)
+        }
+        case 'EXPAND-NODE': {
+            const loading = action.node.loaded === false
+            const node = merge({}, action.node, {expanded: true, loading: loading})
+            return replaceNode(databaseTreeNodes, node)
+        }
+        case 'COLLAPSE-NODE': {
+            const node = merge({}, action.node, {expanded: false})
+            return replaceNode(databaseTreeNodes, node)
+        }
+        default: {
+            return databaseTreeNodes
+        }
+    }
 }
 
 function openEntities(openEntities = initialState.openEntities, action) {
-  switch (action.type) {
-  case 'SELECT-ENTITY': {
-    const entities = openEntities.entities
-    return {
-      entities: entities.indexOf(action.entityPermId) > -1 ? entities : [].concat(entities, [action.entityPermId]),
-      selectedEntity: action.entityPermId,
+    switch (action.type) {
+        case 'SELECT-ENTITY': {
+            const entities = openEntities.entities
+            return {
+                entities: entities.indexOf(action.entityPermId) > -1 ? entities : [].concat(entities, [action.entityPermId]),
+                selectedEntity: action.entityPermId,
+            }
+        }
+        case 'CLOSE-ENTITY': {
+            const newOpenEntities = openEntities.entities.filter(e => e !== action.entityPermId)
+            if (openEntities.selectedEntity === action.entityPermId) {
+                const oldIndex = openEntities.entities.indexOf(action.entityPermId)
+                const newIndex = oldIndex === newOpenEntities.length ? oldIndex - 1 : oldIndex
+                const selectedEntity = newIndex > -1 ? newOpenEntities[newIndex] : null
+                return {
+                    entities: newOpenEntities,
+                    selectedEntity: selectedEntity,
+                }
+            } else {
+                return {
+                    entities: newOpenEntities,
+                    selectedEntity: openEntities.selectedEntity,
+                }
+            }
+        }
+        default: {
+            return openEntities
+        }
     }
-  }
-  case 'CLOSE-ENTITY': {
-    const newOpenEntities = openEntities.entities.filter(e => e !== action.entityPermId)
-    if (openEntities.selectedEntity === action.entityPermId) {
-      const oldIndex = openEntities.entities.indexOf(action.entityPermId)
-      const newIndex = oldIndex === newOpenEntities.length ? oldIndex - 1 : oldIndex
-      const selectedEntity = newIndex > -1 ? newOpenEntities[newIndex] : null  
-      return {
-        entities: newOpenEntities,
-        selectedEntity: selectedEntity,
-      }
-    } else {
-      return {
-        entities: newOpenEntities,
-        selectedEntity: openEntities.selectedEntity,
-      }
-    }  
-  }
-  default: {
-    return openEntities
-  }}
 }
 
 function dirtyEntities(dirtyEntities = initialState.dirtyEntities, action) {
-  switch (action.type) {
-  case 'SET-DIRTY': {
-    if (action.dirty) {
-      return [].concat(dirtyEntities, [action.entityPermId])
-    } else {
-      return dirtyEntities.filter(e => e !== action.entityPermId)
+    switch (action.type) {
+        case 'SET-DIRTY': {
+            if (action.dirty) {
+                return [].concat(dirtyEntities, [action.entityPermId])
+            } else {
+                return dirtyEntities.filter(e => e !== action.entityPermId)
+            }
+        }
+        case 'SAVED-ENTITY': {
+            return dirtyEntities.filter(permId => permId !== action.entity.permId.permId)
+        }
+        case 'CLOSE-ENTITY': {
+            return dirtyEntities.filter(permId => permId !== action.entityPermId)
+        }
+        default: {
+            return dirtyEntities
+        }
     }
-  }
-  case 'SAVED-ENTITY': {
-    return dirtyEntities.filter(permId => permId !== action.entity.permId.permId)
-  }
-  case 'CLOSE-ENTITY': {
-    return dirtyEntities.filter(permId => permId !== action.entityPermId)
-  }
-  default: {
-    return dirtyEntities
-  }}
 }
 
 function exceptions(exceptions = initialState.exceptions, action) {
-  switch (action.type) {
-  case 'ERROR': {
-    return [].concat(exceptions, [action.exception])
-  }
-  case 'CLOSE-ERROR': {
-    return exceptions.slice(1)
-  }
-  default: {
-    return exceptions
-  }}
+    switch (action.type) {
+        case 'ERROR': {
+            return [].concat(exceptions, [action.exception])
+        }
+        case 'CLOSE-ERROR': {
+            return exceptions.slice(1)
+        }
+        default: {
+            return exceptions
+        }
+    }
 }
 
 function sessionActive(sessionActive = initialState.sessionActive, action) {
-  switch(action.type) {
-  case 'LOGIN-DONE': {
-    return true
-  }
-  case 'LOGOUT-DONE': {
-    return false
-  }
-  default: {
-    return sessionActive
-  }}
+    switch (action.type) {
+        case 'LOGIN-DONE': {
+            return true
+        }
+        case 'LOGOUT-DONE': {
+            return false
+        }
+        default: {
+            return sessionActive
+        }
+    }
+}
+
+function mode(mode = initialState.mode, action) {
+    switch (action.type) {
+        case 'SET-MODE-DONE' : {
+            return action.mode
+        }
+        default: {
+            return mode
+        }
+    }
 }
 
 function reducer(state = initialState, action) {
-  return {
-    sessionActive: sessionActive(state.sessionActive, action),
-    database: database(state.database, action),
-    loading: loading(state.loading, action),
-    databaseTreeNodes: databaseTreeNodes(state.databaseTreeNodes, action),
-    openEntities: openEntities(state.openEntities, action),
-    dirtyEntities: dirtyEntities(state.dirtyEntities, action),
-    exceptions: exceptions(state.exceptions, action),
-  }
+    let newMode = mode(state.mode, action);
+
+    return {
+        sessionActive: sessionActive(state.sessionActive, action),
+        mode: newMode,
+        database: database(state.database, action),
+        users: newMode === 'USERS' ? users(state.users, action) : state.users,
+        types: newMode === 'TYPES' ? types(state.types, action) : state.types,
+        loading: loading(state.loading, action),
+        databaseTreeNodes: databaseTreeNodes(state.databaseTreeNodes, action),
+        openEntities: openEntities(state.openEntities, action),
+        dirtyEntities: dirtyEntities(state.dirtyEntities, action),
+        exceptions: exceptions(state.exceptions, action),
+    }
 }
 
 export default reducer
diff --git a/openbis_ng_ui/src/reducer/sagas.js b/openbis_ng_ui/src/reducer/sagas.js
index 1b2ccb6486d..0c6f21e3550 100644
--- a/openbis_ng_ui/src/reducer/sagas.js
+++ b/openbis_ng_ui/src/reducer/sagas.js
@@ -1,78 +1,119 @@
-import { put, takeEvery, call } from 'redux-saga/effects'
+import {put, takeEvery, call} from 'redux-saga/effects'
 import openbis from '../services/openbis'
 import actions from './actions'
 
 // TODO split sagas
 
 function* init() {
-  // TODO we want to check if there is an active session here to avoid the login
+    // TODO we want to check if there is an active session here to avoid the login
 }
 
 function* loginDone() {
-  try {
-    const result = yield call(openbis.getSpaces)
-    yield put(actions.setSpaces(result.getObjects()))
-  } catch(exception) {
-    yield put(actions.error(exception))  
-  }
+    try {
+        const result = yield call(openbis.getSpaces)
+        yield put(actions.setSpaces(result.getObjects()))
+    } catch (exception) {
+        yield put(actions.error(exception))
+    }
 }
 
 function* logout() {
-  try {
-    console.log('logout')
-    const result = yield call(openbis.logout)
-    yield put(actions.logoutDone())
-  } catch(exception) {
-    yield put(actions.error(exception))  
-  }
+    try {
+        console.log('logout')
+        const result = yield call(openbis.logout)
+        yield put(actions.logoutDone())
+    } catch (exception) {
+        yield put(actions.error(exception))
+    }
 }
 
 function* selectSpace(action) {
-  yield put(actions.selectEntity(action.spaces[0].permId.permId))
+    yield put(actions.selectEntity(action.spaces[0].permId.permId))
 }
 
 function* saveEntity(action) {
-  try {
-    yield openbis.updateSpace(action.entity.permId, action.entity.description)
-    const result = yield call(openbis.getSpaces)
-    const spaces = result.getObjects()
-    const space = spaces.filter(space => space.permId.permId === action.entity.permId.permId)[0]
-    yield put(actions.savedEntity(space))
-  } catch(exception) {
-    yield put(actions.error(exception))  
-  }
+    try {
+        yield openbis.updateSpace(action.entity.permId, action.entity.description)
+        const result = yield call(openbis.getSpaces)
+        const spaces = result.getObjects()
+        const space = spaces.filter(space => space.permId.permId === action.entity.permId.permId)[0]
+        yield put(actions.savedEntity(space))
+    } catch (exception) {
+        yield put(actions.error(exception))
+    }
 }
 
 function* expandNode(action) {
-  try {
-    const node = action.node
-    if (node.loaded === false) {
-      if (node.type === 'as.dto.space.Space') {
-        const result = yield openbis.searchProjects(node.id)
-        const projects = result.getObjects()
-        yield put(actions.setProjects(projects, node.id))
-      }
+    try {
+        const node = action.node
+        if (node.loaded === false) {
+            if (node.type === 'as.dto.space.Space') {
+                const result = yield openbis.searchProjects(node.id)
+                const projects = result.getObjects()
+                yield put(actions.setProjects(projects, node.id))
+            }
+        }
+    } catch (exception) {
+        yield put(actions.error(exception))
     }
-  } catch(exception) {
-    yield put(actions.error(exception))  
-  }
 }
 
 export function* login(action) {
-  try {
-    const result = yield openbis.login(action.username, action.password)
-    yield put(actions.loginDone())
-  } catch(exception) {
-    yield put(actions.error(exception))
-  }
+    try {
+        const result = yield openbis.login(action.username, action.password)
+        yield put(actions.loginDone())
+    } catch (exception) {
+        yield put(actions.error(exception))
+    }
+}
+
+function* setMode(action) {
+    try {
+        switch (action.mode) {
+            case 'USERS': {
+                let users = yield call(openbis.getUsers)
+                let groups = yield call(openbis.getGroups)
+                yield put(actions.setModeDone(action.mode, {
+                    users: users.getObjects(),
+                    groups: groups.getObjects()
+                }))
+                break
+            }
+            case 'TYPES': {
+                let objectTypes = yield call(openbis.getObjectTypes)
+                let collectionTypes = yield call(openbis.getCollectionTypes)
+                let dataSetTypes = yield call(openbis.getDataSetTypes)
+                let materialTypes = yield call(openbis.getMaterialTypes)
+                yield put(actions.setModeDone(action.mode, {
+                    objectTypes: objectTypes.getObjects(),
+                    collectionTypes: collectionTypes.getObjects(),
+                    dataSetTypes: dataSetTypes.getObjects(),
+                    materialTypes: materialTypes.getObjects(),
+                }))
+                break
+            }
+            default: {
+                yield put(actions.setModeDone(action.mode, {}))
+                break
+            }
+        }
+    } catch (exception) {
+        yield put(actions.error(exception))
+    }
+}
+
+function* log(action) {
+    console.log('action', action);
 }
 
 export function* watchActions() {
-  yield takeEvery('INIT', init)
-  yield takeEvery('LOGIN', login)
-  yield takeEvery('LOGIN-DONE', loginDone)
-  yield takeEvery('LOGOUT', logout)
-  yield takeEvery('SAVE-ENTITY', saveEntity)
-  yield takeEvery('SET-SPACES', selectSpace)
-  yield takeEvery('EXPAND-NODE', expandNode)
+    yield takeEvery('*', log)
+    yield takeEvery('INIT', init)
+    yield takeEvery('LOGIN', login)
+    yield takeEvery('LOGIN-DONE', loginDone)
+    yield takeEvery('LOGOUT', logout)
+    yield takeEvery('SAVE-ENTITY', saveEntity)
+    yield takeEvery('SET-SPACES', selectSpace)
+    yield takeEvery('EXPAND-NODE', expandNode)
+    yield takeEvery('SET-MODE', setMode)
 }
diff --git a/openbis_ng_ui/src/reducer/types/reducer.js b/openbis_ng_ui/src/reducer/types/reducer.js
new file mode 100644
index 00000000000..d8b8acce886
--- /dev/null
+++ b/openbis_ng_ui/src/reducer/types/reducer.js
@@ -0,0 +1,109 @@
+import initialState from '../initialstate.js'
+import _ from 'lodash'
+
+export default function types(types = initialState.types, action) {
+    return {
+        browser: browser(types.browser, action),
+    }
+}
+
+function browser(browser = initialState.types.browser, action) {
+    switch (action.type) {
+        case 'SET-MODE-DONE':
+            return browser_SetModeDone(browser, action)
+        case 'EXPAND-NODE':
+            return browser_ExpandNode(browser, action)
+        case 'COLLAPSE-NODE':
+            return browser_CollapseNode(browser, action)
+        default:
+            return browser
+    }
+}
+
+function browser_SetModeDone(browser, action) {
+    return {
+        selectedNodeId: browser.selectedNodeId,
+        nodes: [
+            browser_SetModeDone_TypeNodes('Object Types', action.data.objectTypes),
+            browser_SetModeDone_TypeNodes('Collection Types', action.data.collectionTypes),
+            browser_SetModeDone_TypeNodes('Data Set Types', action.data.dataSetTypes),
+            browser_SetModeDone_TypeNodes('Material Types', action.data.materialTypes)
+        ]
+    }
+}
+
+function browser_SetModeDone_TypeNodes(groupId, types) {
+    let typeNodes = []
+
+    types.forEach(type => {
+        typeNodes.push({
+            id: type.getPermId().getPermId(),
+            expanded: false,
+            loading: false,
+            loaded: true,
+            children: []
+        })
+    })
+
+    _sortById(typeNodes)
+
+    return {
+        id: groupId,
+        expanded: false,
+        loading: false,
+        loaded: true,
+        children: typeNodes
+    }
+}
+
+function browser_ExpandNode(browser, action) {
+    let newBrowser = _.cloneDeep(browser)
+    _visitNodes(newBrowser.nodes, node => {
+        if (node.id === action.node.id) {
+            node.expanded = true
+        }
+    })
+    return newBrowser
+}
+
+function browser_CollapseNode(browser, action) {
+    let newBrowser = _.cloneDeep(browser)
+    _visitNodes(newBrowser.nodes, node => {
+        if (node.id === action.node.id) {
+            node.expanded = false
+        }
+    })
+    return newBrowser
+}
+
+const _visitNodes = (nodes, visitor) => {
+    let toVisit = []
+    let visited = {}
+
+    toVisit.push(...nodes)
+
+    while (toVisit.length > 0) {
+        let node = toVisit.shift()
+
+        if (!visited[node.id]) {
+            visited[node.id] = true
+            let result = visitor(node)
+            if (result) {
+                return result
+            }
+        }
+
+        if (node.children !== undefined) {
+            node.children.forEach((child) => {
+                toVisit.push(child)
+            })
+        }
+    }
+}
+
+
+const _sortById = (arr) => {
+    arr.sort((i1, i2) => {
+        return i1.id.localeCompare(i2.id)
+    })
+}
\ No newline at end of file
diff --git a/openbis_ng_ui/src/reducer/users/reducer.js b/openbis_ng_ui/src/reducer/users/reducer.js
new file mode 100644
index 00000000000..436a6a780ea
--- /dev/null
+++ b/openbis_ng_ui/src/reducer/users/reducer.js
@@ -0,0 +1,161 @@
+import initialState from '../initialstate.js'
+import _ from 'lodash'
+
+export default function users(users = initialState.users, action) {
+    return {
+        browser: browser(users.browser, action),
+    }
+}
+
+function browser(browser = initialState.users.browser, action) {
+    switch (action.type) {
+        case 'SET-MODE-DONE':
+            return browser_SetModeDone(browser, action)
+        case 'EXPAND-NODE':
+            return browser_ExpandNode(browser, action)
+        case 'COLLAPSE-NODE':
+            return browser_CollapseNode(browser, action)
+        default:
+            return browser
+    }
+}
+
+function browser_SetModeDone(browser, action) {
+    return {
+        selectedNodeId: browser.selectedNodeId,
+        nodes: [browser_SetModeDone_UserNodes(action.data.users, action.data.groups), browser_SetModeDone_GroupNodes(action.data.groups)]
+    }
+}
+
+function browser_SetModeDone_UserNodes(users, groups) {
+    let userGroupsMap = {}
+    let userNodes = []
+
+    groups.forEach(group => {
+        group.getUsers().forEach(user => {
+            let userGroups = userGroupsMap[user.getPermId().getPermId()] || []
+            userGroups.push(group)
+            userGroupsMap[user.getPermId().getPermId()] = userGroups
+        })
+    })
+
+    users.forEach(user => {
+        let userGroups = userGroupsMap[user.getPermId().getPermId()] || []
+        let groupNodes = []
+
+        userGroups.forEach(group => {
+            groupNodes.push({
+                id: group.getPermId().getPermId(),
+                expanded: false,
+                loading: false,
+                loaded: true,
+                children: []
+            })
+        })
+
+        _sortById(groupNodes)
+
+        userNodes.push({
+            id: user.getPermId().getPermId(),
+            expanded: false,
+            loading: false,
+            loaded: true,
+            children: groupNodes
+        })
+    })
+
+    _sortById(userNodes)
+
+    return {
+        id: 'Users',
+        expanded: false,
+        loading: false,
+        loaded: true,
+        children: userNodes
+    }
+}
+
+function browser_SetModeDone_GroupNodes(groups) {
+    let groupNodes = []
+
+    groups.forEach(group => {
+        let userNodes = []
+        group.getUsers().forEach(user => {
+            userNodes.push({
+                id: user.getPermId().getPermId(),
+                expanded: false,
+                loading: false,
+                loaded: true,
+                children: []
+            })
+        })
+
+        groupNodes.push({
+            id: group.getPermId().getPermId(),
+            expanded: false,
+            loading: false,
+            loaded: true,
+            children: userNodes
+        })
+    })
+
+    return {
+        id: 'Groups',
+        expanded: false,
+        loading: false,
+        loaded: true,
+        children: groupNodes
+    }
+}
+
+function browser_ExpandNode(browser, action) {
+    let newBrowser = _.cloneDeep(browser)
+    _visitNodes(newBrowser.nodes, node => {
+        if (node.id === action.node.id) {
+            node.expanded = true
+        }
+    })
+    return newBrowser
+}
+
+function browser_CollapseNode(browser, action) {
+    let newBrowser = _.cloneDeep(browser)
+    _visitNodes(newBrowser.nodes, node => {
+        if (node.id === action.node.id) {
+            node.expanded = false
+        }
+    })
+    return newBrowser
+}
+
+const _visitNodes = (nodes, visitor) => {
+    let toVisit = []
+    let visited = {}
+
+    toVisit.push(...nodes)
+
+    while (toVisit.length > 0) {
+        let node = toVisit.shift()
+
+        if (!visited[node.id]) {
+            visited[node.id] = true
+            let result = visitor(node)
+            if (result) {
+                return result
+            }
+        }
+
+        if (node.children !== undefined) {
+            node.children.forEach((child) => {
+                toVisit.push(child)
+            })
+        }
+    }
+}
+
+
+const _sortById = (arr) => {
+    arr.sort((i1, i2) => {
+        return i1.id.localeCompare(i2.id)
+    })
+}
\ No newline at end of file
diff --git a/openbis_ng_ui/src/services/openbis.js b/openbis_ng_ui/src/services/openbis.js
index 4362afe4850..641105f377a 100644
--- a/openbis_ng_ui/src/services/openbis.js
+++ b/openbis_ng_ui/src/services/openbis.js
@@ -1,72 +1,145 @@
-import { store } from '../index.js'
+import {store} from '../index.js'
 
 
 let v3 = null
 /* eslint-disable-next-line no-undef */
-requirejs([ 'openbis' ], openbis => {
-  v3 = new openbis()
-  store.dispatch({type: 'INIT'})
+requirejs(['openbis'], openbis => {
+    v3 = new openbis()
 })
 
 function login(user, password) {
-  return new Promise( (resolve, reject) => {
-    v3.login(user, password).done(resolve).fail(() => {
-      reject({ message: 'Login failed' })
+    return new Promise((resolve, reject) => {
+        v3.login(user, password).done(resolve).fail(() => {
+            reject({message: 'Login failed'})
+        })
     })
-  })
 }
 
 function logout() {
-  return new Promise( (resolve, reject) => {
-    v3.logout().done(resolve).fail(reject)
-  })
+    return new Promise((resolve, reject) => {
+        v3.logout().done(resolve).fail(reject)
+    })
 }
 
 function getSpaces() {
-  return new Promise( (resolve, reject) => {
-    /* eslint-disable-next-line no-undef */
-    requirejs(
-      ['as/dto/space/search/SpaceSearchCriteria', 'as/dto/space/fetchoptions/SpaceFetchOptions' ], 
-      (SpaceSearchCriteria, SpaceFetchOptions) => 
-        v3.searchSpaces(new SpaceSearchCriteria(), new SpaceFetchOptions()).done(resolve).fail(reject)
-    )
-  })
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/space/search/SpaceSearchCriteria', 'as/dto/space/fetchoptions/SpaceFetchOptions'],
+            (SpaceSearchCriteria, SpaceFetchOptions) =>
+                v3.searchSpaces(new SpaceSearchCriteria(), new SpaceFetchOptions()).done(resolve).fail(reject)
+        )
+    })
+}
+
+function getUsers() {
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/person/search/PersonSearchCriteria', 'as/dto/person/fetchoptions/PersonFetchOptions'],
+            (PersonSearchCriteria, PersonFetchOptions) => {
+                v3.searchPersons(new PersonSearchCriteria(), new PersonFetchOptions()).done(resolve).fail(reject)
+            })
+    })
+}
+
+function getGroups() {
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/authorizationgroup/search/AuthorizationGroupSearchCriteria', 'as/dto/authorizationgroup/fetchoptions/AuthorizationGroupFetchOptions'],
+            (AuthorizationGroupSearchCriteria, AuthorizationGroupFetchOptions) => {
+                let fo = new AuthorizationGroupFetchOptions()
+                fo.withUsers()
+                v3.searchAuthorizationGroups(new AuthorizationGroupSearchCriteria(), fo).done(resolve).fail(reject)
+            })
+    })
+}
+
+function getObjectTypes() {
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/sample/search/SampleTypeSearchCriteria', 'as/dto/sample/fetchoptions/SampleTypeFetchOptions'],
+            (SampleTypeSearchCriteria, SampleTypeFetchOptions) => {
+                v3.searchSampleTypes(new SampleTypeSearchCriteria(), new SampleTypeFetchOptions()).done(resolve).fail(reject)
+            })
+    })
+}
+
+function getCollectionTypes() {
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/experiment/search/ExperimentTypeSearchCriteria', 'as/dto/experiment/fetchoptions/ExperimentTypeFetchOptions'],
+            (ExperimentTypeSearchCriteria, ExperimentTypeFetchOptions) => {
+                v3.searchExperimentTypes(new ExperimentTypeSearchCriteria(), new ExperimentTypeFetchOptions()).done(resolve).fail(reject)
+            })
+    })
+}
+
+function getDataSetTypes() {
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/dataset/search/DataSetTypeSearchCriteria', 'as/dto/dataset/fetchoptions/DataSetTypeFetchOptions'],
+            (DataSetTypeSearchCriteria, DataSetTypeFetchOptions) => {
+                v3.searchDataSetTypes(new DataSetTypeSearchCriteria(), new DataSetTypeFetchOptions()).done(resolve).fail(reject)
+            })
+    })
+}
+
+function getMaterialTypes() {
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/material/search/MaterialTypeSearchCriteria', 'as/dto/material/fetchoptions/MaterialTypeFetchOptions'],
+            (MaterialTypeSearchCriteria, MaterialTypeFetchOptions) => {
+                v3.searchMaterialTypes(new MaterialTypeSearchCriteria(), new MaterialTypeFetchOptions()).done(resolve).fail(reject)
+            })
+    })
 }
 
 function updateSpace(permId, description) {
-  return new Promise( (resolve, reject) => {
-    /* eslint-disable-next-line no-undef */
-    requirejs(
-      ['as/dto/space/update/SpaceUpdate'], 
-      (SpaceUpdate) => {
-        let spaceUpdate = new SpaceUpdate()
-        spaceUpdate.setSpaceId(permId)
-        spaceUpdate.setDescription(description)
-        v3.updateSpaces([spaceUpdate]).done(resolve).fail(reject)
-      }
-    )
-  })
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/space/update/SpaceUpdate'],
+            (SpaceUpdate) => {
+                let spaceUpdate = new SpaceUpdate()
+                spaceUpdate.setSpaceId(permId)
+                spaceUpdate.setDescription(description)
+                v3.updateSpaces([spaceUpdate]).done(resolve).fail(reject)
+            }
+        )
+    })
 }
 
 function searchProjects(spacePermId) {
-  return new Promise( (resolve, reject) => {
-    /* eslint-disable-next-line no-undef */
-    requirejs(
-      ['as/dto/project/search/ProjectSearchCriteria', 'as/dto/project/fetchoptions/ProjectFetchOptions'], 
-      (ProjectSearchCriteria, ProjectFetchOptions) => {
-        let searchCriteria = new ProjectSearchCriteria()
-        searchCriteria.withSpace().withPermId().thatEquals(spacePermId)
-        let fetchOptions = new ProjectFetchOptions()
-        v3.searchProjects(searchCriteria, fetchOptions).done(resolve).fail(reject)
-      }
-    )
-  })
+    return new Promise((resolve, reject) => {
+        /* eslint-disable-next-line no-undef */
+        requirejs(
+            ['as/dto/project/search/ProjectSearchCriteria', 'as/dto/project/fetchoptions/ProjectFetchOptions'],
+            (ProjectSearchCriteria, ProjectFetchOptions) => {
+                let searchCriteria = new ProjectSearchCriteria()
+                searchCriteria.withSpace().withPermId().thatEquals(spacePermId)
+                let fetchOptions = new ProjectFetchOptions()
+                v3.searchProjects(searchCriteria, fetchOptions).done(resolve).fail(reject)
+            }
+        )
+    })
 }
 
 export default {
-  login: login,
-  logout: logout,
-  getSpaces: getSpaces,
-  updateSpace: updateSpace,
-  searchProjects: searchProjects,
+    login: login,
+    logout: logout,
+    getSpaces: getSpaces,
+    getUsers: getUsers,
+    getGroups: getGroups,
+    getObjectTypes: getObjectTypes,
+    getCollectionTypes: getCollectionTypes,
+    getDataSetTypes: getDataSetTypes,
+    getMaterialTypes: getMaterialTypes,
+    updateSpace: updateSpace,
+    searchProjects: searchProjects,
 }
-- 
GitLab