thingsboard-aplcache
Changes
ui/src/app/api/user.service.js 17(+16 -1)
ui/src/app/app.run.js 11(+8 -3)
ui/src/app/locale/locale.constant.js 10(+9 -1)
ui/src/app/user/add-user.controller.js 112(+112 -0)
ui/src/app/user/add-user.tpl.html 13(+11 -2)
ui/src/app/user/index.js 4(+4 -0)
ui/src/app/user/user.controller.js 28(+27 -1)
ui/src/app/user/user.directive.js 1(+1 -0)
ui/src/app/user/user-fieldset.tpl.html 10(+8 -2)
ui/src/app/user/users.tpl.html 1(+1 -0)
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 {
ui/src/app/api/user.service.js 17(+16 -1)
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') {
ui/src/app/app.run.js 11(+8 -3)
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),
ui/src/app/locale/locale.constant.js 10(+9 -1)
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>
ui/src/app/user/add-user.controller.js 112(+112 -0)
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
ui/src/app/user/add-user.tpl.html 13(+11 -2)
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">
ui/src/app/user/index.js 4(+4 -0)
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;
ui/src/app/user/user.controller.js 28(+27 -1)
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'));
ui/src/app/user/user.directive.js 1(+1 -0)
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: '&'
}
ui/src/app/user/user-fieldset.tpl.html 10(+8 -2)
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()"
ui/src/app/user/users.tpl.html 1(+1 -0)
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>