Commit 17825b2cb4425a1153718b14b3a0f19243fb241c

Authored by Igor Kulikov
Committed by GitHub
2 parents c003b20b b1a1c0bd

Merge pull request #224 from thingsboard/feature/TB-70

TB-70: Introduce default entity for alias state parameter. Add actio…
... ... @@ -15,7 +15,7 @@
15 15 "resources": [],
16 16 "templateHtml": "<tb-alarms-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-alarms-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('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}\n",
  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('alarms-table-data-updated', self.ctx.$scope.tableId);\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",
19 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\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\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\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, alarm, 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 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\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}"
... ...
... ... @@ -353,16 +353,20 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
353 353
354 354 function entitiesToEntitiesInfo(entities) {
355 355 var entitiesInfo = [];
356   - for (var d = 0; d < entities.length; d++) {
357   - entitiesInfo.push(entityToEntityInfo(entities[d]));
  356 + if (entities) {
  357 + for (var d = 0; d < entities.length; d++) {
  358 + entitiesInfo.push(entityToEntityInfo(entities[d]));
  359 + }
358 360 }
359 361 return entitiesInfo;
360 362 }
361 363
362 364 function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
363 365 var entitiesInfo = [];
364   - for (var d = 0; d < entityRelations.length; d++) {
365   - entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
  366 + if (entityRelations) {
  367 + for (var d = 0; d < entityRelations.length; d++) {
  368 + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
  369 + }
366 370 }
367 371 return entitiesInfo;
368 372 }
... ... @@ -371,7 +375,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
371 375 function resolveAlias(entityAlias, stateParams) {
372 376 var deferred = $q.defer();
373 377 var filter = entityAlias.filter;
374   - resolveAliasFilter(filter, stateParams, -1).then(
  378 + resolveAliasFilter(filter, stateParams, -1, false).then(
375 379 function (result) {
376 380 var aliasInfo = {
377 381 alias: entityAlias.alias,
... ... @@ -404,10 +408,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
404 408 entityId = stateParams.entityId;
405 409 }
406 410 }
  411 + if (!entityId) {
  412 + entityId = filter.defaultStateEntity;
  413 + }
407 414 return entityId;
408 415 }
409 416
410   - function resolveAliasFilter(filter, stateParams, maxItems) {
  417 + function resolveAliasFilter(filter, stateParams, maxItems, failOnEmpty) {
411 418 var deferred = $q.defer();
412 419 var result = {
413 420 entities: [],
... ... @@ -421,7 +428,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
421 428 case types.aliasFilterType.entityList.value:
422 429 getEntities(filter.entityType, filter.entityList).then(
423 430 function success(entities) {
424   - if (entities && entities.length) {
  431 + if (entities && entities.length || !failOnEmpty) {
425 432 result.entities = entitiesToEntitiesInfo(entities);
426 433 deferred.resolve(result);
427 434 } else {
... ... @@ -436,7 +443,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
436 443 case types.aliasFilterType.entityName.value:
437 444 getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
438 445 function success(entities) {
439   - if (entities && entities.length) {
  446 + if (entities && entities.length || !failOnEmpty) {
440 447 result.entities = entitiesToEntitiesInfo(entities);
441 448 deferred.resolve(result);
442 449 } else {
... ... @@ -457,7 +464,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
457 464 deferred.resolve(result);
458 465 },
459 466 function fail() {
460   - deferred.reject();
  467 + deferred.resolve(result);
461 468 }
462 469 );
463 470 } else {
... ... @@ -467,7 +474,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
467 474 case types.aliasFilterType.assetType.value:
468 475 getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
469 476 function success(entities) {
470   - if (entities && entities.length) {
  477 + if (entities && entities.length || !failOnEmpty) {
471 478 result.entities = entitiesToEntitiesInfo(entities);
472 479 deferred.resolve(result);
473 480 } else {
... ... @@ -482,7 +489,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
482 489 case types.aliasFilterType.deviceType.value:
483 490 getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
484 491 function success(entities) {
485   - if (entities && entities.length) {
  492 + if (entities && entities.length || !failOnEmpty) {
486 493 result.entities = entitiesToEntitiesInfo(entities);
487 494 deferred.resolve(result);
488 495 } else {
... ... @@ -517,8 +524,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
517 524 searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
518 525 entityRelationService.findInfoByQuery(searchQuery).then(
519 526 function success(allRelations) {
520   - if (allRelations && allRelations.length) {
521   - if (angular.isDefined(maxItems) && maxItems > 0) {
  527 + if (allRelations && allRelations.length || !failOnEmpty) {
  528 + if (angular.isDefined(maxItems) && maxItems > 0 && allRelations) {
522 529 var limit = Math.min(allRelations.length, maxItems);
523 530 allRelations.length = limit;
524 531 }
... ... @@ -566,8 +573,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
566 573 }
567 574 findByQueryPromise.then(
568 575 function success(entities) {
569   - if (entities && entities.length) {
570   - if (angular.isDefined(maxItems) && maxItems > 0) {
  576 + if (entities && entities.length || !failOnEmpty) {
  577 + if (angular.isDefined(maxItems) && maxItems > 0 && entities) {
571 578 var limit = Math.min(entities.length, maxItems);
572 579 entities.length = limit;
573 580 }
... ... @@ -720,7 +727,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
720 727
721 728 function checkEntityAlias(entityAlias) {
722 729 var deferred = $q.defer();
723   - resolveAliasFilter(entityAlias.filter, null, 1).then(
  730 + resolveAliasFilter(entityAlias.filter, null, 1, true).then(
724 731 function success(result) {
725 732 if (result.stateEntity) {
726 733 deferred.resolve(true);
... ...
... ... @@ -737,7 +737,15 @@ export default class Subscription {
737 737 this.ctx.datasourceService.subscribeToDatasource(listener);
738 738 }
739 739
740   - if (datasource.unresolvedStateEntity || !datasource.dataKeys.length) {
  740 + var forceUpdate = false;
  741 + if (datasource.unresolvedStateEntity ||
  742 + !datasource.dataKeys.length ||
  743 + (datasource.type === this.ctx.types.datasourceType.entity && !datasource.entityId)
  744 + ) {
  745 + forceUpdate = true;
  746 + }
  747 +
  748 + if (forceUpdate) {
741 749 this.notifyDataLoaded();
742 750 this.onDataUpdated();
743 751 }
... ... @@ -767,7 +775,14 @@ export default class Subscription {
767 775
768 776 this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener);
769 777
770   - if (this.alarmSource.unresolvedStateEntity) {
  778 + var forceUpdate = false;
  779 + if (this.alarmSource.unresolvedStateEntity ||
  780 + (this.alarmSource.type === this.ctx.types.datasourceType.entity && !this.alarmSource.entityId)
  781 + ) {
  782 + forceUpdate = true;
  783 + }
  784 +
  785 + if (forceUpdate) {
771 786 this.notifyDataLoaded();
772 787 this.onDataUpdated();
773 788 }
... ...
... ... @@ -282,10 +282,30 @@ export default function EntityStateController($scope, $location, $state, $stateP
282 282
283 283 function updateLocation() {
284 284 if (vm.stateObject[vm.stateObject.length-1].id) {
285   - $location.search({state : utils.objToBase64(vm.stateObject)});
  285 + if (isDefaultState()) {
  286 + $location.search({state : null});
  287 + } else {
  288 + $location.search({state : utils.objToBase64(vm.stateObject)});
  289 + }
286 290 }
287 291 }
288 292
  293 + function isDefaultState() {
  294 + if (vm.stateObject.length == 1) {
  295 + var state = vm.stateObject[0];
  296 + var rootStateId = dashboardUtils.getRootStateId(vm.states);
  297 + if (state.id == rootStateId && (!state.params || isEmpty(state.params))) {
  298 + return true;
  299 + }
  300 + }
  301 + return false;
  302 + }
289 303
  304 + function isEmpty(map) {
  305 + for(var key in map) {
  306 + return !map.hasOwnProperty(key);
  307 + }
  308 + return true;
  309 + }
290 310
291 311 }
... ...
... ... @@ -71,7 +71,7 @@ export default function EntityAliasDialogController($scope, $mdDialog, $q, $filt
71 71 entity: null,
72 72 stateEntity: false
73 73 }
74   - entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
  74 + entityService.resolveAliasFilter(vm.alias.filter, null, 1, true).then(
75 75 function success(result) {
76 76 validationResult.stateEntity = result.stateEntity;
77 77 var entities = result.entities;
... ...
... ... @@ -95,12 +95,16 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
95 95 }
96 96 });
97 97
98   - scope.$watch('entity', function () {
99   - scope.updateView();
  98 + scope.$watch('entity', function (newVal, prevVal) {
  99 + if (!angular.equals(newVal, prevVal)) {
  100 + scope.updateView();
  101 + }
100 102 });
101 103
102   - scope.$watch('disabled', function () {
103   - scope.updateView();
  104 + scope.$watch('disabled', function (newVal, prevVal) {
  105 + if (!angular.equals(newVal, prevVal)) {
  106 + scope.updateView();
  107 + }
104 108 });
105 109
106 110
... ...
... ... @@ -55,6 +55,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
55 55 break;
56 56 case types.aliasFilterType.stateEntity.value:
57 57 filter.stateEntityParamName = null;
  58 + filter.defaultStateEntity = null;
58 59 break;
59 60 case types.aliasFilterType.assetType.value:
60 61 filter.assetType = null;
... ... @@ -69,6 +70,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
69 70 case types.aliasFilterType.deviceSearchQuery.value:
70 71 filter.rootStateEntity = false;
71 72 filter.stateEntityParamName = null;
  73 + filter.defaultStateEntity = null;
72 74 filter.rootEntity = null;
73 75 filter.direction = types.entitySearchDirection.from;
74 76 filter.maxLevel = 1;
... ...
... ... @@ -66,6 +66,14 @@
66 66 ng-model="filter.stateEntityParamName"
67 67 aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
68 68 </md-input-container>
  69 + <div layout="column">
  70 + <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
  71 + <tb-entity-select flex
  72 + the-form="theForm"
  73 + tb-required="false"
  74 + ng-model="filter.defaultStateEntity">
  75 + </tb-entity-select>
  76 + </div>
69 77 </section>
70 78 <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
71 79 <tb-entity-subtype-autocomplete
... ... @@ -97,27 +105,35 @@
97 105 </section>
98 106 <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
99 107 <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
100   - <div flex layout="row">
  108 + <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
  109 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  110 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  111 + </md-switch>
  112 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  113 + </section>
  114 + <div flex layout="row" ng-if="!filter.rootStateEntity">
101 115 <tb-entity-select flex
102 116 the-form="theForm"
103 117 tb-required="!filter.rootStateEntity"
104 118 ng-disabled="filter.rootStateEntity"
105 119 ng-model="filter.rootEntity">
106 120 </tb-entity-select>
107   - <div layout="column">
108   - <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
109   - <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
110   - <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
111   - aria-label="{{ 'alias.root-state-entity' | translate }}">
112   - </md-switch>
113   - </section>
114   - <md-input-container ng-if="filter.rootStateEntity" class="md-block">
115   - <label translate>alias.state-entity-parameter-name</label>
116   - <input name="stateEntityParamName"
117   - placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
118   - ng-model="filter.stateEntityParamName"
119   - aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
120   - </md-input-container>
  121 + </div>
  122 + <div flex layout="row" ng-if="filter.rootStateEntity">
  123 + <md-input-container class="md-block" style="margin-top: 32px;">
  124 + <label translate>alias.state-entity-parameter-name</label>
  125 + <input name="stateEntityParamName"
  126 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  127 + ng-model="filter.stateEntityParamName"
  128 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  129 + </md-input-container>
  130 + <div flex layout="column">
  131 + <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
  132 + <tb-entity-select flex
  133 + the-form="theForm"
  134 + tb-required="false"
  135 + ng-model="filter.defaultStateEntity">
  136 + </tb-entity-select>
121 137 </div>
122 138 </div>
123 139 <div flex layout="row">
... ... @@ -148,27 +164,35 @@
148 164 </section>
149 165 <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
150 166 <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
151   - <div flex layout="row">
  167 + <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
  168 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  169 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  170 + </md-switch>
  171 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  172 + </section>
  173 + <div flex layout="row" ng-if="!filter.rootStateEntity">
152 174 <tb-entity-select flex
153 175 the-form="theForm"
154 176 tb-required="!filter.rootStateEntity"
155 177 ng-disabled="filter.rootStateEntity"
156 178 ng-model="filter.rootEntity">
157 179 </tb-entity-select>
158   - <div layout="column">
159   - <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
160   - <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
161   - <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
162   - aria-label="{{ 'alias.root-state-entity' | translate }}">
163   - </md-switch>
164   - </section>
165   - <md-input-container ng-if="filter.rootStateEntity" class="md-block">
166   - <label translate>alias.state-entity-parameter-name</label>
167   - <input name="stateEntityParamName"
168   - placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
169   - ng-model="filter.stateEntityParamName"
170   - aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
171   - </md-input-container>
  180 + </div>
  181 + <div flex layout="row" ng-if="filter.rootStateEntity">
  182 + <md-input-container class="md-block" style="margin-top: 32px;">
  183 + <label translate>alias.state-entity-parameter-name</label>
  184 + <input name="stateEntityParamName"
  185 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  186 + ng-model="filter.stateEntityParamName"
  187 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  188 + </md-input-container>
  189 + <div flex layout="column">
  190 + <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
  191 + <tb-entity-select flex
  192 + the-form="theForm"
  193 + tb-required="false"
  194 + ng-model="filter.defaultStateEntity">
  195 + </tb-entity-select>
172 196 </div>
173 197 </div>
174 198 <div flex layout="row">
... ... @@ -207,27 +231,35 @@
207 231 </section>
208 232 <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
209 233 <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
210   - <div flex layout="row">
  234 + <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
  235 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  236 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  237 + </md-switch>
  238 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  239 + </section>
  240 + <div flex layout="row" ng-if="!filter.rootStateEntity">
211 241 <tb-entity-select flex
212 242 the-form="theForm"
213 243 tb-required="!filter.rootStateEntity"
214 244 ng-disabled="filter.rootStateEntity"
215 245 ng-model="filter.rootEntity">
216 246 </tb-entity-select>
217   - <div layout="column">
218   - <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
219   - <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
220   - <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
221   - aria-label="{{ 'alias.root-state-entity' | translate }}">
222   - </md-switch>
223   - </section>
224   - <md-input-container ng-if="filter.rootStateEntity" class="md-block">
225   - <label translate>alias.state-entity-parameter-name</label>
226   - <input name="stateEntityParamName"
227   - placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
228   - ng-model="filter.stateEntityParamName"
229   - aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
230   - </md-input-container>
  247 + </div>
  248 + <div flex layout="row" ng-if="filter.rootStateEntity">
  249 + <md-input-container class="md-block" style="margin-top: 32px;">
  250 + <label translate>alias.state-entity-parameter-name</label>
  251 + <input name="stateEntityParamName"
  252 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  253 + ng-model="filter.stateEntityParamName"
  254 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  255 + </md-input-container>
  256 + <div flex layout="column">
  257 + <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
  258 + <tb-entity-select flex
  259 + the-form="theForm"
  260 + tb-required="false"
  261 + ng-model="filter.defaultStateEntity">
  262 + </tb-entity-select>
231 263 </div>
232 264 </div>
233 265 <div flex layout="row">
... ...
... ... @@ -48,6 +48,7 @@ export default function EntitySelect($compile, $templateCache) {
48 48 }
49 49
50 50 ngModelCtrl.$render = function () {
  51 + destroyWatchers();
51 52 if (ngModelCtrl.$viewValue) {
52 53 var value = ngModelCtrl.$viewValue;
53 54 scope.model.entityType = value.entityType;
... ... @@ -56,19 +57,43 @@ export default function EntitySelect($compile, $templateCache) {
56 57 scope.model.entityType = null;
57 58 scope.model.entityId = null;
58 59 }
  60 + initWatchers();
59 61 }
60 62
61   - scope.$watch('model.entityType', function () {
62   - scope.updateView();
63   - });
  63 + function initWatchers() {
  64 + scope.entityTypeDeregistration = scope.$watch('model.entityType', function (newVal, prevVal) {
  65 + if (!angular.equals(newVal, prevVal)) {
  66 + scope.updateView();
  67 + }
  68 + });
  69 +
  70 + scope.entityIdDeregistration = scope.$watch('model.entityId', function (newVal, prevVal) {
  71 + if (!angular.equals(newVal, prevVal)) {
  72 + scope.updateView();
  73 + }
  74 + });
64 75
65   - scope.$watch('model.entityId', function () {
66   - scope.updateView();
67   - });
  76 + scope.disabledDeregistration = scope.$watch('disabled', function (newVal, prevVal) {
  77 + if (!angular.equals(newVal, prevVal)) {
  78 + scope.updateView();
  79 + }
  80 + });
  81 + }
68 82
69   - scope.$watch('disabled', function () {
70   - scope.updateView();
71   - });
  83 + function destroyWatchers() {
  84 + if (scope.entityTypeDeregistration) {
  85 + scope.entityTypeDeregistration();
  86 + scope.entityTypeDeregistration = null;
  87 + }
  88 + if (scope.entityIdDeregistration) {
  89 + scope.entityIdDeregistration();
  90 + scope.entityIdDeregistration = null;
  91 + }
  92 + if (scope.disabledDeregistration) {
  93 + scope.disabledDeregistration();
  94 + scope.disabledDeregistration = null;
  95 + }
  96 + }
72 97
73 98 $compile(element.contents())(scope);
74 99 }
... ...
... ... @@ -188,6 +188,7 @@ export default angular.module('thingsboard.locale', [])
188 188 "root-state-entity": "Use dashboard state entity as root",
189 189 "root-entity": "Root entity",
190 190 "state-entity-parameter-name": "State entity parameter name",
  191 + "default-state-entity": "Default state entity",
191 192 "default-entity-parameter-name": "By default",
192 193 "max-relation-level": "Max relation level",
193 194 "unlimited-level": "Unlimited level",
... ...
... ... @@ -69,6 +69,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
69 69 vm.displayDetails = true;
70 70 vm.allowAcknowledgment = true;
71 71 vm.allowClear = true;
  72 + vm.actionCellDescriptors = [];
72 73 vm.displayPagination = true;
73 74 vm.defaultPageSize = 10;
74 75 vm.defaultSortOrder = '-'+types.alarmFields.createdTime.value;
... ... @@ -94,6 +95,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
94 95 vm.onReorder = onReorder;
95 96 vm.onPaginate = onPaginate;
96 97 vm.onRowClick = onRowClick;
  98 + vm.onActionButtonClick = onActionButtonClick;
97 99 vm.isCurrent = isCurrent;
98 100 vm.openAlarmDetails = openAlarmDetails;
99 101 vm.ackAlarms = ackAlarms;
... ... @@ -157,6 +159,8 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
157 159
158 160 vm.ctx.widgetActions = [ vm.searchAction ];
159 161
  162 + vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
  163 +
160 164 if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
161 165 vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
162 166 } else {
... ... @@ -280,9 +284,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
280 284 }
281 285
282 286 function onRowClick($event, alarm) {
  287 + if ($event) {
  288 + $event.stopPropagation();
  289 + }
283 290 if (vm.currentAlarm != alarm) {
284 291 vm.currentAlarm = alarm;
285 292 }
  293 + var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
  294 + if (descriptors.length) {
  295 + var entityId;
  296 + var entityName;
  297 + if (vm.currentAlarm && vm.currentAlarm.originator) {
  298 + entityId = vm.currentAlarm.originator;
  299 + entityName = vm.currentAlarm.originatorName;
  300 + }
  301 + vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
  302 + }
  303 + }
  304 +
  305 + function onActionButtonClick($event, alarm, actionDescriptor) {
  306 + if ($event) {
  307 + $event.stopPropagation();
  308 + }
  309 + var entityId;
  310 + var entityName;
  311 + if (alarm && alarm.originator) {
  312 + entityId = alarm.originator;
  313 + entityName = alarm.originatorName;
  314 + }
  315 + vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName);
286 316 }
287 317
288 318 function isCurrent(alarm) {
... ...
... ... @@ -64,6 +64,7 @@
64 64 <tr md-row>
65 65 <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
66 66 <th md-column ng-if="vm.displayDetails"><span>&nbsp</span></th>
  67 + <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
67 68 </tr>
68 69 </thead>
69 70 <tbody md-body>
... ... @@ -83,6 +84,19 @@
83 84 </md-tooltip>
84 85 </md-button>
85 86 </td>
  87 + <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
  88 + ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
  89 + maxWidth: vm.actionCellDescriptors.length*36+'px',
  90 + width: vm.actionCellDescriptors.length*36+'px'}">
  91 + <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
  92 + aria-label="{{ actionDescriptor.displayName }}"
  93 + ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)">
  94 + <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
  95 + <md-tooltip md-direction="top">
  96 + {{ actionDescriptor.displayName }}
  97 + </md-tooltip>
  98 + </md-button>
  99 + </td>
86 100 </tr>
87 101 </tbody>
88 102 </table>
... ...
... ... @@ -107,6 +107,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
107 107 vm.datasources = vm.subscription.datasources;
108 108 initializeConfig();
109 109 updateDatasources();
  110 + updateEntities();
110 111 }
111 112 });
112 113
... ... @@ -276,16 +277,16 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
276 277 }
277 278 if (vm.currentEntity != entity) {
278 279 vm.currentEntity = entity;
279   - var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
280   - if (descriptors.length) {
281   - var entityId;
282   - var entityName;
283   - if (vm.currentEntity) {
284   - entityId = vm.currentEntity.id;
285   - entityName = vm.currentEntity.entityName;
286   - }
287   - vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
  280 + }
  281 + var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
  282 + if (descriptors.length) {
  283 + var entityId;
  284 + var entityName;
  285 + if (vm.currentEntity) {
  286 + entityId = vm.currentEntity.id;
  287 + entityName = vm.currentEntity.entityName;
288 288 }
  289 + vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
289 290 }
290 291 }
291 292
... ...