thingsboard-aplcache

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
index fe29b08..204c670 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
@@ -173,7 +173,12 @@ public class AuthController extends BaseController {
             String baseUrl = constructBaseUrl(request);
             String loginUrl = String.format("%s/login", baseUrl);
             String email = user.getEmail();
-            mailService.sendAccountActivatedEmail(loginUrl, email);
+
+            try {
+                mailService.sendAccountActivatedEmail(loginUrl, email);
+            } catch (Exception e) {
+                log.info("Unable to send account activation email [{}]", e.getMessage());
+            }
 
             JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
             JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java
index 124c8e0..f80c7c6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/UserController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java
@@ -63,6 +63,7 @@ public class UserController extends BaseController {
     @RequestMapping(value = "/user", method = RequestMethod.POST)
     @ResponseBody 
     public User saveUser(@RequestBody User user,
+                         @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
             HttpServletRequest request) throws ThingsboardException {
         try {
             SecurityUser authUser = getCurrentUser();
@@ -70,7 +71,7 @@ public class UserController extends BaseController {
                 throw new ThingsboardException("You don't have permission to perform this operation!",
                         ThingsboardErrorCode.PERMISSION_DENIED);
             }
-            boolean sendEmail = user.getId() == null;
+            boolean sendEmail = user.getId() == null && sendActivationMail;
             if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
                 user.setTenantId(getCurrentUser().getTenantId());
             }
@@ -117,6 +118,35 @@ public class UserController extends BaseController {
     }
 
     @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/user/{userId}/activationLink", method = RequestMethod.GET, produces = "text/plain")
+    @ResponseBody
+    public String getActivationLink(
+            @PathVariable("userId") String strUserId,
+            HttpServletRequest request) throws ThingsboardException {
+        checkParameter("userId", strUserId);
+        try {
+            UserId userId = new UserId(toUUID(strUserId));
+            SecurityUser authUser = getCurrentUser();
+            if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(userId)) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            User user = checkUserId(userId);
+            UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
+            if (!userCredentials.isEnabled()) {
+                String baseUrl = constructBaseUrl(request);
+                String activateUrl = String.format("%s/api/noauth/activate?activateToken=%s", baseUrl,
+                        userCredentials.getActivateToken());
+                return activateUrl;
+            } else {
+                throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
     @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE)
     @ResponseStatus(value = HttpStatus.OK)
     public void deleteUser(@PathVariable("userId") String strUserId) throws ThingsboardException {
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index a5bb36c..3e552dc 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -45,6 +45,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         isUserLoaded: isUserLoaded,
         saveUser: saveUser,
         sendActivationEmail: sendActivationEmail,
+        getActivationLink: getActivationLink,
         setUserFromJwtToken: setUserFromJwtToken,
         getJwtToken: getJwtToken,
         clearJwtToken: clearJwtToken,
@@ -397,9 +398,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         return deferred.promise;
     }
 
-    function saveUser(user) {
+    function saveUser(user, sendActivationMail) {
         var deferred = $q.defer();
         var url = '/api/user';
+        if (angular.isDefined(sendActivationMail)) {
+            url += '?sendActivationMail=' + sendActivationMail;
+        }
         $http.post(url, user).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail(response) {
@@ -441,6 +445,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
         return deferred.promise;
     }
 
+    function getActivationLink(userId) {
+        var deferred = $q.defer();
+        var url = `/api/user/${userId}/activationLink`
+        $http.get(url).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
     function forceDefaultPlace(to, params) {
         if (currentUser && isAuthenticated()) {
             if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js
index 1e36600..2ab5fe2 100644
--- a/ui/src/app/app.run.js
+++ b/ui/src/app/app.run.js
@@ -74,6 +74,11 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, 
 
             var locationSearch = $location.search();
             var publicId = locationSearch.publicId;
+            var activateToken = locationSearch.activateToken;
+
+            if (to.url === '/createPassword?activateToken' && activateToken && activateToken.length) {
+                userService.setUserFromJwtToken(null, null, false);
+            }
 
             if (userService.isUserLoaded() === true) {
                 if (userService.isAuthenticated()) {
@@ -124,7 +129,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, 
             }
         })
 
-        $rootScope.pageTitle = 'Thingsboard';
+        $rootScope.pageTitle = 'ThingsBoard';
 
         $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
             if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
@@ -133,9 +138,9 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, 
             }
             if (angular.isDefined(to.data.pageTitle)) {
                 $translate(to.data.pageTitle).then(function (translation) {
-                    $rootScope.pageTitle = 'Thingsboard | ' + translation;
+                    $rootScope.pageTitle = 'ThingsBoard | ' + translation;
                 }, function (translationId) {
-                    $rootScope.pageTitle = 'Thingsboard | ' + translationId;
+                    $rootScope.pageTitle = 'ThingsBoard | ' + translationId;
                 });
             }
         })
diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js
index 422501b..69f8e7e 100644
--- a/ui/src/app/components/grid.directive.js
+++ b/ui/src/app/components/grid.directive.js
@@ -26,6 +26,7 @@ import gridTemplate from './grid.tpl.html';
 
 export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav])
     .directive('tbGrid', Grid)
+    .controller('AddItemController', AddItemController)
     .controller('ItemCardController', ItemCardController)
     .directive('tbGridCardContent', GridCardContent)
     .filter('range', RangeFilter)
@@ -342,6 +343,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
         } else {
             vm.itemCardController = 'ItemCardController';
         }
+        if (vm.config.addItemController) {
+            vm.addItemController =  vm.config.addItemController;
+        } else {
+            vm.addItemController = 'AddItemController';
+        }
 
         vm.parentCtl = vm.config.parentCtl || vm;
 
@@ -468,7 +474,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
 
     function addItem($event) {
         $mdDialog.show({
-            controller: AddItemController,
+            controller: vm.addItemController,
             controllerAs: 'vm',
             templateUrl: vm.addItemTemplateUrl,
             parent: angular.element($document[0].body),
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 2a33d96..d5f8dd2 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1037,6 +1037,7 @@ export default angular.module('thingsboard.locale', [])
                     "resend-activation": "Resend activation",
                     "email": "Email",
                     "email-required": "Email is required.",
+                    "invalid-email-format": "Invalid email format.",
                     "first-name": "First Name",
                     "last-name": "Last Name",
                     "description": "Description",
@@ -1044,7 +1045,14 @@ export default angular.module('thingsboard.locale', [])
                     "always-fullscreen": "Always fullscreen",
                     "select-user": "Select user",
                     "no-users-matching": "No users matching '{{entity}}' were found.",
-                    "user-required": "User is required"
+                    "user-required": "User is required",
+                    "activation-method": "Activation method",
+                    "display-activation-link": "Display activation link",
+                    "send-activation-mail": "Send activation mail",
+                    "activation-link": "User activation link",
+                    "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :",
+                    "copy-activation-link": "Copy activation link",
+                    "activation-link-copied-message": "User activation link has been copied to clipboard"
                 },
                 "value": {
                     "type": "Value type",
diff --git a/ui/src/app/user/activation-link.controller.js b/ui/src/app/user/activation-link.controller.js
new file mode 100644
index 0000000..9fb76c5
--- /dev/null
+++ b/ui/src/app/user/activation-link.controller.js
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+/*@ngInject*/
+export default function ActivationLinkDialogController($mdDialog, $translate, toast, activationLink) {
+
+    var vm = this;
+
+    vm.activationLink = activationLink;
+
+    vm.onActivationLinkCopied = onActivationLinkCopied;
+    vm.close = close;
+
+    function onActivationLinkCopied(){
+        toast.showSuccess($translate.instant('user.activation-link-copied-message'), 750, angular.element('#activation-link-dialog-content'), 'bottom left');
+    }
+
+    function close() {
+        $mdDialog.hide();
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/user/activation-link.dialog.tpl.html b/ui/src/app/user/activation-link.dialog.tpl.html
new file mode 100644
index 0000000..3f55d1d
--- /dev/null
+++ b/ui/src/app/user/activation-link.dialog.tpl.html
@@ -0,0 +1,55 @@
+<!--
+
+    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.
+
+-->
+<md-dialog aria-label="{{ 'user.activation-link' | translate }}" style="min-width: 400px;">
+    <form>
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate="user.activation-link"></h2>
+                <span flex></span>
+                <md-button class="md-icon-button" ng-click="vm.close()">
+                    <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+                </md-button>
+            </div>
+        </md-toolbar>
+        <md-dialog-content>
+            <div id="activation-link-dialog-content" class="md-dialog-content">
+                <md-content class="md-padding" layout="column">
+                    <span translate="user.activation-link-text" translate-values="{activationLink: vm.activationLink}"></span>
+                    <div layout="row" layout-align="start center">
+                        <pre class="tb-highlight" flex><code>{{ vm.activationLink }}</code></pre>
+                        <md-button class="md-icon-button"
+                                   ngclipboard
+                                   data-clipboard-text="{{ vm.activationLink }}"
+                                   ngclipboard-success="vm.onActivationLinkCopied(e)">
+                            <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
+                            <md-tooltip md-direction="top">
+                                {{ 'user.copy-activation-link' | translate }}
+                            </md-tooltip>
+                        </md-button>
+                    </div>
+                </md-content>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-click="vm.close()">{{ 'action.ok' |
+                translate }}
+            </md-button>
+        </md-dialog-actions>
+    </form>
+</md-dialog>
diff --git a/ui/src/app/user/add-user.controller.js b/ui/src/app/user/add-user.controller.js
new file mode 100644
index 0000000..07d4751
--- /dev/null
+++ b/ui/src/app/user/add-user.controller.js
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import activationLinkDialogTemplate from './activation-link.dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+/*@ngInject*/
+export default function AddUserController($scope, $mdDialog, $state, $stateParams, $document, $q, types, userService, saveItemFunction, helpLinks) {
+
+    var vm = this;
+
+    var tenantId = $stateParams.tenantId;
+    var customerId = $stateParams.customerId;
+    var usersType = $state.$current.data.usersType;
+
+    vm.helpLinks = helpLinks;
+    vm.item = {};
+
+    vm.activationMethods = [
+        {
+            value: 'displayActivationLink',
+            name: 'user.display-activation-link'
+        },
+        {
+            value: 'sendActivationMail',
+            name: 'user.send-activation-mail'
+        }
+    ];
+
+    vm.userActivationMethod = 'displayActivationLink';
+
+    vm.add = add;
+    vm.cancel = cancel;
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function add($event) {
+        var sendActivationMail = false;
+        if (vm.userActivationMethod == 'sendActivationMail') {
+            sendActivationMail = true;
+        }
+        if (usersType === 'tenant') {
+            vm.item.authority = "TENANT_ADMIN";
+            vm.item.tenantId = {
+                entityType: types.entityType.tenant,
+                id: tenantId
+            };
+        } else if (usersType === 'customer') {
+            vm.item.authority = "CUSTOMER_USER";
+            vm.item.customerId = {
+                entityType: types.entityType.customer,
+                id: customerId
+            };
+        }
+        userService.saveUser(vm.item, sendActivationMail).then(function success(item) {
+            vm.item = item;
+            $scope.theForm.$setPristine();
+            if (vm.userActivationMethod == 'displayActivationLink') {
+                userService.getActivationLink(vm.item.id.id).then(
+                    function success(activationLink) {
+                        displayActivationLink($event, activationLink).then(
+                            function() {
+                                $mdDialog.hide();
+                            }
+                        );
+                    }
+                );
+            } else {
+                $mdDialog.hide();
+            }
+        });
+    }
+
+    function displayActivationLink($event, activationLink) {
+        var deferred = $q.defer();
+        $mdDialog.show({
+            controller: 'ActivationLinkDialogController',
+            controllerAs: 'vm',
+            templateUrl: activationLinkDialogTemplate,
+            locals: {
+                activationLink: activationLink
+            },
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            skipHide: true,
+            targetEvent: $event
+        }).then(function () {
+            deferred.resolve();
+        });
+        return deferred.promise;
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/user/add-user.tpl.html b/ui/src/app/user/add-user.tpl.html
index 8499368..4883394 100644
--- a/ui/src/app/user/add-user.tpl.html
+++ b/ui/src/app/user/add-user.tpl.html
@@ -15,8 +15,8 @@
     limitations under the License.
 
 -->
-<md-dialog aria-label="{{ 'user.add' | translate }}" tb-help="'users'" help-container-id="help-container">
-	<form name="theForm" ng-submit="vm.add()">
+<md-dialog style="width: 600px;" aria-label="{{ 'user.add' | translate }}" tb-help="'users'" help-container-id="help-container">
+	<form name="theForm" ng-submit="vm.add($event)">
 	    <md-toolbar>
 	      <div class="md-toolbar-tools">
 	        <h2 translate>user.add</h2>
@@ -32,6 +32,15 @@
 	    <md-dialog-content>
 	      <div class="md-dialog-content">
   	        	<tb-user user="vm.item" is-edit="true" the-form="theForm"></tb-user>
+				<md-input-container class="md-block">
+					  <label translate>user.activation-method</label>
+					  <md-select aria-label="{{ 'user.activation-method' | translate }}"
+								 ng-model="vm.userActivationMethod">
+						  <md-option ng-repeat="activationMethod in vm.activationMethods" ng-value="activationMethod.value">
+							  {{activationMethod.name | translate}}
+						  </md-option>
+					  </md-select>
+				</md-input-container>
 	      </div>
 	    </md-dialog-content>
 	    <md-dialog-actions layout="row">
diff --git a/ui/src/app/user/index.js b/ui/src/app/user/index.js
index e3b40b6..ae7c13f 100644
--- a/ui/src/app/user/index.js
+++ b/ui/src/app/user/index.js
@@ -20,6 +20,8 @@ import thingsboardToast from '../services/toast';
 
 import UserRoutes from './user.routes';
 import UserController from './user.controller';
+import AddUserController from './add-user.controller';
+import ActivationLinkDialogController from './activation-link.controller';
 import UserDirective from './user.directive';
 
 export default angular.module('thingsboard.user', [
@@ -30,5 +32,7 @@ export default angular.module('thingsboard.user', [
 ])
     .config(UserRoutes)
     .controller('UserController', UserController)
+    .controller('AddUserController', AddUserController)
+    .controller('ActivationLinkDialogController', ActivationLinkDialogController)
     .directive('tbUser', UserDirective)
     .name;
diff --git a/ui/src/app/user/user.controller.js b/ui/src/app/user/user.controller.js
index 4e702bd..a9f4ce3 100644
--- a/ui/src/app/user/user.controller.js
+++ b/ui/src/app/user/user.controller.js
@@ -17,12 +17,13 @@
 
 import addUserTemplate from './add-user.tpl.html';
 import userCard from './user-card.tpl.html';
+import activationLinkDialogTemplate from './activation-link.dialog.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
 
 /*@ngInject*/
-export default function UserController(userService, toast, $scope, $controller, $state, $stateParams, $translate, types) {
+export default function UserController(userService, toast, $scope, $mdDialog, $document, $controller, $state, $stateParams, $translate, types) {
 
     var tenantId = $stateParams.tenantId;
     var customerId = $stateParams.customerId;
@@ -58,6 +59,7 @@ export default function UserController(userService, toast, $scope, $controller, 
         onGridInited: gridInited,
 
         addItemTemplateUrl: addUserTemplate,
+        addItemController: 'AddUserController',
 
         addItemText: function() { return $translate.instant('user.add-user-text') },
         noItemsText: function() { return $translate.instant('user.no-users-text') },
@@ -72,6 +74,7 @@ export default function UserController(userService, toast, $scope, $controller, 
         vm.userGridConfig.topIndex = $stateParams.topIndex;
     }
 
+    vm.displayActivationLink = displayActivationLink;
     vm.resendActivation = resendActivation;
 
     initController();
@@ -151,6 +154,29 @@ export default function UserController(userService, toast, $scope, $controller, 
         return userService.deleteUser(userId);
     }
 
+    function displayActivationLink(event, user) {
+        userService.getActivationLink(user.id.id).then(
+            function success(activationLink) {
+                openActivationLinkDialog(event, activationLink);
+            }
+        );
+    }
+
+    function openActivationLinkDialog(event, activationLink) {
+        $mdDialog.show({
+            controller: 'ActivationLinkDialogController',
+            controllerAs: 'vm',
+            templateUrl: activationLinkDialogTemplate,
+            locals: {
+                activationLink: activationLink
+            },
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            skipHide: true,
+            targetEvent: event
+        });
+    }
+
     function resendActivation(user) {
         userService.sendActivationEmail(user.email).then(function success() {
             toast.showSuccess($translate.instant('user.activation-email-sent-message'));
diff --git a/ui/src/app/user/user.directive.js b/ui/src/app/user/user.directive.js
index bd78dc2..bbb69a2 100644
--- a/ui/src/app/user/user.directive.js
+++ b/ui/src/app/user/user.directive.js
@@ -45,6 +45,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
             user: '=',
             isEdit: '=',
             theForm: '=',
+            onDisplayActivationLink: '&',
             onResendActivation: '&',
             onDeleteUser: '&'
         }
diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html
index a2559ff..9ae097f 100644
--- a/ui/src/app/user/user-fieldset.tpl.html
+++ b/ui/src/app/user/user-fieldset.tpl.html
@@ -15,6 +15,9 @@
     limitations under the License.
 
 -->
+<md-button ng-click="onDisplayActivationLink({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
+    'user.display-activation-link' | translate }}
+</md-button>
 <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
     'user.resend-activation' | translate }}
 </md-button>
@@ -26,9 +29,12 @@
     <fieldset ng-disabled="loading || !isEdit">
         <md-input-container class="md-block">
             <label translate>user.email</label>
-            <input required name="email" type="email" ng-model="user.email">
+            <input required name="email"
+                   ng-pattern="/^[_a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/"
+                   ng-model="user.email">
             <div ng-messages="theForm.email.$error">
                 <div translate ng-message="required">user.email-required</div>
+                <div translate ng-message="pattern">user.invalid-email-format</div>
             </div>
         </md-input-container>
         <md-input-container class="md-block">
@@ -43,7 +49,7 @@
             <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">
+        <section class="tb-default-dashboard" flex layout="column" ng-if="user.id">
             <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-autocomplete ng-if="isTenantAdmin()"
diff --git a/ui/src/app/user/users.tpl.html b/ui/src/app/user/users.tpl.html
index 09eac5c..72f67d2 100644
--- a/ui/src/app/user/users.tpl.html
+++ b/ui/src/app/user/users.tpl.html
@@ -22,6 +22,7 @@
 	<tb-user user="vm.grid.operatingItem()"
 			 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
 			 the-form="vm.grid.detailsForm"
+			 on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
 			 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
 			 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
 </tb-grid>