31 changed files with 1705 additions and 97 deletions
@ -1,8 +1,11 @@ |
|||
<ul> |
|||
<li><b>msg:</b> <code>{[key: string]: any}</code> - is a Message payload key/value object. |
|||
</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><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> |
|||
</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