Commit 86515896dd4328acf415d1d4e691145dcb66bb2a
1 parent
3019bf75
TB-71: Ability to activate users without sending email.
Showing
15 changed files
with
331 additions
and
13 deletions
@@ -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> |
ui/src/app/user/add-user.controller.js
0 → 100644
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> |