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 173 String baseUrl = constructBaseUrl(request);
174 174 String loginUrl = String.format("%s/login", baseUrl);
175 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 183 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
179 184 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
... ...
... ... @@ -63,6 +63,7 @@ public class UserController extends BaseController {
63 63 @RequestMapping(value = "/user", method = RequestMethod.POST)
64 64 @ResponseBody
65 65 public User saveUser(@RequestBody User user,
  66 + @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
66 67 HttpServletRequest request) throws ThingsboardException {
67 68 try {
68 69 SecurityUser authUser = getCurrentUser();
... ... @@ -70,7 +71,7 @@ public class UserController extends BaseController {
70 71 throw new ThingsboardException("You don't have permission to perform this operation!",
71 72 ThingsboardErrorCode.PERMISSION_DENIED);
72 73 }
73   - boolean sendEmail = user.getId() == null;
  74 + boolean sendEmail = user.getId() == null && sendActivationMail;
74 75 if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
75 76 user.setTenantId(getCurrentUser().getTenantId());
76 77 }
... ... @@ -117,6 +118,35 @@ public class UserController extends BaseController {
117 118 }
118 119
119 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 150 @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE)
121 151 @ResponseStatus(value = HttpStatus.OK)
122 152 public void deleteUser(@PathVariable("userId") String strUserId) throws ThingsboardException {
... ...
... ... @@ -45,6 +45,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
45 45 isUserLoaded: isUserLoaded,
46 46 saveUser: saveUser,
47 47 sendActivationEmail: sendActivationEmail,
  48 + getActivationLink: getActivationLink,
48 49 setUserFromJwtToken: setUserFromJwtToken,
49 50 getJwtToken: getJwtToken,
50 51 clearJwtToken: clearJwtToken,
... ... @@ -397,9 +398,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
397 398 return deferred.promise;
398 399 }
399 400
400   - function saveUser(user) {
  401 + function saveUser(user, sendActivationMail) {
401 402 var deferred = $q.defer();
402 403 var url = '/api/user';
  404 + if (angular.isDefined(sendActivationMail)) {
  405 + url += '?sendActivationMail=' + sendActivationMail;
  406 + }
403 407 $http.post(url, user).then(function success(response) {
404 408 deferred.resolve(response.data);
405 409 }, function fail(response) {
... ... @@ -441,6 +445,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
441 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 459 function forceDefaultPlace(to, params) {
445 460 if (currentUser && isAuthenticated()) {
446 461 if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
... ...
... ... @@ -74,6 +74,11 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
74 74
75 75 var locationSearch = $location.search();
76 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 83 if (userService.isUserLoaded() === true) {
79 84 if (userService.isAuthenticated()) {
... ... @@ -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 134 $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
130 135 if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
... ... @@ -133,9 +138,9 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
133 138 }
134 139 if (angular.isDefined(to.data.pageTitle)) {
135 140 $translate(to.data.pageTitle).then(function (translation) {
136   - $rootScope.pageTitle = 'Thingsboard | ' + translation;
  141 + $rootScope.pageTitle = 'ThingsBoard | ' + translation;
137 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 26
27 27 export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav])
28 28 .directive('tbGrid', Grid)
  29 + .controller('AddItemController', AddItemController)
29 30 .controller('ItemCardController', ItemCardController)
30 31 .directive('tbGridCardContent', GridCardContent)
31 32 .filter('range', RangeFilter)
... ... @@ -342,6 +343,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
342 343 } else {
343 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 352 vm.parentCtl = vm.config.parentCtl || vm;
347 353
... ... @@ -468,7 +474,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
468 474
469 475 function addItem($event) {
470 476 $mdDialog.show({
471   - controller: AddItemController,
  477 + controller: vm.addItemController,
472 478 controllerAs: 'vm',
473 479 templateUrl: vm.addItemTemplateUrl,
474 480 parent: angular.element($document[0].body),
... ...
... ... @@ -1037,6 +1037,7 @@ export default angular.module('thingsboard.locale', [])
1037 1037 "resend-activation": "Resend activation",
1038 1038 "email": "Email",
1039 1039 "email-required": "Email is required.",
  1040 + "invalid-email-format": "Invalid email format.",
1040 1041 "first-name": "First Name",
1041 1042 "last-name": "Last Name",
1042 1043 "description": "Description",
... ... @@ -1044,7 +1045,14 @@ export default angular.module('thingsboard.locale', [])
1044 1045 "always-fullscreen": "Always fullscreen",
1045 1046 "select-user": "Select user",
1046 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 1057 "value": {
1050 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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
... ... @@ -15,8 +15,8 @@
15 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 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
22 22 <h2 translate>user.add</h2>
... ... @@ -32,6 +32,15 @@
32 32 <md-dialog-content>
33 33 <div class="md-dialog-content">
34 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 44 </div>
36 45 </md-dialog-content>
37 46 <md-dialog-actions layout="row">
... ...
... ... @@ -20,6 +20,8 @@ import thingsboardToast from '../services/toast';
20 20
21 21 import UserRoutes from './user.routes';
22 22 import UserController from './user.controller';
  23 +import AddUserController from './add-user.controller';
  24 +import ActivationLinkDialogController from './activation-link.controller';
23 25 import UserDirective from './user.directive';
24 26
25 27 export default angular.module('thingsboard.user', [
... ... @@ -30,5 +32,7 @@ export default angular.module('thingsboard.user', [
30 32 ])
31 33 .config(UserRoutes)
32 34 .controller('UserController', UserController)
  35 + .controller('AddUserController', AddUserController)
  36 + .controller('ActivationLinkDialogController', ActivationLinkDialogController)
33 37 .directive('tbUser', UserDirective)
34 38 .name;
... ...
... ... @@ -15,6 +15,9 @@
15 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 21 <md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
19 22 'user.resend-activation' | translate }}
20 23 </md-button>
... ... @@ -26,9 +29,12 @@
26 29 <fieldset ng-disabled="loading || !isEdit">
27 30 <md-input-container class="md-block">
28 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 35 <div ng-messages="theForm.email.$error">
31 36 <div translate ng-message="required">user.email-required</div>
  37 + <div translate ng-message="pattern">user.invalid-email-format</div>
32 38 </div>
33 39 </md-input-container>
34 40 <md-input-container class="md-block">
... ... @@ -43,7 +49,7 @@
43 49 <label translate>user.description</label>
44 50 <textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
45 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 53 <span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span>
48 54 <section flex layout="column" layout-gt-sm="row">
49 55 <tb-dashboard-autocomplete ng-if="isTenantAdmin()"
... ...
... ... @@ -17,12 +17,13 @@
17 17
18 18 import addUserTemplate from './add-user.tpl.html';
19 19 import userCard from './user-card.tpl.html';
  20 +import activationLinkDialogTemplate from './activation-link.dialog.tpl.html';
20 21
21 22 /* eslint-enable import/no-unresolved, import/default */
22 23
23 24
24 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 28 var tenantId = $stateParams.tenantId;
28 29 var customerId = $stateParams.customerId;
... ... @@ -58,6 +59,7 @@ export default function UserController(userService, toast, $scope, $controller,
58 59 onGridInited: gridInited,
59 60
60 61 addItemTemplateUrl: addUserTemplate,
  62 + addItemController: 'AddUserController',
61 63
62 64 addItemText: function() { return $translate.instant('user.add-user-text') },
63 65 noItemsText: function() { return $translate.instant('user.no-users-text') },
... ... @@ -72,6 +74,7 @@ export default function UserController(userService, toast, $scope, $controller,
72 74 vm.userGridConfig.topIndex = $stateParams.topIndex;
73 75 }
74 76
  77 + vm.displayActivationLink = displayActivationLink;
75 78 vm.resendActivation = resendActivation;
76 79
77 80 initController();
... ... @@ -151,6 +154,29 @@ export default function UserController(userService, toast, $scope, $controller,
151 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 180 function resendActivation(user) {
155 181 userService.sendActivationEmail(user.email).then(function success() {
156 182 toast.showSuccess($translate.instant('user.activation-email-sent-message'));
... ...
... ... @@ -45,6 +45,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
45 45 user: '=',
46 46 isEdit: '=',
47 47 theForm: '=',
  48 + onDisplayActivationLink: '&',
48 49 onResendActivation: '&',
49 50 onDeleteUser: '&'
50 51 }
... ...
... ... @@ -22,6 +22,7 @@
22 22 <tb-user user="vm.grid.operatingItem()"
23 23 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
24 24 the-form="vm.grid.detailsForm"
  25 + on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
25 26 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
26 27 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
27 28 </tb-grid>
... ...