Closed lucasKapf closed 2 years ago
Hi @lucasKapf,
I did something similiar with creating Customers from a Dashboard using Custom Widgets (with HTML Template) . In your case you could use the user.service to create a new User from within a Dashboard: https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/core/http/user.service.ts
public saveUser(user: User, sendActivationMail: boolean = false,
config?: RequestConfig): Observable<User> {
let url = '/api/user';
url += '?sendActivationMail=' + sendActivationMail;
return this.http.post<User>(url, user, defaultHttpOptionsFromConfig(config));
}
I hope this helps
Hello @mde2017, thank you for your quick answer, this helps a lot. I will check it. Just one more question, do you know how to build a User object ?
Nevermind I have found it, it is declared here https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/user.model.ts
The suggested method by @mde2017 is working but far from perfect because it is using the raw http post instead of service methods. I am sharing the source code of dialog to create a user for ThingsBoard PE. It is specific to PE cause it also adds the user to specific group (using "entityGroupService"). @vvlladd28 please share the sample code for CE as well.
HTML:
<form #addEntityForm="ngForm" [formGroup]="addUserFormGroup"
(ngSubmit)="save()" class="add-entity-form" style="width: 600px;">
<mat-toolbar fxLayout="row" color="primary">
<h2>Add Smart Retail Administrator</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label>Email</mat-label>
<input matInput formControlName="email" type="email" required>
<mat-error *ngIf="addUserFormGroup.get('email').hasError('required')">
Email is required
</mat-error>
<mat-error *ngIf="addUserFormGroup.get('email').hasError('pattern')">
Invalid value format
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>First Name</mat-label>
<input matInput formControlName="firstName">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Last Name</mat-label>
<input matInput formControlName="lastName" >
</mat-form-field>
</div>
<mat-form-field fxFlex class="mat-block">
<mat-label>Activation method</mat-label>
<mat-select formControlName="userActivationMethod">
<mat-option *ngFor="let method of activationMethods" [value]="method.value">
{{ method.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || addUserFormGroup.invalid || !addUserFormGroup.dirty">
Add user
</button>
</div>
</form>
JS function:
let $injector = widgetContext.$scope.$injector;
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
let userService = $injector.get(widgetContext.servicesMap.get('userService'));
let entityGroupService = $injector.get(widgetContext.servicesMap.get('entityGroupService'));
let dashboardService = $injector.get(widgetContext.servicesMap.get('dashboardService'));
openAddUserDialog();
function openAddUserDialog() {
customDialog.customDialog(htmlTemplate, AddUserDialogController).subscribe();
}
function AddUserDialogController(instance) {
let vm = instance;
vm.activationMethods = [
{
value: 'displayActivationLink',
name: 'Display activation link'
},
{
value: 'sendActivationMail',
name: 'Send activation email'
}
];
vm.addUserFormGroup = vm.fb.group({
email: ['', [vm.validators.required, vm.validators.pattern(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\_\-0-9]+\.)+[a-zA-Z]{2,}))$/)]],
firstName: [null],
lastName: [null],
userActivationMethod: ['displayActivationLink']
});
vm.cancel = function () {
vm.dialogRef.close(null);
};
vm.save = function () {
var customerId;
if (widgetContext.currentUser.authority === 'TENANT_ADMIN') {
customerId = widgetContext.stateController.getStateParams().entityId;
} else {
customerId = { id: widgetContext.currentUser.customerId, entityType: 'CUSTOMER'};
}
vm.addUserFormGroup.markAsPristine();
const formValues = vm.addUserFormGroup.value;
let user = {
email: formValues.email,
firstName: formValues.firstName,
lastName: formValues.lastName,
authority: 'CUSTOMER_USER',
customerId: customerId
};
const sendActivationMail = (formValues.userActivationMethod === 'sendActivationMail');
widgetContext.rxjs.forkJoin([
getTargetUserGroup(customerId),
getDashboardByName('Smart Supermarket Administration')
]).pipe(
widgetContext.rxjs.switchMap((data) => {
var userGroup = data[0];
var defaultDashboard = data[1];
if (defaultDashboard) {
user.additionalInfo = {
defaultDashboardId: defaultDashboard.id.id,
defaultDashboardFullscreen: true
};
}
return saveUserObservable(userGroup, user, sendActivationMail);
})
).subscribe((user) => {
widgetContext.updateAliases();
if (formValues.userActivationMethod === 'displayActivationLink') {
userService.getActivationLink(user.id.id).subscribe(
(activationLink) => {
displayActivationLink(activationLink).subscribe(
() => {
vm.dialogRef.close(null);
}
);
}
);
} else {
vm.dialogRef.close(null);
}
});
};
function saveUserObservable(userGroup, user, sendActivationMail) {
return userService.saveUser(user, sendActivationMail, userGroup.id.id);
}
function getTargetUserGroup(customerId) {
return entityGroupService.getEntityGroupsByOwnerId(customerId.entityType, customerId.id, 'USER').pipe(
widgetContext.rxjs.switchMap((groups) => {
return getOrCreateUserGroup(groups, 'Smart Retail Administrators', customerId);
})
);
}
function getOrCreateUserGroup(groups, groupName, customerId) {
var usersGroup = groups.find(group => group.name === groupName);
if (usersGroup) {
return widgetContext.rxjs.of(usersGroup);
} else {
usersGroup = {
type: 'USER',
name: groupName,
ownerId: customerId
};
return entityGroupService.saveEntityGroup(usersGroup);
}
}
function getDashboardByName(dashboardName) {
var dashboardsPageLink = widgetContext.pageLink(10, 0, dashboardName);
return dashboardService.getUserDashboards(null, null, dashboardsPageLink, {ignoreLoading: true}).pipe(
widgetContext.rxjs.map((data) => {
if (data.data.length) {
return data.data.find((dashboard) => dashboard.name === dashboardName);
} else {
return null;
}
})
);
}
function displayActivationLink(activationLink) {
const template = '<form style="min-width: 400px;">\n' +
' <mat-toolbar color="primary">\n' +
' <h2 translate>user.activation-link</h2>\n' +
' <span fxFlex></span>\n' +
' <button mat-button mat-icon-button\n' +
' (click)="close()"\n' +
' type="button">\n' +
' <mat-icon class="material-icons">close</mat-icon>\n' +
' </button>\n' +
' </mat-toolbar>\n' +
' <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">\n' +
' </mat-progress-bar>\n' +
' <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>\n' +
' <div mat-dialog-content tb-toast toastTarget="activationLinkDialogContent">\n' +
' <div class="mat-content" fxLayout="column">\n' +
' <span [innerHTML]="\'user.activation-link-text\' | translate: {activationLink: activationLink}"></span>\n' +
' <div fxLayout="row" fxLayoutAlign="start center">\n' +
' <pre class="tb-highlight" fxFlex><code>{{ activationLink }}</code></pre>\n' +
' <button mat-icon-button\n' +
' color="primary"\n' +
' ngxClipboard\n' +
' cbContent="{{ activationLink }}"\n' +
' (cbOnSuccess)="onActivationLinkCopied()"\n' +
' matTooltip="{{ \'user.copy-activation-link\' | translate }}"\n' +
' matTooltipPosition="above">\n' +
' <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>\n' +
' </button>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <div mat-dialog-actions fxLayoutAlign="end center">\n' +
' <button mat-button color="primary"\n' +
' type="button"\n' +
' cdkFocusInitial\n' +
' [disabled]="(isLoading$ | async)"\n' +
' (click)="close()">\n' +
' {{ \'action.ok\' | translate }}\n' +
' </button>\n' +
' </div>\n' +
'</form>';
return customDialog.customDialog(template, ActivationLinkDialogController, {activationLink: activationLink});
}
function ActivationLinkDialogController(instance) {
var vm = instance;
vm.activationLink = instance.data.activationLink;
vm.onActivationLinkCopied = onActivationLinkCopied;
vm.close = close;
function onActivationLinkCopied(){
widgetContext.showSuccessToast(translate.instant('user.activation-link-copied-message'), 1000, 'bottom', 'left', 'activationLinkDialogContent');
}
function close() {
vm.dialogRef.close(null);
}
}
}
@ashvayka Thank you very much for the code, I will do it this way. I'm using the Professional Edition so it should work.
@ashvayka Do you know how to suggest existing dashboard, like it is the case when you edit a user dashboard from the Customer Hierarchy ? I tried this but it doesn't seem to work:
<tb-entity-subtype-autocomplete fxFlex="50"
formControlName="dashboardName"
[required]="true"
[entityType]="'DASHBOARD'">
</tb-entity-subtype-autocomplete>
Or maybe it is not possible to do that from a dashboard ?
Hi @lucasKapf, tb-entity-subtype-autocomplete is for the selection entity subtype. You need to use component tb-entiti-autocomplete for your use case.
Hi @vvlladd28 Thank you for your answer, I will look into it.
Regarding the CustomerGroups on this nice little script, Is there a way to "transfer". the current users assigned groups to the new user? I can only get the group that's mentioned in the script to be transferred.
function getTargetUserGroup(customerId) { return entityGroupService.getEntityGroupsByOwnerId(customerId.entityType, customerId.id, 'USER').pipe( widgetContext.rxjs.switchMap((groups) => { return getOrCreateUserGroup(groups, 'Smart Retail Administrators', customerId); }) ); }
function getOrCreateUserGroup(groups, groupName, customerId) { var usersGroup = groups.find(group => group.name === groupName); if (usersGroup) { return widgetContext.rxjs.of(usersGroup); } else { usersGroup = { type: 'USER', name: groupName, ownerId: customerId }; return entityGroupService.saveEntityGroup(usersGroup); }
Hello,
I have a dashboard which displays all my customers. I would like to know if it is possible to create Users for my customers from this dashboard ?
Best regards,
Lucas