31 changed files with 1705 additions and 97 deletions
@ -1,8 +1,11 @@ |
|||||
<ul> |
<ul> |
||||
<li><b>msg:</b> <code>{[key: string]: any}</code> - is a Message payload key/value object. |
<li><b>msg:</b> <code>{[key: string]: any}</code> - is a Message payload key/value object. |
||||
</li> |
</li> |
||||
<li><b>metadata:</b> <code>{[key: string]: string}</code> - is a Message metadata key/value object. |
<li><b>metadata:</b> <code>{[key: string]: string}</code> - is a Message metadata key/value map, where both keys and values are strings. |
||||
</li> |
</li> |
||||
<li><b>msgType:</b> <code>string</code> - is a string Message type. See <a href="https://github.com/thingsboard/thingsboard/blob/ea039008b148453dfa166cf92bc40b26e487e660/ui-ngx/src/app/shared/models/rule-node.models.ts#L338" target="_blank">MessageType</a> enum for common used values. |
<li><b>msgType:</b> <code>string</code> - is a string containing Message type. See <a href="https://github.com/thingsboard/thingsboard/blob/ea039008b148453dfa166cf92bc40b26e487e660/ui-ngx/src/app/shared/models/rule-node.models.ts#L338" target="_blank">MessageType</a> enum for common used values. |
||||
</li> |
</li> |
||||
</ul> |
</ul> |
||||
|
|
||||
|
Enable 'debug mode' for your rule node to see the messages that arrive in near real-time. |
||||
|
See <a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/overview/#debugging" target="_blank">Debugging</a> for more information. |
||||
@ -0,0 +1,27 @@ |
|||||
|
#### Function go back to the first state, after this go to the target state |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
var stateIndex = widgetContext.stateController.getStateIndex(); |
||||
|
while (stateIndex > 0) { |
||||
|
stateIndex -= 1; |
||||
|
backToPrevState(stateIndex); |
||||
|
} |
||||
|
openDashboardState('devices'); |
||||
|
|
||||
|
function backToPrevState(stateIndex) { |
||||
|
widgetContext.stateController.navigatePrevState(stateIndex); |
||||
|
} |
||||
|
|
||||
|
function openDashboardState(statedId) { |
||||
|
var currentState = widgetContext.stateController.getStateId(); |
||||
|
if (currentState !== statedId) { |
||||
|
var params = {}; |
||||
|
widgetContext.stateController.updateState(statedId, params, false); |
||||
|
} |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,44 @@ |
|||||
|
#### Function copy device access token to buffer |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
var $injector = widgetContext.$scope.$injector; |
||||
|
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); |
||||
|
var $translate = $injector.get(widgetContext.servicesMap.get('translate')); |
||||
|
var $scope = widgetContext.$scope; |
||||
|
if (entityId.id && entityId.entityType === 'DEVICE') { |
||||
|
deviceService.getDeviceCredentials(entityId.id, true).subscribe( |
||||
|
(deviceCredentials) => { |
||||
|
var credentialsId = deviceCredentials.credentialsId; |
||||
|
if (copyToClipboard(credentialsId)) { |
||||
|
$scope.showSuccessToast($translate.instant('device.accessTokenCopiedMessage'), 750, "top", "left"); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function copyToClipboard(text) { |
||||
|
if (window.clipboardData && window.clipboardData.setData) { |
||||
|
return window.clipboardData.setData("Text", text); |
||||
|
} |
||||
|
else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { |
||||
|
var textarea = document.createElement("textarea"); |
||||
|
textarea.textContent = text; |
||||
|
textarea.style.position = "fixed"; |
||||
|
document.body.appendChild(textarea); |
||||
|
textarea.select(); |
||||
|
try { |
||||
|
return document.execCommand("copy"); |
||||
|
} |
||||
|
catch (ex) { |
||||
|
console.warn("Copy to clipboard failed.", ex); |
||||
|
return false; |
||||
|
} |
||||
|
document.body.removeChild(textarea); |
||||
|
} |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,33 @@ |
|||||
|
#### Function delete device after confirmation |
||||
|
|
||||
|
```javascript |
||||
|
var $injector = widgetContext.$scope.$injector; |
||||
|
var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs')); |
||||
|
var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); |
||||
|
|
||||
|
openDeleteDeviceDialog(); |
||||
|
|
||||
|
function openDeleteDeviceDialog() { |
||||
|
var title = 'Are you sure you want to delete the device ' + entityName + '?'; |
||||
|
var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!'; |
||||
|
dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe( |
||||
|
function(result) { |
||||
|
if (result) { |
||||
|
deleteDevice(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function deleteDevice() { |
||||
|
deviceService.deleteDevice(entityId.id).subscribe( |
||||
|
function() { |
||||
|
widgetContext.updateAliases(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,35 @@ |
|||||
|
#### Function display alert dialog with entity information |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
var title; |
||||
|
var content; |
||||
|
if (entityName) { |
||||
|
title = entityName + ' details'; |
||||
|
content = '<b>Entity name</b>: ' + entityName; |
||||
|
if (additionalParams && additionalParams.entity) { |
||||
|
var entity = additionalParams.entity; |
||||
|
if (entity.id) { |
||||
|
content += '<br><b>Entity type</b>: ' + entity.id.entityType; |
||||
|
} |
||||
|
if (!isNaN(entity.temperature) && entity.temperature !== '') { |
||||
|
content += '<br><b>Temperature</b>: ' + entity.temperature + ' °C'; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
title = 'No entity information available'; |
||||
|
content = '<b>No entity information available</b>'; |
||||
|
} |
||||
|
|
||||
|
showAlertDialog(title, content); |
||||
|
|
||||
|
function showAlertDialog(title, content) { |
||||
|
setTimeout(function() { |
||||
|
widgetContext.dialogs.alert(title, content).subscribe(); |
||||
|
}, 100); |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,34 @@ |
|||||
|
#### Function open state conditionally with saving particular state parameters |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
var entitySubType; |
||||
|
var $injector = widgetContext.$scope.$injector; |
||||
|
$injector.get(widgetContext.servicesMap.get('entityService')).getEntity(entityId.entityType, entityId.id) |
||||
|
.subscribe(function(data) { |
||||
|
entitySubType = data.type; |
||||
|
if (entitySubType == 'energy meter') { |
||||
|
openDashboardStates('energy_meter_details_view'); |
||||
|
} else if (entitySubType == 'thermometer') { |
||||
|
openDashboardStates('thermometer_details_view'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function openDashboardStates(statedId) { |
||||
|
var stateParams = widgetContext.stateController.getStateParams(); |
||||
|
var params = { |
||||
|
entityId: entityId, |
||||
|
entityName: entityName |
||||
|
}; |
||||
|
|
||||
|
if (stateParams.city) { |
||||
|
params.city = stateParams.city; |
||||
|
} |
||||
|
|
||||
|
widgetContext.stateController.openState(statedId, params, false); |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,18 @@ |
|||||
|
#### Function return to the previous state |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
let stateIndex = widgetContext.stateController.getStateIndex(); |
||||
|
if (stateIndex > 0) { |
||||
|
stateIndex -= 1; |
||||
|
backToPrevState(stateIndex); |
||||
|
} |
||||
|
|
||||
|
function backToPrevState(stateIndex) { |
||||
|
widgetContext.stateController.navigatePrevState(stateIndex); |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,46 @@ |
|||||
|
#### HTML template of dialog to clone device |
||||
|
|
||||
|
```html |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
<form [formGroup]="cloneDeviceFormGroup" (ngSubmit)="save()" style="min-width:320px;"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2>Clone device: {{ deviceName }}</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>Clone device name</mat-label> |
||||
|
<input matInput formControlName="cloneName" required> |
||||
|
<mat-error *ngIf="cloneDeviceFormGroup.get('cloneName').hasError('required')"> |
||||
|
Clone device name is required |
||||
|
</mat-error> |
||||
|
</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) || cloneDeviceFormGroup.invalid || !cloneDeviceFormGroup.dirty"> |
||||
|
Save |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,56 @@ |
|||||
|
#### Function displaying dialog to clone device |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
const $injector = widgetContext.$scope.$injector; |
||||
|
const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); |
||||
|
const attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); |
||||
|
const deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); |
||||
|
const rxjs = widgetContext.rxjs; |
||||
|
|
||||
|
openCloneDeviceDialog(); |
||||
|
|
||||
|
function openCloneDeviceDialog() { |
||||
|
customDialog.customDialog(htmlTemplate, CloneDeviceDialogController).subscribe(); |
||||
|
} |
||||
|
|
||||
|
function CloneDeviceDialogController(instance) { |
||||
|
let vm = instance; |
||||
|
vm.deviceName = entityName; |
||||
|
|
||||
|
vm.cloneDeviceFormGroup = vm.fb.group({ |
||||
|
cloneName: ['', [vm.validators.required]] |
||||
|
}); |
||||
|
|
||||
|
vm.save = function() { |
||||
|
deviceService.getDevice(entityId.id).pipe( |
||||
|
rxjs.mergeMap((origDevice) => { |
||||
|
let cloneDevice = { |
||||
|
name: vm.cloneDeviceFormGroup.get('cloneName').value, |
||||
|
type: origDevice.type |
||||
|
}; |
||||
|
return deviceService.saveDevice(cloneDevice).pipe( |
||||
|
rxjs.mergeMap((newDevice) => { |
||||
|
return attributeService.getEntityAttributes(origDevice.id, 'SERVER_SCOPE').pipe( |
||||
|
rxjs.mergeMap((origAttributes) => { |
||||
|
return attributeService.saveEntityAttributes(newDevice.id, 'SERVER_SCOPE', origAttributes); |
||||
|
}) |
||||
|
); |
||||
|
}) |
||||
|
); |
||||
|
}) |
||||
|
).subscribe(() => { |
||||
|
widgetContext.updateAliases(); |
||||
|
vm.dialogRef.close(null); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
vm.cancel = function() { |
||||
|
vm.dialogRef.close(null); |
||||
|
}; |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,164 @@ |
|||||
|
#### HTML template of dialog to create a device or an asset |
||||
|
|
||||
|
```html |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
<form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup" |
||||
|
(ngSubmit)="save()" class="add-entity-form"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2>Add entity</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"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Entity Name</mat-label> |
||||
|
<input matInput formControlName="entityName" required> |
||||
|
<mat-error *ngIf="addEntityFormGroup.get('entityName').hasError('required')"> |
||||
|
Entity name is required. |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Entity Label</mat-label> |
||||
|
<input matInput formControlName="entityLabel" > |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<tb-entity-type-select |
||||
|
class="mat-block" |
||||
|
formControlName="entityType" |
||||
|
[showLabel]="true" |
||||
|
[allowedEntityTypes]="allowedEntityTypes" |
||||
|
></tb-entity-type-select> |
||||
|
<tb-entity-subtype-autocomplete |
||||
|
fxFlex *ngIf="addEntityFormGroup.get('entityType').value == 'ASSET'" |
||||
|
class="mat-block" |
||||
|
formControlName="type" |
||||
|
[required]="true" |
||||
|
[entityType]="'ASSET'" |
||||
|
></tb-entity-subtype-autocomplete> |
||||
|
<tb-entity-subtype-autocomplete |
||||
|
fxFlex *ngIf="addEntityFormGroup.get('entityType').value != 'ASSET'" |
||||
|
class="mat-block" |
||||
|
formControlName="type" |
||||
|
[required]="true" |
||||
|
[entityType]="'DEVICE'" |
||||
|
></tb-entity-subtype-autocomplete> |
||||
|
</div> |
||||
|
<div formGroupName="attributes" fxLayout="column"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Latitude</mat-label> |
||||
|
<input type="number" step="any" matInput formControlName="latitude"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Longitude</mat-label> |
||||
|
<input type="number" step="any" matInput formControlName="longitude"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Address</mat-label> |
||||
|
<input matInput formControlName="address"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Owner</mat-label> |
||||
|
<input matInput formControlName="owner"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Integer Value</mat-label> |
||||
|
<input type="number" step="1" matInput formControlName="number"> |
||||
|
<mat-error *ngIf="addEntityFormGroup.get('attributes.number').hasError('pattern')"> |
||||
|
Invalid integer value. |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex> |
||||
|
<label class="checkbox-label">Boolean Value</label> |
||||
|
<mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;"> |
||||
|
|
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="relations-list"> |
||||
|
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div> |
||||
|
<div class="body" [fxShow]="relations().length"> |
||||
|
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;"> |
||||
|
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;"> |
||||
|
<div fxFlex fxLayout="column"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field class="mat-block" style="min-width: 100px;"> |
||||
|
<mat-label>Direction</mat-label> |
||||
|
<mat-select formControlName="direction" name="direction"> |
||||
|
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value"> |
||||
|
|
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="relation.get('direction').hasError('required')"> |
||||
|
Relation direction is required. |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-relation-type-autocomplete |
||||
|
fxFlex class="mat-block" |
||||
|
formControlName="relationType" |
||||
|
[required]="true"> |
||||
|
</tb-relation-type-autocomplete> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayout.xs="column"> |
||||
|
<tb-entity-select |
||||
|
fxFlex class="mat-block" |
||||
|
[required]="true" |
||||
|
formControlName="relatedEntity"> |
||||
|
</tb-entity-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div fxLayout="column" fxLayoutAlign="center center"> |
||||
|
<button mat-icon-button color="primary" |
||||
|
aria-label="Remove" |
||||
|
type="button" |
||||
|
(click)="removeRelation(i)" |
||||
|
matTooltip="Remove relation" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
(click)="addRelation()" |
||||
|
matTooltip="Add Relation" |
||||
|
matTooltipPosition="above"> |
||||
|
Add |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</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) || addEntityForm.invalid || !addEntityForm.dirty"> |
||||
|
Create |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,136 @@ |
|||||
|
#### Function displaying dialog to create a device or an asset |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
let $injector = widgetContext.$scope.$injector; |
||||
|
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); |
||||
|
let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); |
||||
|
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); |
||||
|
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); |
||||
|
let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService')); |
||||
|
|
||||
|
openAddEntityDialog(); |
||||
|
|
||||
|
function openAddEntityDialog() { |
||||
|
customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(); |
||||
|
} |
||||
|
|
||||
|
function AddEntityDialogController(instance) { |
||||
|
let vm = instance; |
||||
|
|
||||
|
vm.allowedEntityTypes = ['ASSET', 'DEVICE']; |
||||
|
vm.entitySearchDirection = { |
||||
|
from: "FROM", |
||||
|
to: "TO" |
||||
|
} |
||||
|
|
||||
|
vm.addEntityFormGroup = vm.fb.group({ |
||||
|
entityName: ['', [vm.validators.required]], |
||||
|
entityType: ['DEVICE'], |
||||
|
entityLabel: [null], |
||||
|
type: ['', [vm.validators.required]], |
||||
|
attributes: vm.fb.group({ |
||||
|
latitude: [null], |
||||
|
longitude: [null], |
||||
|
address: [null], |
||||
|
owner: [null], |
||||
|
number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]], |
||||
|
booleanValue: [null] |
||||
|
}), |
||||
|
relations: vm.fb.array([]) |
||||
|
}); |
||||
|
|
||||
|
vm.cancel = function () { |
||||
|
vm.dialogRef.close(null); |
||||
|
}; |
||||
|
|
||||
|
vm.relations = function () { |
||||
|
return vm.addEntityFormGroup.get('relations'); |
||||
|
}; |
||||
|
|
||||
|
vm.addRelation = function () { |
||||
|
vm.relations().push(vm.fb.group({ |
||||
|
relatedEntity: [null, [vm.validators.required]], |
||||
|
relationType: [null, [vm.validators.required]], |
||||
|
direction: [null, [vm.validators.required]] |
||||
|
})); |
||||
|
}; |
||||
|
|
||||
|
vm.removeRelation = function (index) { |
||||
|
vm.relations().removeAt(index); |
||||
|
vm.relations().markAsDirty(); |
||||
|
}; |
||||
|
|
||||
|
vm.save = function () { |
||||
|
vm.addEntityFormGroup.markAsPristine(); |
||||
|
saveEntityObservable().subscribe( |
||||
|
function (entity) { |
||||
|
widgetContext.rxjs.forkJoin([ |
||||
|
saveAttributes(entity.id), |
||||
|
saveRelations(entity.id) |
||||
|
]).subscribe( |
||||
|
function () { |
||||
|
widgetContext.updateAliases(); |
||||
|
vm.dialogRef.close(null); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
function saveEntityObservable() { |
||||
|
const formValues = vm.addEntityFormGroup.value; |
||||
|
let entity = { |
||||
|
name: formValues.entityName, |
||||
|
type: formValues.type, |
||||
|
label: formValues.entityLabel |
||||
|
}; |
||||
|
if (formValues.entityType == 'ASSET') { |
||||
|
return assetService.saveAsset(entity); |
||||
|
} else if (formValues.entityType == 'DEVICE') { |
||||
|
return deviceService.saveDevice(entity); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function saveAttributes(entityId) { |
||||
|
let attributes = vm.addEntityFormGroup.get('attributes').value; |
||||
|
let attributesArray = []; |
||||
|
for (let key in attributes) { |
||||
|
if (attributes[key] !== null) { |
||||
|
attributesArray.push({key: key, value: attributes[key]}); |
||||
|
} |
||||
|
} |
||||
|
if (attributesArray.length > 0) { |
||||
|
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); |
||||
|
} |
||||
|
return widgetContext.rxjs.of([]); |
||||
|
} |
||||
|
|
||||
|
function saveRelations(entityId) { |
||||
|
let relations = vm.addEntityFormGroup.get('relations').value; |
||||
|
let tasks = []; |
||||
|
for (let i = 0; i < relations.length; i++) { |
||||
|
let relation = { |
||||
|
type: relations[i].relationType, |
||||
|
typeGroup: 'COMMON' |
||||
|
}; |
||||
|
if (relations[i].direction == 'FROM') { |
||||
|
relation.to = relations[i].relatedEntity; |
||||
|
relation.from = entityId; |
||||
|
} else { |
||||
|
relation.to = entityId; |
||||
|
relation.from = relations[i].relatedEntity; |
||||
|
} |
||||
|
tasks.push(entityRelationService.saveRelation(relation)); |
||||
|
} |
||||
|
if (tasks.length > 0) { |
||||
|
return widgetContext.rxjs.forkJoin(tasks); |
||||
|
} |
||||
|
return widgetContext.rxjs.of([]); |
||||
|
} |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,81 @@ |
|||||
|
#### HTML template of dialog to create new user |
||||
|
|
||||
|
```html |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
<form [formGroup]="addEntityFormGroup" (ngSubmit)="save()" style="min-width:480px;"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2>Add new User</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"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" |
||||
|
fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Email</mat-label> |
||||
|
<input matInput formControlName="email" required |
||||
|
ng-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,}))$/'> |
||||
|
<mat-error *ngIf="addEntityFormGroup.get('email').hasError('required')"> |
||||
|
Email is required |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="addEntityFormGroup.get('email').hasError('pattern')"> |
||||
|
Invalid email format |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<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> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" |
||||
|
fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Last Name</mat-label> |
||||
|
<input matInput |
||||
|
formControlName="lastName"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" |
||||
|
fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>User activation method</mat-label> |
||||
|
<mat-select matInput formControlName="userActivationMethod"> |
||||
|
<mat-option *ngFor="let activationMethod of activationMethods" [value]="activationMethod.value"> |
||||
|
{{activationMethod.name}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="addEntityFormGroup.get('userActivationMethod').hasError('required')">Please choose activation method</mat-error> |
||||
|
<mat-hint>e.g. Send activation email</mat-hint> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</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) || addEntityFormGroup.invalid || !addEntityFormGroup.dirty"> |
||||
|
Create |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,137 @@ |
|||||
|
#### Function displaying dialog to create new user |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
const $injector = widgetContext.$scope.$injector; |
||||
|
const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); |
||||
|
const userService = $injector.get(widgetContext.servicesMap.get('userService')); |
||||
|
const $scope = widgetContext.$scope; |
||||
|
const rxjs = widgetContext.rxjs; |
||||
|
|
||||
|
openAddUserDialog(); |
||||
|
|
||||
|
function openAddUserDialog() { |
||||
|
customDialog.customDialog(htmlTemplate, AddUserDialogController).subscribe(); |
||||
|
} |
||||
|
|
||||
|
function AddUserDialogController(instance) { |
||||
|
let vm = instance; |
||||
|
|
||||
|
vm.currentUser = widgetContext.currentUser; |
||||
|
|
||||
|
vm.addEntityFormGroup = 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: [''], |
||||
|
lastName: ['', ], |
||||
|
userActivationMethod: ['', [vm.validators.required]] |
||||
|
}); |
||||
|
|
||||
|
vm.activationMethods = [ |
||||
|
{ |
||||
|
value: 'displayActivationLink', |
||||
|
name: 'Display activation link' |
||||
|
}, |
||||
|
{ |
||||
|
value: 'sendActivationMail', |
||||
|
name: 'Send activation email' |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
vm.cancel = function() { |
||||
|
vm.dialogRef.close(null); |
||||
|
}; |
||||
|
|
||||
|
vm.save = function() { |
||||
|
let formObj = vm.addEntityFormGroup.getRawValue(); |
||||
|
let attributes = []; |
||||
|
let sendActivationMail = false; |
||||
|
let newUser = { |
||||
|
email: formObj.email, |
||||
|
firstName: formObj.firstName, |
||||
|
lastName: formObj.lastName, |
||||
|
authority: 'TENANT_ADMIN' |
||||
|
}; |
||||
|
|
||||
|
if (formObj.userActivationMethod === 'sendActivationMail') { |
||||
|
sendActivationMail = true; |
||||
|
} |
||||
|
|
||||
|
userService.saveUser(newUser, sendActivationMail).pipe( |
||||
|
rxjs.mergeMap((user) => { |
||||
|
let activationObs; |
||||
|
if (sendActivationMail) { |
||||
|
activationObs = rxjs.of(null); |
||||
|
} else { |
||||
|
activationObs = userService.getActivationLink(user.id.id); |
||||
|
} |
||||
|
return activationObs.pipe( |
||||
|
rxjs.mergeMap((activationLink) => { |
||||
|
return activationLink ? customDialog.customDialog(activationLinkDialogTemplate, ActivationLinkDialogController, {"activationLink": activationLink}) : rxjs.of(null); |
||||
|
}) |
||||
|
); |
||||
|
}) |
||||
|
).subscribe(() => { |
||||
|
vm.dialogRef.close(null); |
||||
|
}); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function ActivationLinkDialogController(instance) { |
||||
|
let vm = instance; |
||||
|
|
||||
|
vm.activationLink = vm.data.activationLink; |
||||
|
|
||||
|
vm.onActivationLinkCopied = () => { |
||||
|
$scope.showSuccessToast("User activation link has been copied to clipboard", 1200, "bottom", "left", "activationLinkDialogContent"); |
||||
|
}; |
||||
|
|
||||
|
vm.close = () => { |
||||
|
vm.dialogRef.close(null); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
let activationLinkDialogTemplate = `<form style="min-width: 400px; position: relative;"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2 translate>user.activation-link</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="close()" |
||||
|
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 tb-toast toastTarget="activationLinkDialogContent"> |
||||
|
<div class="mat-content" fxLayout="column"> |
||||
|
<span [innerHTML]="'user.activation-link-text' | translate: {activationLink: activationLink}"></span> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<pre class="tb-highlight" fxFlex><code>{{ activationLink }}</code></pre> |
||||
|
<button mat-icon-button |
||||
|
color="primary" |
||||
|
ngxClipboard |
||||
|
cbContent="{{ activationLink }}" |
||||
|
(cbOnSuccess)="onActivationLinkCopied()" |
||||
|
matTooltip="{{ 'user.copy-activation-link' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()"> |
||||
|
{{ 'action.ok' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form>`; |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,196 @@ |
|||||
|
#### HTML template of dialog to edit a device or an asset |
||||
|
|
||||
|
```html |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup" |
||||
|
(ngSubmit)="save()" class="edit-entity-form"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2>Edit </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"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Entity Name</mat-label> |
||||
|
<input matInput formControlName="entityName" required readonly=""> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Entity Label</mat-label> |
||||
|
<input matInput formControlName="entityLabel"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Entity Type</mat-label> |
||||
|
<input matInput formControlName="entityType" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Type</mat-label> |
||||
|
<input matInput formControlName="type" readonly> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div formGroupName="attributes" fxLayout="column"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Latitude</mat-label> |
||||
|
<input type="number" step="any" matInput formControlName="latitude"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Longitude</mat-label> |
||||
|
<input type="number" step="any" matInput formControlName="longitude"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Address</mat-label> |
||||
|
<input matInput formControlName="address"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Owner</mat-label> |
||||
|
<input matInput formControlName="owner"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label>Integer Value</mat-label> |
||||
|
<input type="number" step="1" matInput formControlName="number"> |
||||
|
<mat-error *ngIf="editEntityFormGroup.get('attributes.number').hasError('pattern')"> |
||||
|
Invalid integer value. |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex> |
||||
|
<label class="checkbox-label">Boolean Value</label> |
||||
|
<mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;"> |
||||
|
|
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="relations-list old-relations"> |
||||
|
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div> |
||||
|
<div class="body" [fxShow]="oldRelations().length"> |
||||
|
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="oldRelations" |
||||
|
*ngFor="let relation of oldRelations().controls; let i = index;"> |
||||
|
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;"> |
||||
|
<div fxFlex fxLayout="column"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field class="mat-block" style="min-width: 100px;"> |
||||
|
<mat-label>Direction</mat-label> |
||||
|
<mat-select formControlName="direction" name="direction"> |
||||
|
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value"> |
||||
|
|
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="relation.get('direction').hasError('required')"> |
||||
|
Relation direction is required. |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-relation-type-autocomplete |
||||
|
fxFlex class="mat-block" |
||||
|
formControlName="relationType" |
||||
|
required="true"> |
||||
|
</tb-relation-type-autocomplete> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayout.xs="column"> |
||||
|
<tb-entity-select |
||||
|
fxFlex class="mat-block" |
||||
|
required="true" |
||||
|
formControlName="relatedEntity"> |
||||
|
</tb-entity-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div fxLayout="column" fxLayoutAlign="center center"> |
||||
|
<button mat-icon-button color="primary" |
||||
|
aria-label="Remove" |
||||
|
type="button" |
||||
|
(click)="removeOldRelation(i)" |
||||
|
matTooltip="Remove relation" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="relations-list"> |
||||
|
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">New Relations</div> |
||||
|
<div class="body" [fxShow]="relations().length"> |
||||
|
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;"> |
||||
|
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;"> |
||||
|
<div fxFlex fxLayout="column"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0"> |
||||
|
<mat-form-field class="mat-block" style="min-width: 100px;"> |
||||
|
<mat-label>Direction</mat-label> |
||||
|
<mat-select formControlName="direction" name="direction"> |
||||
|
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value"> |
||||
|
|
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="relation.get('direction').hasError('required')"> |
||||
|
Relation direction is required. |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-relation-type-autocomplete |
||||
|
fxFlex class="mat-block" |
||||
|
formControlName="relationType" |
||||
|
[required]="true"> |
||||
|
</tb-relation-type-autocomplete> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayout.xs="column"> |
||||
|
<tb-entity-select |
||||
|
fxFlex class="mat-block" |
||||
|
[required]="true" |
||||
|
formControlName="relatedEntity"> |
||||
|
</tb-entity-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div fxLayout="column" fxLayoutAlign="center center"> |
||||
|
<button mat-icon-button color="primary" |
||||
|
aria-label="Remove" |
||||
|
type="button" |
||||
|
(click)="removeRelation(i)" |
||||
|
matTooltip="Remove relation" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
(click)="addRelation()" |
||||
|
matTooltip="Add Relation" |
||||
|
matTooltipPosition="above"> |
||||
|
Add |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</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) || editEntityForm.invalid || !editEntityForm.dirty"> |
||||
|
Save |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,224 @@ |
|||||
|
#### Function displaying dialog to edit a device or an asset |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
let $injector = widgetContext.$scope.$injector; |
||||
|
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); |
||||
|
let entityService = $injector.get(widgetContext.servicesMap.get('entityService')); |
||||
|
let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); |
||||
|
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); |
||||
|
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); |
||||
|
let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService')); |
||||
|
|
||||
|
openEditEntityDialog(); |
||||
|
|
||||
|
function openEditEntityDialog() { |
||||
|
customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe(); |
||||
|
} |
||||
|
|
||||
|
function EditEntityDialogController(instance) { |
||||
|
let vm = instance; |
||||
|
|
||||
|
vm.entityName = entityName; |
||||
|
vm.entityType = entityId.entityType; |
||||
|
vm.entitySearchDirection = { |
||||
|
from: "FROM", |
||||
|
to: "TO" |
||||
|
}; |
||||
|
vm.attributes = {}; |
||||
|
vm.oldRelationsData = []; |
||||
|
vm.relationsToDelete = []; |
||||
|
vm.entity = {}; |
||||
|
|
||||
|
vm.editEntityFormGroup = vm.fb.group({ |
||||
|
entityName: ['', [vm.validators.required]], |
||||
|
entityType: [null], |
||||
|
entityLabel: [null], |
||||
|
type: ['', [vm.validators.required]], |
||||
|
attributes: vm.fb.group({ |
||||
|
latitude: [null], |
||||
|
longitude: [null], |
||||
|
address: [null], |
||||
|
owner: [null], |
||||
|
number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]], |
||||
|
booleanValue: [false] |
||||
|
}), |
||||
|
oldRelations: vm.fb.array([]), |
||||
|
relations: vm.fb.array([]) |
||||
|
}); |
||||
|
|
||||
|
getEntityInfo(); |
||||
|
|
||||
|
vm.cancel = function() { |
||||
|
vm.dialogRef.close(null); |
||||
|
}; |
||||
|
|
||||
|
vm.relations = function() { |
||||
|
return vm.editEntityFormGroup.get('relations'); |
||||
|
}; |
||||
|
|
||||
|
vm.oldRelations = function() { |
||||
|
return vm.editEntityFormGroup.get('oldRelations'); |
||||
|
}; |
||||
|
|
||||
|
vm.addRelation = function() { |
||||
|
vm.relations().push(vm.fb.group({ |
||||
|
relatedEntity: [null, [vm.validators.required]], |
||||
|
relationType: [null, [vm.validators.required]], |
||||
|
direction: [null, [vm.validators.required]] |
||||
|
})); |
||||
|
}; |
||||
|
|
||||
|
function addOldRelation() { |
||||
|
vm.oldRelations().push(vm.fb.group({ |
||||
|
relatedEntity: [{value: null, disabled: true}, [vm.validators.required]], |
||||
|
relationType: [{value: null, disabled: true}, [vm.validators.required]], |
||||
|
direction: [{value: null, disabled: true}, [vm.validators.required]] |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
vm.removeRelation = function(index) { |
||||
|
vm.relations().removeAt(index); |
||||
|
vm.relations().markAsDirty(); |
||||
|
}; |
||||
|
|
||||
|
vm.removeOldRelation = function(index) { |
||||
|
vm.oldRelations().removeAt(index); |
||||
|
vm.relationsToDelete.push(vm.oldRelationsData[index]); |
||||
|
vm.oldRelations().markAsDirty(); |
||||
|
}; |
||||
|
|
||||
|
vm.save = function() { |
||||
|
vm.editEntityFormGroup.markAsPristine(); |
||||
|
widgetContext.rxjs.forkJoin([ |
||||
|
saveAttributes(entityId), |
||||
|
saveRelations(entityId), |
||||
|
saveEntity() |
||||
|
]).subscribe( |
||||
|
function () { |
||||
|
widgetContext.updateAliases(); |
||||
|
vm.dialogRef.close(null); |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
function getEntityAttributes(attributes) { |
||||
|
for (var i = 0; i < attributes.length; i++) { |
||||
|
vm.attributes[attributes[i].key] = attributes[i].value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getEntityRelations(relations) { |
||||
|
let relationsFrom = relations[0]; |
||||
|
let relationsTo = relations[1]; |
||||
|
for (let i=0; i < relationsFrom.length; i++) { |
||||
|
let relation = { |
||||
|
direction: 'FROM', |
||||
|
relationType: relationsFrom[i].type, |
||||
|
relatedEntity: relationsFrom[i].to |
||||
|
}; |
||||
|
vm.oldRelationsData.push(relation); |
||||
|
addOldRelation(); |
||||
|
} |
||||
|
for (let i=0; i < relationsTo.length; i++) { |
||||
|
let relation = { |
||||
|
direction: 'TO', |
||||
|
relationType: relationsTo[i].type, |
||||
|
relatedEntity: relationsTo[i].from |
||||
|
}; |
||||
|
vm.oldRelationsData.push(relation); |
||||
|
addOldRelation(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getEntityInfo() { |
||||
|
widgetContext.rxjs.forkJoin([ |
||||
|
entityRelationService.findInfoByFrom(entityId), |
||||
|
entityRelationService.findInfoByTo(entityId), |
||||
|
attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE'), |
||||
|
entityService.getEntity(entityId.entityType, entityId.id) |
||||
|
]).subscribe( |
||||
|
function (data) { |
||||
|
getEntityRelations(data.slice(0,2)); |
||||
|
getEntityAttributes(data[2]); |
||||
|
vm.entity = data[3]; |
||||
|
vm.editEntityFormGroup.patchValue({ |
||||
|
entityName: vm.entity.name, |
||||
|
entityType: vm.entityType, |
||||
|
entityLabel: vm.entity.label, |
||||
|
type: vm.entity.type, |
||||
|
attributes: vm.attributes, |
||||
|
oldRelations: vm.oldRelationsData |
||||
|
}, {emitEvent: false}); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function saveEntity() { |
||||
|
const formValues = vm.editEntityFormGroup.value; |
||||
|
if (vm.entity.label !== formValues.entityLabel){ |
||||
|
vm.entity.label = formValues.entityLabel; |
||||
|
if (formValues.entityType == 'ASSET') { |
||||
|
return assetService.saveAsset(vm.entity); |
||||
|
} else if (formValues.entityType == 'DEVICE') { |
||||
|
return deviceService.saveDevice(vm.entity); |
||||
|
} |
||||
|
} |
||||
|
return widgetContext.rxjs.of([]); |
||||
|
} |
||||
|
|
||||
|
function saveAttributes(entityId) { |
||||
|
let attributes = vm.editEntityFormGroup.get('attributes').value; |
||||
|
let attributesArray = []; |
||||
|
for (let key in attributes) { |
||||
|
if (attributes[key] !== vm.attributes[key]) { |
||||
|
attributesArray.push({key: key, value: attributes[key]}); |
||||
|
} |
||||
|
} |
||||
|
if (attributesArray.length > 0) { |
||||
|
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); |
||||
|
} |
||||
|
return widgetContext.rxjs.of([]); |
||||
|
} |
||||
|
|
||||
|
function saveRelations(entityId) { |
||||
|
let relations = vm.editEntityFormGroup.get('relations').value; |
||||
|
let tasks = []; |
||||
|
for(let i=0; i < relations.length; i++) { |
||||
|
let relation = { |
||||
|
type: relations[i].relationType, |
||||
|
typeGroup: 'COMMON' |
||||
|
}; |
||||
|
if (relations[i].direction == 'FROM') { |
||||
|
relation.to = relations[i].relatedEntity; |
||||
|
relation.from = entityId; |
||||
|
} else { |
||||
|
relation.to = entityId; |
||||
|
relation.from = relations[i].relatedEntity; |
||||
|
} |
||||
|
tasks.push(entityRelationService.saveRelation(relation)); |
||||
|
} |
||||
|
for (let i=0; i < vm.relationsToDelete.length; i++) { |
||||
|
let relation = { |
||||
|
type: vm.relationsToDelete[i].relationType |
||||
|
}; |
||||
|
if (vm.relationsToDelete[i].direction == 'FROM') { |
||||
|
relation.to = vm.relationsToDelete[i].relatedEntity; |
||||
|
relation.from = entityId; |
||||
|
} else { |
||||
|
relation.to = entityId; |
||||
|
relation.from = vm.relationsToDelete[i].relatedEntity; |
||||
|
} |
||||
|
tasks.push(entityRelationService.deleteRelation(relation.from, relation.type, relation.to)); |
||||
|
} |
||||
|
if (tasks.length > 0) { |
||||
|
return widgetContext.rxjs.forkJoin(tasks); |
||||
|
} |
||||
|
return widgetContext.rxjs.of([]); |
||||
|
} |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,42 @@ |
|||||
|
#### HTML template of dialog to add/edit image in entity attribute |
||||
|
|
||||
|
```html |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
<form [formGroup]="editEntity" (ngSubmit)="save()" class="edit-entity-form"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2>Edit {{entityName}} image</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) || loading"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async) && !loading"></div> |
||||
|
<div mat-dialog-content fxLayout="column"> |
||||
|
<div formGroupName="attributes" fxLayout="column"> |
||||
|
<tb-image-input |
||||
|
label="Entity image" |
||||
|
formControlName="image" |
||||
|
></tb-image-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> |
||||
|
<button mat-button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || editEntity.invalid || !editEntity.dirty"> |
||||
|
Save |
||||
|
</button> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="cancel()" cdkFocusInitial> |
||||
|
Cancel |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
@ -0,0 +1,85 @@ |
|||||
|
#### Function displaying dialog to add/edit image in entity attribute |
||||
|
|
||||
|
```javascript |
||||
|
{:code-style="max-height: 400px;"} |
||||
|
let $injector = widgetContext.$scope.$injector; |
||||
|
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); |
||||
|
let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); |
||||
|
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); |
||||
|
let entityService = $injector.get(widgetContext.servicesMap.get('entityService')); |
||||
|
|
||||
|
openAddEntityDialog(); |
||||
|
|
||||
|
function openAddEntityDialog() { |
||||
|
customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(() => {}); |
||||
|
} |
||||
|
|
||||
|
function AddEntityDialogController(instance) { |
||||
|
let vm = instance; |
||||
|
|
||||
|
vm.entityName = entityName; |
||||
|
|
||||
|
vm.attributes = {}; |
||||
|
|
||||
|
vm.editEntity = vm.fb.group({ |
||||
|
attributes: vm.fb.group({ |
||||
|
image: [null] |
||||
|
}) |
||||
|
}); |
||||
|
|
||||
|
getEntityInfo(); |
||||
|
|
||||
|
vm.cancel = function() { |
||||
|
vm.dialogRef.close(null); |
||||
|
}; |
||||
|
|
||||
|
vm.save = function() { |
||||
|
vm.loading = true; |
||||
|
saveAttributes(entityId).subscribe( |
||||
|
() => { |
||||
|
vm.dialogRef.close(null); |
||||
|
}, () =>{ |
||||
|
vm.loading = false; |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
function getEntityAttributes(attributes) { |
||||
|
for (var i = 0; i < attributes.length; i++) { |
||||
|
vm.attributes[attributes[i].key] = attributes[i].value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getEntityInfo() { |
||||
|
vm.loading = true; |
||||
|
attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe( |
||||
|
function (data) { |
||||
|
getEntityAttributes(data); |
||||
|
|
||||
|
vm.editEntity.patchValue({ |
||||
|
attributes: vm.attributes |
||||
|
}, {emitEvent: false}); |
||||
|
vm.loading = false; |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function saveAttributes(entityId) { |
||||
|
let attributes = vm.editEntity.get('attributes').value; |
||||
|
let attributesArray = []; |
||||
|
for (let key in attributes) { |
||||
|
if (attributes[key] !== vm.attributes[key]) { |
||||
|
attributesArray.push({key: key, value: attributes[key]}); |
||||
|
} |
||||
|
} |
||||
|
if (attributesArray.length > 0) { |
||||
|
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); |
||||
|
} |
||||
|
return widgetContext.rxjs.of([]); |
||||
|
} |
||||
|
} |
||||
|
{:copy-code} |
||||
|
``` |
||||
|
|
||||
|
<br> |
||||
|
<br> |
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 37 KiB |
Loading…
Reference in new issue