Commit 86515896dd4328acf415d1d4e691145dcb66bb2a

Authored by Igor Kulikov
1 parent 3019bf75

TB-71: Ability to activate users without sending email.

@@ -173,7 +173,12 @@ public class AuthController extends BaseController { @@ -173,7 +173,12 @@ public class AuthController extends BaseController {
173 String baseUrl = constructBaseUrl(request); 173 String baseUrl = constructBaseUrl(request);
174 String loginUrl = String.format("%s/login", baseUrl); 174 String loginUrl = String.format("%s/login", baseUrl);
175 String email = user.getEmail(); 175 String email = user.getEmail();
176 - mailService.sendAccountActivatedEmail(loginUrl, email); 176 +
  177 + try {
  178 + mailService.sendAccountActivatedEmail(loginUrl, email);
  179 + } catch (Exception e) {
  180 + log.info("Unable to send account activation email [{}]", e.getMessage());
  181 + }
177 182
178 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); 183 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
179 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); 184 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
@@ -63,6 +63,7 @@ public class UserController extends BaseController { @@ -63,6 +63,7 @@ public class UserController extends BaseController {
63 @RequestMapping(value = "/user", method = RequestMethod.POST) 63 @RequestMapping(value = "/user", method = RequestMethod.POST)
64 @ResponseBody 64 @ResponseBody
65 public User saveUser(@RequestBody User user, 65 public User saveUser(@RequestBody User user,
  66 + @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
66 HttpServletRequest request) throws ThingsboardException { 67 HttpServletRequest request) throws ThingsboardException {
67 try { 68 try {
68 SecurityUser authUser = getCurrentUser(); 69 SecurityUser authUser = getCurrentUser();
@@ -70,7 +71,7 @@ public class UserController extends BaseController { @@ -70,7 +71,7 @@ public class UserController extends BaseController {
70 throw new ThingsboardException("You don't have permission to perform this operation!", 71 throw new ThingsboardException("You don't have permission to perform this operation!",
71 ThingsboardErrorCode.PERMISSION_DENIED); 72 ThingsboardErrorCode.PERMISSION_DENIED);
72 } 73 }
73 - boolean sendEmail = user.getId() == null; 74 + boolean sendEmail = user.getId() == null && sendActivationMail;
74 if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) { 75 if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
75 user.setTenantId(getCurrentUser().getTenantId()); 76 user.setTenantId(getCurrentUser().getTenantId());
76 } 77 }
@@ -117,6 +118,35 @@ public class UserController extends BaseController { @@ -117,6 +118,35 @@ public class UserController extends BaseController {
117 } 118 }
118 119
119 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") 120 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  121 + @RequestMapping(value = "/user/{userId}/activationLink", method = RequestMethod.GET, produces = "text/plain")
  122 + @ResponseBody
  123 + public String getActivationLink(
  124 + @PathVariable("userId") String strUserId,
  125 + HttpServletRequest request) throws ThingsboardException {
  126 + checkParameter("userId", strUserId);
  127 + try {
  128 + UserId userId = new UserId(toUUID(strUserId));
  129 + SecurityUser authUser = getCurrentUser();
  130 + if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(userId)) {
  131 + throw new ThingsboardException("You don't have permission to perform this operation!",
  132 + ThingsboardErrorCode.PERMISSION_DENIED);
  133 + }
  134 + User user = checkUserId(userId);
  135 + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
  136 + if (!userCredentials.isEnabled()) {
  137 + String baseUrl = constructBaseUrl(request);
  138 + String activateUrl = String.format("%s/api/noauth/activate?activateToken=%s", baseUrl,
  139 + userCredentials.getActivateToken());
  140 + return activateUrl;
  141 + } else {
  142 + throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  143 + }
  144 + } catch (Exception e) {
  145 + throw handleException(e);
  146 + }
  147 + }
  148 +
  149 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
120 @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE) 150 @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE)
121 @ResponseStatus(value = HttpStatus.OK) 151 @ResponseStatus(value = HttpStatus.OK)
122 public void deleteUser(@PathVariable("userId") String strUserId) throws ThingsboardException { 152 public void deleteUser(@PathVariable("userId") String strUserId) throws ThingsboardException {
@@ -45,6 +45,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi @@ -45,6 +45,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
45 isUserLoaded: isUserLoaded, 45 isUserLoaded: isUserLoaded,
46 saveUser: saveUser, 46 saveUser: saveUser,
47 sendActivationEmail: sendActivationEmail, 47 sendActivationEmail: sendActivationEmail,
  48 + getActivationLink: getActivationLink,
48 setUserFromJwtToken: setUserFromJwtToken, 49 setUserFromJwtToken: setUserFromJwtToken,
49 getJwtToken: getJwtToken, 50 getJwtToken: getJwtToken,
50 clearJwtToken: clearJwtToken, 51 clearJwtToken: clearJwtToken,
@@ -397,9 +398,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi @@ -397,9 +398,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
397 return deferred.promise; 398 return deferred.promise;
398 } 399 }
399 400
400 - function saveUser(user) { 401 + function saveUser(user, sendActivationMail) {
401 var deferred = $q.defer(); 402 var deferred = $q.defer();
402 var url = '/api/user'; 403 var url = '/api/user';
  404 + if (angular.isDefined(sendActivationMail)) {
  405 + url += '?sendActivationMail=' + sendActivationMail;
  406 + }
403 $http.post(url, user).then(function success(response) { 407 $http.post(url, user).then(function success(response) {
404 deferred.resolve(response.data); 408 deferred.resolve(response.data);
405 }, function fail(response) { 409 }, function fail(response) {
@@ -441,6 +445,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi @@ -441,6 +445,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
441 return deferred.promise; 445 return deferred.promise;
442 } 446 }
443 447
  448 + function getActivationLink(userId) {
  449 + var deferred = $q.defer();
  450 + var url = `/api/user/${userId}/activationLink`
  451 + $http.get(url).then(function success(response) {
  452 + deferred.resolve(response.data);
  453 + }, function fail() {
  454 + deferred.reject();
  455 + });
  456 + return deferred.promise;
  457 + }
  458 +
444 function forceDefaultPlace(to, params) { 459 function forceDefaultPlace(to, params) {
445 if (currentUser && isAuthenticated()) { 460 if (currentUser && isAuthenticated()) {
446 if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') { 461 if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
@@ -74,6 +74,11 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, @@ -74,6 +74,11 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
74 74
75 var locationSearch = $location.search(); 75 var locationSearch = $location.search();
76 var publicId = locationSearch.publicId; 76 var publicId = locationSearch.publicId;
  77 + var activateToken = locationSearch.activateToken;
  78 +
  79 + if (to.url === '/createPassword?activateToken' && activateToken && activateToken.length) {
  80 + userService.setUserFromJwtToken(null, null, false);
  81 + }
77 82
78 if (userService.isUserLoaded() === true) { 83 if (userService.isUserLoaded() === true) {
79 if (userService.isAuthenticated()) { 84 if (userService.isAuthenticated()) {
@@ -124,7 +129,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, @@ -124,7 +129,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
124 } 129 }
125 }) 130 })
126 131
127 - $rootScope.pageTitle = 'Thingsboard'; 132 + $rootScope.pageTitle = 'ThingsBoard';
128 133
129 $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) { 134 $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
130 if (userService.isPublic() && to.name === 'home.dashboards.dashboard') { 135 if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
@@ -133,9 +138,9 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, @@ -133,9 +138,9 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
133 } 138 }
134 if (angular.isDefined(to.data.pageTitle)) { 139 if (angular.isDefined(to.data.pageTitle)) {
135 $translate(to.data.pageTitle).then(function (translation) { 140 $translate(to.data.pageTitle).then(function (translation) {
136 - $rootScope.pageTitle = 'Thingsboard | ' + translation; 141 + $rootScope.pageTitle = 'ThingsBoard | ' + translation;
137 }, function (translationId) { 142 }, function (translationId) {
138 - $rootScope.pageTitle = 'Thingsboard | ' + translationId; 143 + $rootScope.pageTitle = 'ThingsBoard | ' + translationId;
139 }); 144 });
140 } 145 }
141 }) 146 })
@@ -26,6 +26,7 @@ import gridTemplate from './grid.tpl.html'; @@ -26,6 +26,7 @@ import gridTemplate from './grid.tpl.html';
26 26
27 export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav]) 27 export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav])
28 .directive('tbGrid', Grid) 28 .directive('tbGrid', Grid)
  29 + .controller('AddItemController', AddItemController)
29 .controller('ItemCardController', ItemCardController) 30 .controller('ItemCardController', ItemCardController)
30 .directive('tbGridCardContent', GridCardContent) 31 .directive('tbGridCardContent', GridCardContent)
31 .filter('range', RangeFilter) 32 .filter('range', RangeFilter)
@@ -342,6 +343,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -342,6 +343,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
342 } else { 343 } else {
343 vm.itemCardController = 'ItemCardController'; 344 vm.itemCardController = 'ItemCardController';
344 } 345 }
  346 + if (vm.config.addItemController) {
  347 + vm.addItemController = vm.config.addItemController;
  348 + } else {
  349 + vm.addItemController = 'AddItemController';
  350 + }
345 351
346 vm.parentCtl = vm.config.parentCtl || vm; 352 vm.parentCtl = vm.config.parentCtl || vm;
347 353
@@ -468,7 +474,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -468,7 +474,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
468 474
469 function addItem($event) { 475 function addItem($event) {
470 $mdDialog.show({ 476 $mdDialog.show({
471 - controller: AddItemController, 477 + controller: vm.addItemController,
472 controllerAs: 'vm', 478 controllerAs: 'vm',
473 templateUrl: vm.addItemTemplateUrl, 479 templateUrl: vm.addItemTemplateUrl,
474 parent: angular.element($document[0].body), 480 parent: angular.element($document[0].body),
@@ -1037,6 +1037,7 @@ export default angular.module('thingsboard.locale', []) @@ -1037,6 +1037,7 @@ export default angular.module('thingsboard.locale', [])
1037 "resend-activation": "Resend activation", 1037 "resend-activation": "Resend activation",
1038 "email": "Email", 1038 "email": "Email",
1039 "email-required": "Email is required.", 1039 "email-required": "Email is required.",
  1040 + "invalid-email-format": "Invalid email format.",
1040 "first-name": "First Name", 1041 "first-name": "First Name",
1041 "last-name": "Last Name", 1042 "last-name": "Last Name",
1042 "description": "Description", 1043 "description": "Description",
@@ -1044,7 +1045,14 @@ export default angular.module('thingsboard.locale', []) @@ -1044,7 +1045,14 @@ export default angular.module('thingsboard.locale', [])
1044 "always-fullscreen": "Always fullscreen", 1045 "always-fullscreen": "Always fullscreen",
1045 "select-user": "Select user", 1046 "select-user": "Select user",
1046 "no-users-matching": "No users matching '{{entity}}' were found.", 1047 "no-users-matching": "No users matching '{{entity}}' were found.",
1047 - "user-required": "User is required" 1048 + "user-required": "User is required",
  1049 + "activation-method": "Activation method",
  1050 + "display-activation-link": "Display activation link",
  1051 + "send-activation-mail": "Send activation mail",
  1052 + "activation-link": "User activation link",
  1053 + "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :",
  1054 + "copy-activation-link": "Copy activation link",
  1055 + "activation-link-copied-message": "User activation link has been copied to clipboard"
1048 }, 1056 },
1049 "value": { 1057 "value": {
1050 "type": "Value type", 1058 "type": "Value type",
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/*@ngInject*/
  18 +export default function ActivationLinkDialogController($mdDialog, $translate, toast, activationLink) {
  19 +
  20 + var vm = this;
  21 +
  22 + vm.activationLink = activationLink;
  23 +
  24 + vm.onActivationLinkCopied = onActivationLinkCopied;
  25 + vm.close = close;
  26 +
  27 + function onActivationLinkCopied(){
  28 + toast.showSuccess($translate.instant('user.activation-link-copied-message'), 750, angular.element('#activation-link-dialog-content'), 'bottom left');
  29 + }
  30 +
  31 + function close() {
  32 + $mdDialog.hide();
  33 + }
  34 +
  35 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'user.activation-link' | translate }}" style="min-width: 400px;">
  19 + <form>
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate="user.activation-link"></h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.close()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-dialog-content>
  30 + <div id="activation-link-dialog-content" class="md-dialog-content">
  31 + <md-content class="md-padding" layout="column">
  32 + <span translate="user.activation-link-text" translate-values="{activationLink: vm.activationLink}"></span>
  33 + <div layout="row" layout-align="start center">
  34 + <pre class="tb-highlight" flex><code>{{ vm.activationLink }}</code></pre>
  35 + <md-button class="md-icon-button"
  36 + ngclipboard
  37 + data-clipboard-text="{{ vm.activationLink }}"
  38 + ngclipboard-success="vm.onActivationLinkCopied(e)">
  39 + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
  40 + <md-tooltip md-direction="top">
  41 + {{ 'user.copy-activation-link' | translate }}
  42 + </md-tooltip>
  43 + </md-button>
  44 + </div>
  45 + </md-content>
  46 + </div>
  47 + </md-dialog-content>
  48 + <md-dialog-actions layout="row">
  49 + <span flex></span>
  50 + <md-button ng-click="vm.close()">{{ 'action.ok' |
  51 + translate }}
  52 + </md-button>
  53 + </md-dialog-actions>
  54 + </form>
  55 +</md-dialog>
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import activationLinkDialogTemplate from './activation-link.dialog.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +
  24 +/*@ngInject*/
  25 +export default function AddUserController($scope, $mdDialog, $state, $stateParams, $document, $q, types, userService, saveItemFunction, helpLinks) {
  26 +
  27 + var vm = this;
  28 +
  29 + var tenantId = $stateParams.tenantId;
  30 + var customerId = $stateParams.customerId;
  31 + var usersType = $state.$current.data.usersType;
  32 +
  33 + vm.helpLinks = helpLinks;
  34 + vm.item = {};
  35 +
  36 + vm.activationMethods = [
  37 + {
  38 + value: 'displayActivationLink',
  39 + name: 'user.display-activation-link'
  40 + },
  41 + {
  42 + value: 'sendActivationMail',
  43 + name: 'user.send-activation-mail'
  44 + }
  45 + ];
  46 +
  47 + vm.userActivationMethod = 'displayActivationLink';
  48 +
  49 + vm.add = add;
  50 + vm.cancel = cancel;
  51 +
  52 + function cancel() {
  53 + $mdDialog.cancel();
  54 + }
  55 +
  56 + function add($event) {
  57 + var sendActivationMail = false;
  58 + if (vm.userActivationMethod == 'sendActivationMail') {
  59 + sendActivationMail = true;
  60 + }
  61 + if (usersType === 'tenant') {
  62 + vm.item.authority = "TENANT_ADMIN";
  63 + vm.item.tenantId = {
  64 + entityType: types.entityType.tenant,
  65 + id: tenantId
  66 + };
  67 + } else if (usersType === 'customer') {
  68 + vm.item.authority = "CUSTOMER_USER";
  69 + vm.item.customerId = {
  70 + entityType: types.entityType.customer,
  71 + id: customerId
  72 + };
  73 + }
  74 + userService.saveUser(vm.item, sendActivationMail).then(function success(item) {
  75 + vm.item = item;
  76 + $scope.theForm.$setPristine();
  77 + if (vm.userActivationMethod == 'displayActivationLink') {
  78 + userService.getActivationLink(vm.item.id.id).then(
  79 + function success(activationLink) {
  80 + displayActivationLink($event, activationLink).then(
  81 + function() {
  82 + $mdDialog.hide();
  83 + }
  84 + );
  85 + }
  86 + );
  87 + } else {
  88 + $mdDialog.hide();
  89 + }
  90 + });
  91 + }
  92 +
  93 + function displayActivationLink($event, activationLink) {
  94 + var deferred = $q.defer();
  95 + $mdDialog.show({
  96 + controller: 'ActivationLinkDialogController',
  97 + controllerAs: 'vm',
  98 + templateUrl: activationLinkDialogTemplate,
  99 + locals: {
  100 + activationLink: activationLink
  101 + },
  102 + parent: angular.element($document[0].body),
  103 + fullscreen: true,
  104 + skipHide: true,
  105 + targetEvent: $event
  106 + }).then(function () {
  107 + deferred.resolve();
  108 + });
  109 + return deferred.promise;
  110 + }
  111 +
  112 +}
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<md-dialog aria-label="{{ 'user.add' | translate }}" tb-help="'users'" help-container-id="help-container">  
19 - <form name="theForm" ng-submit="vm.add()"> 18 +<md-dialog style="width: 600px;" aria-label="{{ 'user.add' | translate }}" tb-help="'users'" help-container-id="help-container">
  19 + <form name="theForm" ng-submit="vm.add($event)">
20 <md-toolbar> 20 <md-toolbar>
21 <div class="md-toolbar-tools"> 21 <div class="md-toolbar-tools">
22 <h2 translate>user.add</h2> 22 <h2 translate>user.add</h2>
@@ -32,6 +32,15 @@ @@ -32,6 +32,15 @@
32 <md-dialog-content> 32 <md-dialog-content>
33 <div class="md-dialog-content"> 33 <div class="md-dialog-content">
34 <tb-user user="vm.item" is-edit="true" the-form="theForm"></tb-user> 34 <tb-user user="vm.item" is-edit="true" the-form="theForm"></tb-user>
  35 + <md-input-container class="md-block">
  36 + <label translate>user.activation-method</label>
  37 + <md-select aria-label="{{ 'user.activation-method' | translate }}"
  38 + ng-model="vm.userActivationMethod">
  39 + <md-option ng-repeat="activationMethod in vm.activationMethods" ng-value="activationMethod.value">
  40 + {{activationMethod.name | translate}}
  41 + </md-option>
  42 + </md-select>
  43 + </md-input-container>
35 </div> 44 </div>
36 </md-dialog-content> 45 </md-dialog-content>
37 <md-dialog-actions layout="row"> 46 <md-dialog-actions layout="row">
@@ -20,6 +20,8 @@ import thingsboardToast from '../services/toast'; @@ -20,6 +20,8 @@ import thingsboardToast from '../services/toast';
20 20
21 import UserRoutes from './user.routes'; 21 import UserRoutes from './user.routes';
22 import UserController from './user.controller'; 22 import UserController from './user.controller';
  23 +import AddUserController from './add-user.controller';
  24 +import ActivationLinkDialogController from './activation-link.controller';
23 import UserDirective from './user.directive'; 25 import UserDirective from './user.directive';
24 26
25 export default angular.module('thingsboard.user', [ 27 export default angular.module('thingsboard.user', [
@@ -30,5 +32,7 @@ export default angular.module('thingsboard.user', [ @@ -30,5 +32,7 @@ export default angular.module('thingsboard.user', [
30 ]) 32 ])
31 .config(UserRoutes) 33 .config(UserRoutes)
32 .controller('UserController', UserController) 34 .controller('UserController', UserController)
  35 + .controller('AddUserController', AddUserController)
  36 + .controller('ActivationLinkDialogController', ActivationLinkDialogController)
33 .directive('tbUser', UserDirective) 37 .directive('tbUser', UserDirective)
34 .name; 38 .name;
@@ -15,6 +15,9 @@ @@ -15,6 +15,9 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
  18 +<md-button ng-click="onDisplayActivationLink({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
  19 + 'user.display-activation-link' | translate }}
  20 +</md-button>
18 <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 21 <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
19 'user.resend-activation' | translate }} 22 'user.resend-activation' | translate }}
20 </md-button> 23 </md-button>
@@ -26,9 +29,12 @@ @@ -26,9 +29,12 @@
26 <fieldset ng-disabled="loading || !isEdit"> 29 <fieldset ng-disabled="loading || !isEdit">
27 <md-input-container class="md-block"> 30 <md-input-container class="md-block">
28 <label translate>user.email</label> 31 <label translate>user.email</label>
29 - <input required name="email" type="email" ng-model="user.email"> 32 + <input required name="email"
  33 + ng-pattern="/^[_a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/"
  34 + ng-model="user.email">
30 <div ng-messages="theForm.email.$error"> 35 <div ng-messages="theForm.email.$error">
31 <div translate ng-message="required">user.email-required</div> 36 <div translate ng-message="required">user.email-required</div>
  37 + <div translate ng-message="pattern">user.invalid-email-format</div>
32 </div> 38 </div>
33 </md-input-container> 39 </md-input-container>
34 <md-input-container class="md-block"> 40 <md-input-container class="md-block">
@@ -43,7 +49,7 @@ @@ -43,7 +49,7 @@
43 <label translate>user.description</label> 49 <label translate>user.description</label>
44 <textarea ng-model="user.additionalInfo.description" rows="2"></textarea> 50 <textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
45 </md-input-container> 51 </md-input-container>
46 - <section class="tb-default-dashboard" flex layout="column"> 52 + <section class="tb-default-dashboard" flex layout="column" ng-if="user.id">
47 <span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span> 53 <span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span>
48 <section flex layout="column" layout-gt-sm="row"> 54 <section flex layout="column" layout-gt-sm="row">
49 <tb-dashboard-autocomplete ng-if="isTenantAdmin()" 55 <tb-dashboard-autocomplete ng-if="isTenantAdmin()"
@@ -17,12 +17,13 @@ @@ -17,12 +17,13 @@
17 17
18 import addUserTemplate from './add-user.tpl.html'; 18 import addUserTemplate from './add-user.tpl.html';
19 import userCard from './user-card.tpl.html'; 19 import userCard from './user-card.tpl.html';
  20 +import activationLinkDialogTemplate from './activation-link.dialog.tpl.html';
20 21
21 /* eslint-enable import/no-unresolved, import/default */ 22 /* eslint-enable import/no-unresolved, import/default */
22 23
23 24
24 /*@ngInject*/ 25 /*@ngInject*/
25 -export default function UserController(userService, toast, $scope, $controller, $state, $stateParams, $translate, types) { 26 +export default function UserController(userService, toast, $scope, $mdDialog, $document, $controller, $state, $stateParams, $translate, types) {
26 27
27 var tenantId = $stateParams.tenantId; 28 var tenantId = $stateParams.tenantId;
28 var customerId = $stateParams.customerId; 29 var customerId = $stateParams.customerId;
@@ -58,6 +59,7 @@ export default function UserController(userService, toast, $scope, $controller, @@ -58,6 +59,7 @@ export default function UserController(userService, toast, $scope, $controller,
58 onGridInited: gridInited, 59 onGridInited: gridInited,
59 60
60 addItemTemplateUrl: addUserTemplate, 61 addItemTemplateUrl: addUserTemplate,
  62 + addItemController: 'AddUserController',
61 63
62 addItemText: function() { return $translate.instant('user.add-user-text') }, 64 addItemText: function() { return $translate.instant('user.add-user-text') },
63 noItemsText: function() { return $translate.instant('user.no-users-text') }, 65 noItemsText: function() { return $translate.instant('user.no-users-text') },
@@ -72,6 +74,7 @@ export default function UserController(userService, toast, $scope, $controller, @@ -72,6 +74,7 @@ export default function UserController(userService, toast, $scope, $controller,
72 vm.userGridConfig.topIndex = $stateParams.topIndex; 74 vm.userGridConfig.topIndex = $stateParams.topIndex;
73 } 75 }
74 76
  77 + vm.displayActivationLink = displayActivationLink;
75 vm.resendActivation = resendActivation; 78 vm.resendActivation = resendActivation;
76 79
77 initController(); 80 initController();
@@ -151,6 +154,29 @@ export default function UserController(userService, toast, $scope, $controller, @@ -151,6 +154,29 @@ export default function UserController(userService, toast, $scope, $controller,
151 return userService.deleteUser(userId); 154 return userService.deleteUser(userId);
152 } 155 }
153 156
  157 + function displayActivationLink(event, user) {
  158 + userService.getActivationLink(user.id.id).then(
  159 + function success(activationLink) {
  160 + openActivationLinkDialog(event, activationLink);
  161 + }
  162 + );
  163 + }
  164 +
  165 + function openActivationLinkDialog(event, activationLink) {
  166 + $mdDialog.show({
  167 + controller: 'ActivationLinkDialogController',
  168 + controllerAs: 'vm',
  169 + templateUrl: activationLinkDialogTemplate,
  170 + locals: {
  171 + activationLink: activationLink
  172 + },
  173 + parent: angular.element($document[0].body),
  174 + fullscreen: true,
  175 + skipHide: true,
  176 + targetEvent: event
  177 + });
  178 + }
  179 +
154 function resendActivation(user) { 180 function resendActivation(user) {
155 userService.sendActivationEmail(user.email).then(function success() { 181 userService.sendActivationEmail(user.email).then(function success() {
156 toast.showSuccess($translate.instant('user.activation-email-sent-message')); 182 toast.showSuccess($translate.instant('user.activation-email-sent-message'));
@@ -45,6 +45,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi @@ -45,6 +45,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
45 user: '=', 45 user: '=',
46 isEdit: '=', 46 isEdit: '=',
47 theForm: '=', 47 theForm: '=',
  48 + onDisplayActivationLink: '&',
48 onResendActivation: '&', 49 onResendActivation: '&',
49 onDeleteUser: '&' 50 onDeleteUser: '&'
50 } 51 }
@@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
22 <tb-user user="vm.grid.operatingItem()" 22 <tb-user user="vm.grid.operatingItem()"
23 is-edit="vm.grid.detailsConfig.isDetailsEditMode" 23 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
24 the-form="vm.grid.detailsForm" 24 the-form="vm.grid.detailsForm"
  25 + on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
25 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)" 26 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
26 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user> 27 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
27 </tb-grid> 28 </tb-grid>