thingsboard-memoizeit

Details

diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index 8f87048..ea86cd6 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -22,8 +22,9 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
     .name;
 
 /*@ngInject*/
-function UserService($http, $q, $rootScope, store, jwtHelper, $translate) {
+function UserService($http, $q, $rootScope, store, jwtHelper, $translate, $state) {
     var currentUser = null,
+        currentUserDetails = null,
         userLoaded = false;
 
     var refreshTokenQueue = [];
@@ -47,6 +48,8 @@ function UserService($http, $q, $rootScope, store, jwtHelper, $translate) {
         refreshJwtToken: refreshJwtToken,
         refreshTokenPending: refreshTokenPending,
         updateAuthorizationHeader: updateAuthorizationHeader,
+        gotoDefaultPlace: gotoDefaultPlace,
+        forceDefaultPlace: forceDefaultPlace,
         logout: logout
     }
 
@@ -86,6 +89,7 @@ function UserService($http, $q, $rootScope, store, jwtHelper, $translate) {
 
     function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) {
         currentUser = null;
+        currentUserDetails = null;
         if (!jwtToken) {
             clearTokenData();
             if (notify) {
@@ -222,7 +226,25 @@ function UserService($http, $q, $rootScope, store, jwtHelper, $translate) {
                 } else if (currentUser) {
                     currentUser.authority = "ANONYMOUS";
                 }
-                deferred.resolve();
+                if (currentUser.userId) {
+                    getUser(currentUser.userId).then(
+                        function success(user) {
+                            currentUserDetails = user;
+                            if (currentUserDetails.additionalInfo &&
+                                currentUserDetails.additionalInfo.defaultDashboardFullscreen) {
+                                $rootScope.forceFullscreen = currentUserDetails.additionalInfo.defaultDashboardFullscreen === true;
+                            } else {
+                                $rootScope.forceFullscreen = false;
+                            }
+                            deferred.resolve();
+                        },
+                        function fail() {
+                            deferred.reject();
+                        }
+                    )
+                } else {
+                    deferred.reject();
+                }
             }, function fail() {
                 deferred.reject();
             });
@@ -331,4 +353,40 @@ function UserService($http, $q, $rootScope, store, jwtHelper, $translate) {
         return deferred.promise;
     }
 
+    function forceDefaultPlace(to, params) {
+        if (currentUser && isAuthenticated()) {
+            if (currentUser.authority === 'CUSTOMER_USER') {
+                if (currentUserDetails &&
+                    currentUserDetails.additionalInfo &&
+                    currentUserDetails.additionalInfo.defaultDashboardId) {
+                    if ($rootScope.forceFullscreen) {
+                        if (to.name === 'home.profile') {
+                            return false;
+                        } else if (to.name !== 'home.dashboards.dashboard' && params.dashboardId !== currentUserDetails.additionalInfo.defaultDashboardId) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    function gotoDefaultPlace(params) {
+        if (currentUser && isAuthenticated()) {
+            var place = 'home.links';
+            if (currentUser.authority === 'CUSTOMER_USER') {
+                if (currentUserDetails &&
+                    currentUserDetails.additionalInfo &&
+                    currentUserDetails.additionalInfo.defaultDashboardId) {
+                    place = 'home.dashboards.dashboard';
+                    params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
+                }
+            }
+            $state.go(place, params);
+        } else {
+            $state.go('login', params);
+        }
+    }
+
 }
diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js
index bed6b0d..bde3525 100644
--- a/ui/src/app/app.config.js
+++ b/ui/src/app/app.config.js
@@ -38,7 +38,7 @@ export default function AppConfig($provide,
 
     injectTapEventPlugin();
     $locationProvider.html5Mode(true);
-    $urlRouterProvider.otherwise('/home');
+    //$urlRouterProvider.otherwise('/home');
     storeProvider.setCaching(false);
 
     $translateProvider.useSanitizeValueStrategy('sanitize');

ui/src/app/app.run.js 55(+34 -21)

diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js
index 057b413..ae932ec 100644
--- a/ui/src/app/app.run.js
+++ b/ui/src/app/app.run.js
@@ -36,6 +36,8 @@ export default function AppRun($rootScope, $window, $log, $state, $mdDialog, $fi
 
     initWatchers();
 
+    checkCurrentState();
+
     function initWatchers() {
         $rootScope.unauthenticatedHandle = $rootScope.$on('unauthenticated', function (event, doLogout) {
             if (doLogout) {
@@ -56,22 +58,27 @@ export default function AppRun($rootScope, $window, $log, $state, $mdDialog, $fi
         $rootScope.stateChangeStartHandle = $rootScope.$on('$stateChangeStart', function (evt, to, params) {
             if (userService.isUserLoaded() === true) {
                 if (userService.isAuthenticated()) {
-                    var authority = userService.getAuthority();
-                    if (to.module === 'public') {
-                        evt.preventDefault();
-                        $state.go('home', params);
-                    } else if (angular.isDefined(to.auth) &&
-                        to.auth.indexOf(authority) === -1) {
+                    if (userService.forceDefaultPlace(to, params)) {
                         evt.preventDefault();
-                        showForbiddenDialog();
-                    } else if (to.redirectTo) {
-                        evt.preventDefault();
-                        $state.go(to.redirectTo, params)
+                        gotoDefaultPlace(params);
+                    } else {
+                        var authority = userService.getAuthority();
+                        if (to.module === 'public') {
+                            evt.preventDefault();
+                            gotoDefaultPlace(params);
+                        } else if (angular.isDefined(to.auth) &&
+                            to.auth.indexOf(authority) === -1) {
+                            evt.preventDefault();
+                            showForbiddenDialog();
+                        } else if (to.redirectTo) {
+                            evt.preventDefault();
+                            $state.go(to.redirectTo, params)
+                        }
                     }
                 } else {
                     if (to.module === 'private') {
                         evt.preventDefault();
-                        if (to.url === '/home') {
+                        if (to.url === '/home' || to.url === '/') {
                             $state.go('login', params);
                         } else {
                             showUnauthorizedDialog();
@@ -80,6 +87,9 @@ export default function AppRun($rootScope, $window, $log, $state, $mdDialog, $fi
                 }
             } else {
                 evt.preventDefault();
+                if ($rootScope.userLoadedHandle) {
+                    $rootScope.userLoadedHandle();
+                }
                 $rootScope.userLoadedHandle = $rootScope.$on('userLoaded', function () {
                     $rootScope.userLoadedHandle();
                     $state.go(to.name, params);
@@ -102,23 +112,26 @@ export default function AppRun($rootScope, $window, $log, $state, $mdDialog, $fi
 
     function checkCurrentState() {
         if (userService.isUserLoaded() === true) {
-            var module = $state.$current.module;
             if (userService.isAuthenticated()) {
-                if ($state.$current.module === 'public') {
-                    $state.go('home');
-                }
+                gotoDefaultPlace();
             } else {
-                if (angular.isUndefined(module) || !module) {
-                    //$state.go('login');
-                } else if ($state.$current.module === 'private') {
-                    showUnauthorizedDialog();
-                }
+                $state.go('login');
             }
         } else {
-            showUnauthorizedDialog();
+            if ($rootScope.userLoadedHandle) {
+                $rootScope.userLoadedHandle();
+            }
+            $rootScope.userLoadedHandle = $rootScope.$on('userLoaded', function () {
+                $rootScope.userLoadedHandle();
+                checkCurrentState();
+            });
         }
     }
 
+    function gotoDefaultPlace(params) {
+        userService.gotoDefaultPlace(params);
+    }
+
     function showUnauthorizedDialog() {
         if (unauthorizedDialog === null) {
             $translate(['access.unauthorized-access',
diff --git a/ui/src/app/components/dashboard-select.directive.js b/ui/src/app/components/dashboard-select.directive.js
index 0f76258..7569698 100644
--- a/ui/src/app/components/dashboard-select.directive.js
+++ b/ui/src/app/components/dashboard-select.directive.js
@@ -40,27 +40,19 @@ function DashboardSelect($compile, $templateCache, $q, dashboardService, userSer
         scope.dashboard = null;
         scope.dashboardSearchText = '';
 
-        scope.dashboardFetchFunction = dashboardService.getTenantDashboards;
-        if (angular.isDefined(scope.dashboardsScope)) {
-            if (scope.dashboardsScope === 'customer') {
-                scope.dashboardFetchFunction = dashboardService.getCustomerDashboards;
-            } else {
-                scope.dashboardFetchFunction = dashboardService.getTenantDashboards;
-            }
-        } else {
-            if (userService.getAuthority() === 'TENANT_ADMIN') {
-                scope.dashboardFetchFunction = dashboardService.getTenantDashboards;
-            } else if (userService.getAuthority() === 'CUSTOMER_USER') {
-                scope.dashboardFetchFunction = dashboardService.getCustomerDashboards;
-            }
-        }
-
         scope.fetchDashboards = function(searchText) {
             var pageLink = {limit: 10, textSearch: searchText};
 
             var deferred = $q.defer();
 
-            scope.dashboardFetchFunction(pageLink).then(function success(result) {
+            var promise;
+            if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
+                promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink);
+            } else {
+                promise = dashboardService.getTenantDashboards(pageLink);
+            }
+
+            promise.then(function success(result) {
                 deferred.resolve(result.data);
             }, function fail() {
                 deferred.reject();
@@ -79,6 +71,8 @@ function DashboardSelect($compile, $templateCache, $q, dashboardService, userSer
         ngModelCtrl.$render = function () {
             if (ngModelCtrl.$viewValue) {
                 scope.dashboard = ngModelCtrl.$viewValue;
+            } else {
+                scope.dashboard = null;
             }
         }
 
@@ -106,6 +100,7 @@ function DashboardSelect($compile, $templateCache, $q, dashboardService, userSer
         link: linker,
         scope: {
             dashboardsScope: '@',
+            customerId: '=',
             theForm: '=?',
             tbRequired: '=?',
             selectFirstDashboard: '='
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index 1744a53..efc897e 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -108,10 +108,10 @@ section.tb-dashboard-toolbar {
       }
     }
     .md-fab-toolbar-wrapper {
-      height: 40px;
+      height: 50px;
       md-toolbar {
-        min-height: 36px;
-        height: 36px;
+        min-height: 46px;
+        height: 46px;
         md-fab-actions {
           font-size: 16px;
           margin-top: 0px;
@@ -126,7 +126,7 @@ section.tb-dashboard-toolbar {
 
 .tb-dashboard-container {
    &.tb-dashboard-toolbar-opened {
-     margin-top: 40px;
+     margin-top: 50px;
      @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
    }
    &.tb-dashboard-toolbar-closed {
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 7390485..a9a3625 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -15,8 +15,8 @@
     limitations under the License.
 
 -->
-<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode" expand-button-id="dashboard-expand-button"
-            hide-expand-button="vm.widgetEditMode || vm.iframeMode" expand-tooltip-direction="bottom"
+<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-button-id="dashboard-expand-button"
+            hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom"
             ng-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
                     'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
                     'background-repeat': 'no-repeat',
@@ -47,6 +47,8 @@
                                aria-label="{{ 'fullscreen.fullscreen' | translate }}"
                                class="md-icon-button">
                     </md-button>
+                    <tb-user-menu ng-show="forceFullscreen" display-user-info="true">
+                    </tb-user-menu>
                     <tb-timewindow direction="left" tooltip-direction="bottom" aggregation ng-model="vm.dashboardConfiguration.timewindow">
                     </tb-timewindow>
                     <tb-aliases-device-select ng-show="!vm.isEdit"
diff --git a/ui/src/app/layout/home.controller.js b/ui/src/app/layout/home.controller.js
index bf113e6..290cacf 100644
--- a/ui/src/app/layout/home.controller.js
+++ b/ui/src/app/layout/home.controller.js
@@ -26,9 +26,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
 
 /*@ngInject*/
 export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
-                                       $log, $mdMedia, $animate, $timeout, $translate) {
-
-    var dashboardUser = userService.getCurrentUser();
+                                       $log, $mdMedia, $animate, $timeout) {
 
     var siteSideNav = $('.tb-site-sidenav', $element);
 
@@ -48,15 +46,11 @@ export default function HomeController(loginService, userService, deviceService,
     vm.isShowSidenav = false;
     vm.isLockSidenav = false;
 
-    vm.authorityName = authorityName;
     vm.displaySearchMode = displaySearchMode;
-    vm.logout = logout;
-    vm.openProfile = openProfile;
     vm.openSidenav = openSidenav;
     vm.searchTextUpdated = searchTextUpdated;
     vm.sidenavClicked = sidenavClicked;
     vm.toggleFullscreen = toggleFullscreen;
-    vm.userDisplayName = userDisplayName;
 
     $scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
         if (angular.isDefined(to.data.searchEnabled)) {
@@ -106,50 +100,6 @@ export default function HomeController(loginService, userService, deviceService,
         $scope.$broadcast('searchTextUpdated');
     }
 
-    function authorityName() {
-        var name = "user.anonymous";
-        if (dashboardUser) {
-            var authority = dashboardUser.authority;
-            if (authority === 'SYS_ADMIN') {
-                name = 'user.sys-admin';
-            } else if (authority === 'TENANT_ADMIN') {
-                name = 'user.tenant-admin';
-            } else if (authority === 'CUSTOMER_USER') {
-                name = 'user.customer';
-            }
-        }
-        return $translate.instant(name);
-    }
-
-    function userDisplayName() {
-        var name = "";
-        if (dashboardUser) {
-            if ((dashboardUser.firstName && dashboardUser.firstName.length > 0) ||
-                (dashboardUser.lastName && dashboardUser.lastName.length > 0)) {
-                if (dashboardUser.firstName) {
-                    name += dashboardUser.firstName;
-                }
-                if (dashboardUser.lastName) {
-                    if (name.length > 0) {
-                        name += " ";
-                    }
-                    name += dashboardUser.lastName;
-                }
-            } else {
-                name = dashboardUser.email;
-            }
-        }
-        return name;
-    }
-
-    function openProfile() {
-        $state.go('home.profile');
-    }
-
-    function logout() {
-        userService.logout();
-    }
-
     function openSidenav() {
         vm.isShowSidenav = true;
     }
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index dcbd662..edcdbc8 100644
--- a/ui/src/app/layout/home.scss
+++ b/ui/src/app/layout/home.scss
@@ -53,37 +53,11 @@ md-sidenav.tb-site-sidenav {
   width: 250px;
 }
 
-md-icon.tb-mini-avatar {
-  margin: auto 8px;
-  font-size: 36px;
-  height: 36px;
-  width: 36px;
-}
-
 md-icon.tb-logo-title {
   height: 36px;
   width: 200px;
 }
 
-div.tb-user-info {
-  line-height: 1.5;
-  span {
-    text-transform: none;
-    text-align: left;
-  }
-  span.tb-user-display-name {
-    font-size: 0.800rem;
-    font-weight: 300;
-    letter-spacing: 0.008em;
-  }
-  span.tb-user-authority {
-    font-size: 0.800rem;
-    font-weight: 300;
-    letter-spacing: 0.005em;
-    opacity: 0.8;
-  }
-}
-
 .tb-nav-header {
   flex-shrink: 0;
   z-index: 2;
diff --git a/ui/src/app/layout/home.tpl.html b/ui/src/app/layout/home.tpl.html
index 488e750..1f45ba7 100644
--- a/ui/src/app/layout/home.tpl.html
+++ b/ui/src/app/layout/home.tpl.html
@@ -16,6 +16,7 @@
 
 -->
   <md-sidenav class="tb-site-sidenav md-sidenav-left md-whiteframe-z2"
+			  ng-show="!forceFullscreen"
 			  hide-print=""
       		  md-component-id="left"
 			  aria-label="Toggle Nav"
@@ -59,33 +60,10 @@
           	  </md-button>
 		      <md-button ng-show="!vm.displaySearchMode()" hide-xs hide-sm class="md-icon-button" ng-click="vm.toggleFullscreen()" aria-label="{{ 'fullscreen.toggle' | translate }}">
 	    			<ng-md-icon icon="{{vm.Fullscreen.isEnabled() ? 'fullscreen_exit' : 'fullscreen'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
-		      </md-button>          	  
-	      	  <div hide-xs hide-sm ng-show="!vm.displaySearchMode()" class="tb-user-info" layout="row">
-  		        <md-icon aria-label="{{ 'home.avatar' | translate }}" class="material-icons tb-mini-avatar">account_circle</md-icon>
-  		        <div layout="column">
-	             	<span class="tb-user-display-name">{{vm.userDisplayName()}}</span>
-	             	<span class="tb-user-authority">{{vm.authorityName()}}</span>
-             	</div>
-              </div>
- 		      <md-menu md-position-mode="target-right target">
-			      <md-button class="md-icon-button"  aria-label="{{ 'home.open-user-menu' | translate }}" ng-click="$mdOpenMenu($event)">
-			      	<md-icon md-menu-origin aria-label="{{ 'home.open-user-menu' | translate }}" class="material-icons">more_vert</md-icon>
-			      </md-button>
-			      <md-menu-content width="4">
-			        <md-menu-item>
-			          <md-button ng-click="vm.openProfile()">
-			          	<md-icon md-menu-align-target aria-label="{{ 'home.profile' | translate }}" class="material-icons">account_circle</md-icon>
-						<span translate>home.profile</span>
-			          </md-button>
-			        </md-menu-item>
-			        <md-menu-item>
-			          <md-button ng-click="vm.logout()">
-			          	<md-icon md-menu-align-target aria-label="{{ 'home.logout' | translate }}" class="material-icons">exit_to_app</md-icon>
-						<span translate>home.logout</span>
-			          </md-button>
-			        </md-menu-item>
-			      </md-menu-content>
-		      </md-menu>		      
+		      </md-button>
+			  <tb-user-menu
+					  display-user-info="!vm.displaySearchMode()">
+			  </tb-user-menu>
       	</div>
     </md-toolbar>
    	<md-progress-linear class="md-warn" style="z-index: 10; max-height: 0px; width: 100%;" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 8cccd28..c747cbf 100644
--- a/ui/src/app/layout/index.js
+++ b/ui/src/app/layout/index.js
@@ -28,6 +28,8 @@ import thingsboardApiUser from '../api/user.service';
 import thingsboardNoAnimate from '../components/no-animate.directive';
 import thingsboardSideMenu from '../components/side-menu.directive';
 
+import thingsboardUserMenu from './user-menu.directive';
+
 import thingsboardTenant from '../tenant';
 import thingsboardCustomer from '../customer';
 import thingsboardUser from '../user';
@@ -54,6 +56,7 @@ export default angular.module('thingsboard.home', [
     'ncy-angular-breadcrumb',
     thingsboardMenu,
     thingsboardHomeLinks,
+    thingsboardUserMenu,
     thingsboardTenant,
     thingsboardCustomer,
     thingsboardUser,
diff --git a/ui/src/app/layout/user-menu.directive.js b/ui/src/app/layout/user-menu.directive.js
new file mode 100644
index 0000000..6d09e67
--- /dev/null
+++ b/ui/src/app/layout/user-menu.directive.js
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './user-menu.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import userMenuTemplate from './user-menu.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.usermenu', [])
+    .directive('tbUserMenu', UserMenu)
+    .name;
+
+/*@ngInject*/
+function UserMenu() {
+    return {
+        restrict: "E",
+        scope: true,
+        bindToController: {
+            displayUserInfo: '=',
+        },
+        controller: UserMenuController,
+        controllerAs: 'vm',
+        templateUrl: userMenuTemplate
+    };
+}
+
+/*@ngInject*/
+function UserMenuController($scope, userService, $translate, $state) {
+
+    var vm = this;
+
+    var dashboardUser = userService.getCurrentUser();
+
+    vm.authorityName = authorityName;
+    vm.displaySearchMode = displaySearchMode;
+    vm.logout = logout;
+    vm.openProfile = openProfile;
+    vm.userDisplayName = userDisplayName;
+
+    function displaySearchMode() {
+        return $scope.searchConfig.searchEnabled &&
+            $scope.searchConfig.showSearch;
+    }
+
+    function authorityName() {
+        var name = "user.anonymous";
+        if (dashboardUser) {
+            var authority = dashboardUser.authority;
+            if (authority === 'SYS_ADMIN') {
+                name = 'user.sys-admin';
+            } else if (authority === 'TENANT_ADMIN') {
+                name = 'user.tenant-admin';
+            } else if (authority === 'CUSTOMER_USER') {
+                name = 'user.customer';
+            }
+        }
+        return $translate.instant(name);
+    }
+
+    function userDisplayName() {
+        var name = "";
+        if (dashboardUser) {
+            if ((dashboardUser.firstName && dashboardUser.firstName.length > 0) ||
+                (dashboardUser.lastName && dashboardUser.lastName.length > 0)) {
+                if (dashboardUser.firstName) {
+                    name += dashboardUser.firstName;
+                }
+                if (dashboardUser.lastName) {
+                    if (name.length > 0) {
+                        name += " ";
+                    }
+                    name += dashboardUser.lastName;
+                }
+            } else {
+                name = dashboardUser.email;
+            }
+        }
+        return name;
+    }
+
+    function openProfile() {
+        $state.go('home.profile');
+    }
+
+    function logout() {
+        userService.logout();
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/layout/user-menu.scss b/ui/src/app/layout/user-menu.scss
new file mode 100644
index 0000000..b90c8ee
--- /dev/null
+++ b/ui/src/app/layout/user-menu.scss
@@ -0,0 +1,41 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+div.tb-user-info {
+  line-height: 1.5;
+  span {
+    text-transform: none;
+    text-align: left;
+  }
+  span.tb-user-display-name {
+    font-size: 0.800rem;
+    font-weight: 300;
+    letter-spacing: 0.008em;
+  }
+  span.tb-user-authority {
+    font-size: 0.800rem;
+    font-weight: 300;
+    letter-spacing: 0.005em;
+    opacity: 0.8;
+  }
+}
+
+md-icon.tb-mini-avatar {
+  margin: auto 8px;
+  font-size: 36px;
+  height: 36px;
+  width: 36px;
+}
diff --git a/ui/src/app/layout/user-menu.tpl.html b/ui/src/app/layout/user-menu.tpl.html
new file mode 100644
index 0000000..3e70334
--- /dev/null
+++ b/ui/src/app/layout/user-menu.tpl.html
@@ -0,0 +1,28 @@
+<section layout="row">
+    <div hide-xs hide-sm ng-show="vm.displayUserInfo" class="tb-user-info" layout="row">
+        <md-icon aria-label="{{ 'home.avatar' | translate }}" class="material-icons tb-mini-avatar">account_circle</md-icon>
+        <div layout="column">
+            <span class="tb-user-display-name">{{vm.userDisplayName()}}</span>
+            <span class="tb-user-authority">{{vm.authorityName()}}</span>
+        </div>
+    </div>
+    <md-menu md-position-mode="target-right target">
+        <md-button class="md-icon-button"  aria-label="{{ 'home.open-user-menu' | translate }}" ng-click="$mdOpenMenu($event)">
+            <md-icon md-menu-origin aria-label="{{ 'home.open-user-menu' | translate }}" class="material-icons">more_vert</md-icon>
+        </md-button>
+        <md-menu-content width="4">
+            <md-menu-item>
+                <md-button ng-click="vm.openProfile()">
+                    <md-icon md-menu-align-target aria-label="{{ 'home.profile' | translate }}" class="material-icons">account_circle</md-icon>
+                    <span translate>home.profile</span>
+                </md-button>
+            </md-menu-item>
+            <md-menu-item>
+                <md-button ng-click="vm.logout()">
+                    <md-icon md-menu-align-target aria-label="{{ 'home.logout' | translate }}" class="material-icons">exit_to_app</md-icon>
+                    <span translate>home.logout</span>
+                </md-button>
+            </md-menu-item>
+        </md-menu-content>
+    </md-menu>
+</section>
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 332da30..bf56900 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -632,7 +632,9 @@ export default angular.module('thingsboard.locale', [])
                     "email-required": "Email is required.",
                     "first-name": "First Name",
                     "last-name": "Last Name",
-                    "description": "Description"
+                    "description": "Description",
+                    "default-dashboard": "Default dashboard",
+                    "always-fullscreen": "Always fullscreen"
                 },
                 "value": {
                     "type": "Value type",
diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js
index 0a0208e..d0d1596 100644
--- a/ui/src/app/services/menu.service.js
+++ b/ui/src/app/services/menu.service.js
@@ -63,7 +63,7 @@ function Menu(userService, $state, $rootScope) {
                         {
                             name: 'home.home',
                             type: 'link',
-                            state: 'home',
+                            state: 'home.links',
                             icon: 'home'
                         },
                         {
@@ -167,7 +167,7 @@ function Menu(userService, $state, $rootScope) {
                         {
                             name: 'home.home',
                             type: 'link',
-                            state: 'home',
+                            state: 'home.links',
                             icon: 'home'
                         },
                         {
@@ -264,7 +264,7 @@ function Menu(userService, $state, $rootScope) {
                         {
                             name: 'home.home',
                             type: 'link',
-                            state: 'home',
+                            state: 'home.links',
                             icon: 'home'
                         },
                         {
diff --git a/ui/src/app/user/user.directive.js b/ui/src/app/user/user.directive.js
index fb24dec..a05d5ed 100644
--- a/ui/src/app/user/user.directive.js
+++ b/ui/src/app/user/user.directive.js
@@ -13,6 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+import './user-fieldset.scss';
+
 /* eslint-disable import/no-unresolved, import/default */
 
 import userFieldsetTemplate from './user-fieldset.tpl.html';
@@ -20,10 +23,40 @@ import userFieldsetTemplate from './user-fieldset.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function UserDirective($compile, $templateCache) {
+export default function UserDirective($compile, $templateCache, dashboardService) {
     var linker = function (scope, element) {
         var template = $templateCache.get(userFieldsetTemplate);
         element.html(template);
+
+        scope.isCustomerUser = function() {
+            return scope.user && scope.user.authority === 'CUSTOMER_USER';
+        }
+
+        scope.$watch('user', function(newUser, prevUser) {
+            if (!angular.equals(newUser, prevUser) && newUser) {
+                scope.defaultDashboard = null;
+                if (scope.isCustomerUser() && scope.user.additionalInfo &&
+                    scope.user.additionalInfo.defaultDashboardId) {
+                    dashboardService.getDashboard(scope.user.additionalInfo.defaultDashboardId).then(
+                        function(dashboard) {
+                            scope.defaultDashboard = dashboard;
+                        }
+                    )
+                }
+            }
+        });
+
+        scope.$watch('defaultDashboard', function(newDashboard, prevDashboard) {
+            if (!angular.equals(newDashboard, prevDashboard)) {
+                if (scope.isCustomerUser()) {
+                    if (!scope.user.additionalInfo) {
+                        scope.user.additionalInfo = {};
+                    }
+                    scope.user.additionalInfo.defaultDashboardId = newDashboard ? newDashboard.id.id : null;
+                }
+            }
+        });
+
         $compile(element.contents())(scope);
     }
     return {
diff --git a/ui/src/app/user/user-fieldset.scss b/ui/src/app/user/user-fieldset.scss
new file mode 100644
index 0000000..ff02a0c
--- /dev/null
+++ b/ui/src/app/user/user-fieldset.scss
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import '../../scss/constants';
+
+.tb-default-dashboard {
+  .tb-default-dashboard-label {
+    padding-bottom: 8px;
+  }
+  tb-dashboard-select {
+    @media (min-width: $layout-breakpoint-gt-sm) {
+      padding-right: 12px;
+    }
+    @media (max-width: $layout-breakpoint-gt-sm) {
+      padding-bottom: 12px;
+    }
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html
index e6b48d4..8812798 100644
--- a/ui/src/app/user/user-fieldset.tpl.html
+++ b/ui/src/app/user/user-fieldset.tpl.html
@@ -43,5 +43,20 @@
             <label translate>user.description</label>
             <textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
         </md-input-container>
+        <section class="tb-default-dashboard" flex layout="column"ng-show="isCustomerUser()">
+            <span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span>
+            <section flex layout="column" layout-gt-sm="row">
+                <tb-dashboard-select flex
+                                     the-form="theForm"
+                                     ng-model="defaultDashboard"
+                                     dashboards-scope="customer"
+                                     customer-id="user.customerId.id"
+                                     select-first-dashboard="false">
+                </tb-dashboard-select>
+                <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'user.always-fullscreen' | translate }}"
+                             ng-model="user.additionalInfo.defaultDashboardFullscreen">{{ 'user.always-fullscreen' | translate }}
+                </md-checkbox>
+            </section>
+        </section>
     </fieldset>
 </md-content>