Commit 1157df05e94e5f0d2e24e26fa6a04d9104dceae8

Authored by Igor Kulikov
1 parent 1d960b96

Entity Admin widgets. Introduce updateAliases method of alias controller.

... ... @@ -13,12 +13,12 @@
13 13 "sizeX": 7.5,
14 14 "sizeY": 6.5,
15 15 "resources": [],
16   - "templateHtml": "<tb-entities-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-entities-table-widget>",
  16 + "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>",
17 17 "templateCss": "",
18   - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
  18 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
19 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
20 20 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
21   - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"device\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\",\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<md-dialog aria-label=\\\"Add entity\\\" style=\\\"width: 480px\\\">\\n <form name=\\\"addDeviceForm\\\" ng-submit=\\\"vm.save()\\\">\\n <md-toolbar>\\n <div class=\\\"md-toolbar-tools\\\">\\n <h2>Add device</h2>\\n <span flex></span>\\n <md-button class=\\\"md-icon-button\\\" ng-click=\\\"vm.cancel()\\\">\\n <ng-md-icon icon=\\\"close\\\" aria-label=\\\"Close\\\"></ng-md-icon>\\n </md-button>\\n </div>\\n </md-toolbar>\\n <md-progress-linear class=\\\"md-warn\\\" md-mode=\\\"indeterminate\\\" ng-disabled=\\\"!$root.loading && !vm.loading\\\" ng-show=\\\"$root.loading || vm.loading\\\"></md-progress-linear>\\n <span style=\\\"min-height: 5px;\\\" flex=\\\"\\\" ng-show=\\\"!$root.loading && !vm.loading\\\"></span>\\n <md-dialog-content>\\n <div class=\\\"md-dialog-content\\\">\\n <fieldset ng-disabled=\\\"$root.loading || vm.loading\\\">\\n <md-input-container flex class=\\\"md-block\\\">\\n <label>Device name</label>\\n <input ng-model=\\\"vm.deviceName\\\" name=deviceName required>\\n <div ng-messages=\\\"addDeviceForm.deviceName.$error\\\">\\n <div ng-message=\\\"required\\\">Device name is required.</div>\\n </div>\\n </md-input-container>\\n <div flex layout=\\\"row\\\">\\n <tb-entity-subtype-autocomplete flex=\\\"50\\\"\\n ng-disabled=\\\"$root.loading || vm.loading\\\"\\n tb-required=\\\"true\\\"\\n the-form=\\\"addDeviceForm\\\"\\n ng-model=\\\"vm.deviceType\\\"\\n entity-type=\\\"vm.types.entityType.device\\\">\\n </tb-entity-subtype-autocomplete>\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Label</label>\\n <input name=\\\"deviceLabel\\\" ng-model=\\\"vm.deviceLabel\\\">\\n </md-input-container>\\n </div>\\n <div flex layout=\\\"row\\\">\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Latitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"latitude\\\" ng-model=\\\"vm.attributes.latitude\\\">\\n </md-input-container>\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Longitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"longitude\\\" ng-model=\\\"vm.attributes.longitude\\\">\\n </md-input-container>\\n </div>\\n </fieldset>\\n </div>\\n </md-dialog-content>\\n <md-dialog-actions>\\n <md-button type=\\\"submit\\\" ng-disabled=\\\"vm.loading || addDeviceForm.$invalid || !addDeviceForm.$dirty\\\" class=\\\"md-raised md-primary\\\">Create</md-button>\\n <md-button ng-click=\\\"vm.cancel()\\\" class=\\\"md-primary\\\">Cancel</md-button>\\n </md-dialog-actions>\\n </form>\\n</md-dialog>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', AddDeviceDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction AddDeviceDialogController($scope, $mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.attributes = {};\\n \\n vm.cancel = () => {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.addDeviceForm.$setPristine();\\n let device = {\\n name: vm.deviceName,\\n type: vm.deviceType,\\n label: vm.deviceLabel\\n };\\n deviceService.saveDevice(device).then(\\n (device) => {\\n saveAttributes(device.id).then(\\n () => {\\n vm.loading = false;\\n updateAliasData();\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\"}],\"actionCellButton\":[{\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\",\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<md-dialog aria-label=\\\"Edit entity\\\" style=\\\"width: 480px\\\">\\n <form name=\\\"editDeviceForm\\\" ng-submit=\\\"vm.save()\\\">\\n <md-toolbar>\\n <div class=\\\"md-toolbar-tools\\\">\\n <h2>Edit device</h2>\\n <span flex></span>\\n <md-button class=\\\"md-icon-button\\\" ng-click=\\\"vm.cancel()\\\">\\n <ng-md-icon icon=\\\"close\\\" aria-label=\\\"Close\\\"></ng-md-icon>\\n </md-button>\\n </div>\\n </md-toolbar>\\n <md-progress-linear class=\\\"md-warn\\\" md-mode=\\\"indeterminate\\\" ng-disabled=\\\"!$root.loading && !vm.loading\\\" ng-show=\\\"$root.loading || vm.loading\\\"></md-progress-linear>\\n <span style=\\\"min-height: 5px;\\\" flex=\\\"\\\" ng-show=\\\"!$root.loading && !vm.loading\\\"></span>\\n <md-dialog-content>\\n <div class=\\\"md-dialog-content\\\">\\n <fieldset ng-disabled=\\\"$root.loading || vm.loading\\\">\\n <md-input-container flex class=\\\"md-block\\\">\\n <label>Device name</label>\\n <input ng-model=\\\"vm.device.name\\\" name=deviceName required>\\n <div ng-messages=\\\"editDeviceForm.deviceName.$error\\\">\\n <div ng-message=\\\"required\\\">Device name is required.</div>\\n </div>\\n </md-input-container>\\n <div flex layout=\\\"row\\\">\\n <tb-entity-subtype-autocomplete flex=\\\"50\\\"\\n ng-disabled=\\\"$root.loading || vm.loading\\\"\\n tb-required=\\\"true\\\"\\n the-form=\\\"editDeviceForm\\\"\\n ng-model=\\\"vm.device.type\\\"\\n entity-type=\\\"vm.types.entityType.device\\\">\\n </tb-entity-subtype-autocomplete>\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Label</label>\\n <input name=\\\"deviceLabel\\\" ng-model=\\\"vm.device.label\\\">\\n </md-input-container>\\n </div>\\n <div flex layout=\\\"row\\\">\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Latitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"latitude\\\" ng-model=\\\"vm.attributes.latitude\\\">\\n </md-input-container>\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Longitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"longitude\\\" ng-model=\\\"vm.attributes.longitude\\\">\\n </md-input-container>\\n </div>\\n </fieldset>\\n </div>\\n </md-dialog-content>\\n <md-dialog-actions>\\n <md-button type=\\\"submit\\\" ng-disabled=\\\"vm.loading || editDeviceForm.$invalid || !editDeviceForm.$dirty\\\" class=\\\"md-raised md-primary\\\">Create</md-button>\\n <md-button ng-click=\\\"vm.cancel()\\\" class=\\\"md-primary\\\">Cancel</md-button>\\n </md-dialog-actions>\\n </form>\\n</md-dialog>\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', EditDeviceDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction EditDeviceDialogController($scope,$mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.loading = false;\\n vm.attributes = {};\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n vm.loading = true;\\n deviceService.getDevice(entityId.id).then(\\n (device) => {\\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\\n (data) => {\\n if (data.length) {\\n getEntityAttributes(data);\\n }\\n vm.device = device;\\n vm.loading = false;\\n } \\n );\\n }\\n )\\n }\\n \\n vm.cancel = function() {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.editDeviceForm.$setPristine();\\n deviceService.saveDevice(vm.device).then(\\n () => {\\n saveAttributes().then(\\n () => {\\n updateAliasData();\\n vm.loading = false;\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n }\\n \\n function getEntityAttributes(attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n }\\n \\n function saveAttributes() {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n console.log(widgetContext);\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\\n\"},{\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\",\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n types = $injector.get('types'),\\n deviceService = $injector.get('deviceService'),\\n $rootScope = $injector.get('$rootScope'),\\n $q = $injector.get('$q');\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n let confirm = $mdDialog.confirm()\\n .targetEvent($event)\\n .title(title)\\n .htmlContent(content)\\n .ariaLabel(title)\\n .cancel('Cancel')\\n .ok('Delete');\\n $mdDialog.show(confirm).then(() => {\\n deleteDevice();\\n })\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).then(\\n () => {\\n updateAliasData();\\n }\\n );\\n}\\n\\nfunction updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\\n });\\n}\"}]}}"
  21 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"addDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"addDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addDeviceForm.invalid || !addDeviceForm.dirty\\\">\\n Create\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"editDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"editDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || editDeviceForm.invalid || !editDeviceForm.dirty\\\">\\n Update\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
22 22 }
23 23 },
24 24 {
... ... @@ -29,13 +29,13 @@
29 29 "sizeX": 7.5,
30 30 "sizeY": 6.5,
31 31 "resources": [],
32   - "templateHtml": "<tb-entities-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-entities-table-widget>",
  32 + "templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>",
33 33 "templateCss": "",
34   - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
  34 + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
35 35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
36 36 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
37   - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"asset\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\",\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<md-dialog aria-label=\\\"Add entity\\\" style=\\\"width: 480px\\\">\\n <form name=\\\"addAssetForm\\\" ng-submit=\\\"vm.save()\\\">\\n <md-toolbar>\\n <div class=\\\"md-toolbar-tools\\\">\\n <h2>Add asset</h2>\\n <span flex></span>\\n <md-button class=\\\"md-icon-button\\\" ng-click=\\\"vm.cancel()\\\">\\n <ng-md-icon icon=\\\"close\\\" aria-label=\\\"Close\\\"></ng-md-icon>\\n </md-button>\\n </div>\\n </md-toolbar>\\n <md-progress-linear class=\\\"md-warn\\\" md-mode=\\\"indeterminate\\\" ng-disabled=\\\"!$root.loading && !vm.loading\\\" ng-show=\\\"$root.loading || vm.loading\\\"></md-progress-linear>\\n <span style=\\\"min-height: 5px;\\\" flex=\\\"\\\" ng-show=\\\"!$root.loading && !vm.loading\\\"></span>\\n <md-dialog-content>\\n <div class=\\\"md-dialog-content\\\">\\n <fieldset ng-disabled=\\\"$root.loading || vm.loading\\\">\\n <md-input-container flex class=\\\"md-block\\\">\\n <label>Asset name</label>\\n <input ng-model=\\\"vm.assetName\\\" name=assetName required>\\n <div ng-messages=\\\"addAssetForm.assetName.$error\\\">\\n <div ng-message=\\\"required\\\">Asset name is required.</div>\\n </div>\\n </md-input-container>\\n <tb-entity-subtype-autocomplete flex=\\\"50\\\"\\n ng-disabled=\\\"$root.loading || vm.loading\\\"\\n tb-required=\\\"true\\\"\\n the-form=\\\"addAssetForm\\\"\\n ng-model=\\\"vm.assetType\\\"\\n entity-type=\\\"vm.types.entityType.asset\\\">\\n </tb-entity-subtype-autocomplete>\\n <div flex layout=\\\"row\\\">\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Latitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"latitude\\\" ng-model=\\\"vm.attributes.latitude\\\">\\n </md-input-container>\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Longitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"longitude\\\" ng-model=\\\"vm.attributes.longitude\\\">\\n </md-input-container>\\n </div>\\n </fieldset>\\n </div>\\n </md-dialog-content>\\n <md-dialog-actions>\\n <md-button type=\\\"submit\\\" ng-disabled=\\\"vm.loading || addAssetForm.$invalid || !addAssetForm.$dirty\\\" class=\\\"md-raised md-primary\\\">Create</md-button>\\n <md-button ng-click=\\\"vm.cancel()\\\" class=\\\"md-primary\\\">Cancel</md-button>\\n </md-dialog-actions>\\n </form>\\n</md-dialog>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', AddAssetDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction AddAssetDialogController($scope, $mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.attributes = {};\\n \\n vm.cancel = () => {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.addAssetForm.$setPristine();\\n let asset = {\\n name: vm.assetName,\\n type: vm.assetType\\n };\\n assetService.saveAsset(asset).then(\\n (asset) => {\\n saveAttributes(asset.id).then(\\n () => {\\n vm.loading = false;\\n updateAliasData();\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\"}],\"actionCellButton\":[{\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\",\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<md-dialog aria-label=\\\"Add entity\\\" style=\\\"width: 480px\\\">\\n <form name=\\\"editAssetForm\\\" ng-submit=\\\"vm.save()\\\">\\n <md-toolbar>\\n <div class=\\\"md-toolbar-tools\\\">\\n <h2>Edit asset</h2>\\n <span flex></span>\\n <md-button class=\\\"md-icon-button\\\" ng-click=\\\"vm.cancel()\\\">\\n <ng-md-icon icon=\\\"close\\\" aria-label=\\\"Close\\\"></ng-md-icon>\\n </md-button>\\n </div>\\n </md-toolbar>\\n <md-progress-linear class=\\\"md-warn\\\" md-mode=\\\"indeterminate\\\" ng-disabled=\\\"!$root.loading && !vm.loading\\\" ng-show=\\\"$root.loading || vm.loading\\\"></md-progress-linear>\\n <span style=\\\"min-height: 5px;\\\" flex=\\\"\\\" ng-show=\\\"!$root.loading && !vm.loading\\\"></span>\\n <md-dialog-content>\\n <div class=\\\"md-dialog-content\\\">\\n <fieldset ng-disabled=\\\"$root.loading || vm.loading\\\">\\n <md-input-container flex class=\\\"md-block\\\">\\n <label>Asset name</label>\\n <input ng-model=\\\"vm.asset.name\\\" name=assetName required>\\n <div ng-messages=\\\"editAssetForm.assetName.$error\\\">\\n <div ng-message=\\\"required\\\">Asset name is required.</div>\\n </div>\\n </md-input-container>\\n <tb-entity-subtype-autocomplete flex=\\\"50\\\"\\n ng-disabled=\\\"$root.loading || vm.loading\\\"\\n tb-required=\\\"true\\\"\\n the-form=\\\"editAssetForm\\\"\\n ng-model=\\\"vm.asset.type\\\"\\n entity-type=\\\"vm.types.entityType.asset\\\">\\n </tb-entity-subtype-autocomplete>\\n <div flex layout=\\\"row\\\">\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Latitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"latitude\\\" ng-model=\\\"vm.attributes.latitude\\\">\\n </md-input-container>\\n <md-input-container flex=\\\"50\\\" class=\\\"md-block\\\">\\n <label>Longitude</label>\\n <input type=\\\"number\\\" step=\\\"any\\\" name=\\\"longitude\\\" ng-model=\\\"vm.attributes.longitude\\\">\\n </md-input-container>\\n </div>\\n </fieldset>\\n </div>\\n </md-dialog-content>\\n <md-dialog-actions>\\n <md-button type=\\\"submit\\\" ng-disabled=\\\"vm.loading || editAssetForm.$invalid || !editAssetForm.$dirty\\\" class=\\\"md-raised md-primary\\\">Create</md-button>\\n <md-button ng-click=\\\"vm.cancel()\\\" class=\\\"md-primary\\\">Cancel</md-button>\\n </md-dialog-actions>\\n </form>\\n</md-dialog>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n $q = $injector.get('$q'),\\n $rootScope = $injector.get('$rootScope'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n attributeService = $injector.get('attributeService');\\n \\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n $mdDialog.show({\\n controller: ['$scope','$mdDialog', EditAssetDialogController],\\n controllerAs: 'vm',\\n template: htmlTemplate,\\n parent: angular.element($document[0].body),\\n targetEvent: $event,\\n multiple: true,\\n clickOutsideToClose: false\\n });\\n}\\n\\nfunction EditAssetDialogController($scope,$mdDialog) {\\n let vm = this;\\n vm.types = types;\\n vm.loading = false;\\n vm.attributes = {};\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n vm.loading = true;\\n assetService.getAsset(entityId.id).then(\\n (asset) => {\\n attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then(\\n (data) => {\\n if (data.length) {\\n getEntityAttributes(data);\\n }\\n vm.asset = asset;\\n vm.loading = false;\\n } \\n );\\n }\\n )\\n }\\n \\n vm.cancel = function() {\\n $mdDialog.hide();\\n };\\n \\n vm.save = () => {\\n vm.loading = true;\\n $scope.editAssetForm.$setPristine();\\n assetService.saveAsset(vm.asset).then(\\n () => {\\n saveAttributes().then(\\n () => {\\n updateAliasData();\\n vm.loading = false;\\n $mdDialog.hide();\\n }\\n );\\n },\\n () => {\\n vm.loading = false;\\n }\\n );\\n }\\n \\n function getEntityAttributes(attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n }\\n \\n function saveAttributes() {\\n let attributesArray = [];\\n for (let key in vm.attributes) {\\n attributesArray.push({key: key, value: vm.attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return $q.when([]);\\n }\\n }\\n \\n function updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n console.log(widgetContext);\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('widgetForceReInit');\\n });\\n }\\n}\\n\"},{\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\",\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet $mdDialog = $injector.get('$mdDialog'),\\n $document = $injector.get('$document'),\\n types = $injector.get('types'),\\n assetService = $injector.get('assetService'),\\n $rootScope = $injector.get('$rootScope'),\\n $q = $injector.get('$q');\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n let confirm = $mdDialog.confirm()\\n .targetEvent($event)\\n .title(title)\\n .htmlContent(content)\\n .ariaLabel(title)\\n .cancel('Cancel')\\n .ok('Delete');\\n $mdDialog.show(confirm).then(() => {\\n deleteAsset();\\n })\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).then(\\n () => {\\n updateAliasData();\\n }\\n );\\n}\\n\\nfunction updateAliasData() {\\n let aliasIds = [];\\n for (let id in widgetContext.aliasController.resolvedAliases) {\\n aliasIds.push(id);\\n }\\n let tasks = [];\\n aliasIds.forEach((aliasId) => {\\n widgetContext.aliasController.setAliasUnresolved(aliasId);\\n tasks.push(widgetContext.aliasController.getAliasInfo(aliasId));\\n });\\n $q.all(tasks).then(() => {\\n $rootScope.$broadcast('entityAliasesChanged', aliasIds);\\n });\\n}\"}]}}"
  37 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addAssetForm=\\\"ngForm\\\" [formGroup]=\\\"addAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"addAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addAssetForm.invalid || !addAssetForm.dirty\\\">\\n Create\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editAssetForm=\\\"ngForm\\\" [formGroup]=\\\"editAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"editAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || editAssetForm.invalid || !editAssetForm.dirty\\\">\\n Update\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
38 38 }
39 39 }
40 40 ]
41   -}
  41 +}
\ No newline at end of file
... ...
... ... @@ -95,6 +95,7 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
95 95 this.customerId = assetEntity.getCustomerId();
96 96 this.type = assetEntity.getType();
97 97 this.name = assetEntity.getName();
  98 + this.label = assetEntity.getLabel();
98 99 this.searchText = assetEntity.getSearchText();
99 100 this.additionalInfo = assetEntity.getAdditionalInfo();
100 101 }
... ...
... ... @@ -26,7 +26,7 @@ import { map } from 'rxjs/operators';
26 26
27 27 export class AliasController implements IAliasController {
28 28
29   - private entityAliasesChangedSubject = new Subject<Array<string>>();
  29 + entityAliasesChangedSubject = new Subject<Array<string>>();
30 30 entityAliasesChanged: Observable<Array<string>> = this.entityAliasesChangedSubject.asObservable();
31 31
32 32 private entityAliasResolvedSubject = new Subject<string>();
... ... @@ -69,6 +69,23 @@ export class AliasController implements IAliasController {
69 69 }
70 70 }
71 71
  72 + updateAliases(aliasIds?: Array<string>) {
  73 + if (!aliasIds) {
  74 + aliasIds = [];
  75 + for (const aliasId of Object.keys(this.resolvedAliases)) {
  76 + aliasIds.push(aliasId);
  77 + }
  78 + }
  79 + const tasks: Observable<AliasInfo>[] = [];
  80 + for (const aliasId of aliasIds) {
  81 + this.setAliasUnresolved(aliasId);
  82 + tasks.push(this.getAliasInfo(aliasId));
  83 + }
  84 + forkJoin(tasks).subscribe(() => {
  85 + this.entityAliasesChangedSubject.next(aliasIds);
  86 + });
  87 + }
  88 +
72 89 dashboardStateChanged() {
73 90 const changedAliasIds: Array<string> = [];
74 91 for (const aliasId of Object.keys(this.resolvedAliasesToStateEntities)) {
... ... @@ -85,7 +102,7 @@ export class AliasController implements IAliasController {
85 102 }
86 103 }
87 104
88   - private setAliasUnresolved(aliasId: string) {
  105 + setAliasUnresolved(aliasId: string) {
89 106 delete this.resolvedAliases[aliasId];
90 107 delete this.resolvedAliasesObservable[aliasId];
91 108 delete this.resolvedAliasesToStateEntities[aliasId];
... ... @@ -225,7 +242,7 @@ export class AliasController implements IAliasController {
225 242 resolveAlarmSource(alarmSource: Datasource): Observable<Datasource> {
226 243 return this.resolveDatasource(alarmSource, true).pipe(
227 244 map((datasources) => {
228   - const datasource = datasources[0];
  245 + const datasource = datasources && datasources.length ? datasources[0] : deepClone(alarmSource);
229 246 if (datasource.type === DatasourceType.function) {
230 247 let name: string;
231 248 if (datasource.name && datasource.name.length) {
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Observable } from 'rxjs';
  17 +import { Observable, Subject } from 'rxjs';
18 18 import { EntityId } from '@app/shared/models/id/entity-id';
19 19 import {
20 20 DataSet,
... ... @@ -99,6 +99,7 @@ export interface IAliasController {
99 99 getEntityAliases(): EntityAliases;
100 100 updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo);
101 101 updateEntityAliases(entityAliases: EntityAliases);
  102 + updateAliases(aliasIds?: Array<string>);
102 103 dashboardStateChanged();
103 104 }
104 105
... ... @@ -218,6 +219,7 @@ export interface SubscriptionEntityInfo {
218 219
219 220 export interface IWidgetSubscription {
220 221
  222 + options: WidgetSubscriptionOptions;
221 223 id: string;
222 224 init$: Observable<IWidgetSubscription>;
223 225 ctx: WidgetSubscriptionContext;
... ...
... ... @@ -127,7 +127,7 @@ export class WidgetSubscription implements IWidgetSubscription {
127 127 targetDeviceName: string;
128 128 executingSubjects: Array<Subject<any>>;
129 129
130   - constructor(subscriptionContext: WidgetSubscriptionContext, options: WidgetSubscriptionOptions) {
  130 + constructor(subscriptionContext: WidgetSubscriptionContext, public options: WidgetSubscriptionOptions) {
131 131 const subscriptionSubject = new ReplaySubject<IWidgetSubscription>();
132 132 this.init$ = subscriptionSubject.asObservable();
133 133 this.ctx = subscriptionContext;
... ... @@ -878,8 +878,8 @@ export class WidgetSubscription implements IWidgetSubscription {
878 878 }
879 879
880 880 private checkAlarmSource(aliasIds: Array<string>): boolean {
881   - if (this.alarmSource && this.alarmSource.entityAliasId) {
882   - return aliasIds.indexOf(this.alarmSource.entityAliasId) > -1;
  881 + if (this.options.alarmSource && this.options.alarmSource.entityAliasId) {
  882 + return aliasIds.indexOf(this.options.alarmSource.entityAliasId) > -1;
883 883 } else {
884 884 return false;
885 885 }
... ... @@ -887,11 +887,14 @@ export class WidgetSubscription implements IWidgetSubscription {
887 887
888 888 private checkSubscriptions(aliasIds: Array<string>): boolean {
889 889 let subscriptionsChanged = false;
890   - for (const listener of this.datasourceListeners) {
891   - if (listener.datasource.entityAliasId) {
892   - if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) {
893   - subscriptionsChanged = true;
894   - break;
  890 + const datasources = this.options.datasources;
  891 + if (datasources) {
  892 + for (const datasource of datasources) {
  893 + if (datasource.entityAliasId) {
  894 + if (aliasIds.indexOf(datasource.entityAliasId) > -1) {
  895 + subscriptionsChanged = true;
  896 + break;
  897 + }
895 898 }
896 899 }
897 900 }
... ...
... ... @@ -20,6 +20,7 @@ import { forkJoin, Observable, of } from 'rxjs';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { EntityId } from '@shared/models/id/entity-id';
22 22 import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models';
  23 +import { isDefinedAndNotNull } from '@core/utils';
23 24
24 25 @Injectable({
25 26 providedIn: 'root'
... ... @@ -31,10 +32,12 @@ export class AttributeService {
31 32 ) { }
32 33
33 34 public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope,
34   - config?: RequestConfig): Observable<Array<AttributeData>> {
35   - return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` +
36   - `${attributeScope}`,
37   - defaultHttpOptionsFromConfig(config));
  35 + keys?: Array<string>, config?: RequestConfig): Observable<Array<AttributeData>> {
  36 + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/${attributeScope}`;
  37 + if (keys && keys.length) {
  38 + url += `?keys=${keys.join(',')}`;
  39 + }
  40 + return this.http.get<Array<AttributeData>>(url, defaultHttpOptionsFromConfig(config));
38 41 }
39 42
40 43 public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
... ... @@ -58,7 +61,7 @@ export class AttributeService {
58 61 const attributesData: {[key: string]: any} = {};
59 62 const deleteAttributes: AttributeData[] = [];
60 63 attributes.forEach((attribute) => {
61   - if (attribute.value !== null) {
  64 + if (isDefinedAndNotNull(attribute.value)) {
62 65 attributesData[attribute.key] = attribute.value;
63 66 } else {
64 67 deleteAttributes.push(attribute);
... ... @@ -85,7 +88,7 @@ export class AttributeService {
85 88 const timeseriesData: {[key: string]: any} = {};
86 89 const deleteTimeseries: AttributeData[] = [];
87 90 timeseries.forEach((attribute) => {
88   - if (attribute.value !== null) {
  91 + if (isDefinedAndNotNull(attribute.value)) {
89 92 timeseriesData[attribute.key] = attribute.value;
90 93 } else {
91 94 deleteTimeseries.push(attribute);
... ...
... ... @@ -109,15 +109,20 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
109 109 }
110 110
111 111 private validate(): boolean {
112   - for (const resource of this.action.customResources) {
113   - if (!resource.url) {
114   - return false;
  112 + if (this.action.customResources) {
  113 + for (const resource of this.action.customResources) {
  114 + if (!resource.url) {
  115 + return false;
  116 + }
115 117 }
116 118 }
117 119 return true;
118 120 }
119 121
120 122 public addResource() {
  123 + if (!this.action.customResources) {
  124 + this.action.customResources = [];
  125 + }
121 126 this.action.customResources.push({url: ''});
122 127 this.notifyActionUpdated();
123 128 }
... ...
... ... @@ -28,7 +28,7 @@ import { PageLink } from '@shared/models/page/page-link';
28 28 import { catchError, map, publishReplay, refCount, share, take, tap } from 'rxjs/operators';
29 29 import { entityTypeTranslations } from '@shared/models/entity-type.models';
30 30 import { UtilsService } from '@core/services/utils.service';
31   -import { deepClone, isUndefined } from '@core/utils';
  31 +import { deepClone, isDefined, isUndefined } from '@core/utils';
32 32
33 33 import customSampleJs from '!raw-loader!./custom-sample-js.raw';
34 34 import customSampleCss from '!raw-loader!./custom-sample-css.raw';
... ... @@ -72,7 +72,7 @@ export function toCustomAction(action: WidgetActionDescriptorInfo): CustomAction
72 72 customFunction: action.customFunction
73 73 };
74 74 }
75   - result.customResources = action ? deepClone(action.customResources) : [];
  75 + result.customResources = action && isDefined(action.customResources) ? deepClone(action.customResources) : [];
76 76 return result;
77 77 }
78 78
... ...
... ... @@ -102,8 +102,4 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
102 102 }));
103 103 }
104 104
105   - widgetForceReInit() {
106   - this.ctx.widgetForceReInit();
107   - }
108   -
109 105 }
... ...
... ... @@ -304,7 +304,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
304 304
305 305 const dataKeys: Array<DataKey> = [];
306 306
307   - const datasource = this.subscription.datasources[0];
  307 + const datasource = this.subscription.options.datasources ? this.subscription.options.datasources[0] : null;
308 308
309 309 if (datasource) {
310 310 datasource.dataKeys.forEach((entityDataKey) => {
... ...
... ... @@ -263,7 +263,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
263 263 this.widgetContext.servicesMap = ServicesMap;
264 264 this.widgetContext.isEdit = this.isEdit;
265 265 this.widgetContext.isMobile = this.isMobile;
266   - this.widgetContext.widgetForceReInit = this.reInit.bind(this);
267 266
268 267 this.widgetContext.subscriptionApi = {
269 268 createSubscription: this.createSubscription.bind(this),
... ... @@ -1096,7 +1095,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
1096 1095 this.cssParser.createStyleElement(actionNamespace, customCss, 'nonamespace');
1097 1096 }
1098 1097 const resourceTasks: Observable<string>[] = [];
1099   - if (customResources.length > 0) {
  1098 + if (isDefined(customResources) && customResources.length > 0) {
1100 1099 customResources.forEach((resource) => {
1101 1100 resourceTasks.push(
1102 1101 this.resources.loadResource(resource.url).pipe(
... ...
... ... @@ -14,7 +14,6 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { ExceptionData } from '@shared/models/error.models';
18 17 import { IDashboardComponent } from '@home/models/dashboard-component.models';
19 18 import {
20 19 DataSet,
... ... @@ -51,7 +50,7 @@ import { AssetService } from '@app/core/http/asset.service';
51 50 import { DialogService } from '@core/services/dialog.service';
52 51 import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
53 52 import { isDefined, formatValue } from '@core/utils';
54   -import { Observable, of, ReplaySubject } from 'rxjs';
  53 +import { forkJoin, Observable, of, ReplaySubject } from 'rxjs';
55 54 import { WidgetSubscription } from '@core/api/widget-subscription';
56 55
57 56 export interface IWidgetAction {
... ... @@ -161,8 +160,6 @@ export class WidgetContext {
161 160 isEdit: boolean;
162 161 isMobile: boolean;
163 162
164   - widgetForceReInit?: () => void;
165   -
166 163 widgetNamespace?: string;
167 164 subscriptionApi?: WidgetSubscriptionApi;
168 165
... ... @@ -187,6 +184,11 @@ export class WidgetContext {
187 184
188 185 ngZone?: NgZone;
189 186
  187 + rxjs = {
  188 + forkJoin,
  189 + of
  190 + };
  191 +
190 192 detectChanges(updateWidgetParams: boolean = false) {
191 193 if (!this.destroyed) {
192 194 if (updateWidgetParams) {
... ... @@ -208,11 +210,14 @@ export class WidgetContext {
208 210 }
209 211 }
210 212
  213 + updateAliases(aliasIds?: Array<string>) {
  214 + this.aliasController.updateAliases(aliasIds);
  215 + }
  216 +
211 217 reset() {
212 218 this.destroyed = false;
213 219 this.hideTitlePanel = false;
214 220 this.widgetTitle = undefined;
215   - this.customHeaderActions = undefined;
216 221 this.widgetActions = undefined;
217 222 }
218 223 }
... ... @@ -226,7 +231,6 @@ export interface IDynamicWidgetComponent {
226 231 rpcErrorText: string;
227 232 rpcRejection: HttpErrorResponse;
228 233 raf: RafService;
229   - widgetForceReInit(): void;
230 234 [key: string]: any;
231 235 }
232 236
... ...