Commit 4e8c05f6707dd93d6b8c7e3da4772319f14d44ca

Authored by Igor Kulikov
Committed by GitHub
2 parents 3ecf91c5 d69e98e8

Merge pull request #175 from thingsboard/feature/TB-62

TB-62: Implement Entities table widget
@@ -20,6 +20,7 @@ import tinycolor from 'tinycolor2'; @@ -20,6 +20,7 @@ import tinycolor from 'tinycolor2';
20 import thingsboardLedLight from '../components/led-light.directive'; 20 import thingsboardLedLight from '../components/led-light.directive';
21 import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget'; 21 import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget';
22 import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget'; 22 import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
  23 +import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget';
23 24
24 import TbFlot from '../widget/lib/flot-widget'; 25 import TbFlot from '../widget/lib/flot-widget';
25 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; 26 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
@@ -34,7 +35,7 @@ import thingsboardTypes from '../common/types.constant'; @@ -34,7 +35,7 @@ import thingsboardTypes from '../common/types.constant';
34 import thingsboardUtils from '../common/utils.service'; 35 import thingsboardUtils from '../common/utils.service';
35 36
36 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget, 37 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
37 - thingsboardAlarmsTableWidget, thingsboardTypes, thingsboardUtils]) 38 + thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardTypes, thingsboardUtils])
38 .factory('widgetService', WidgetService) 39 .factory('widgetService', WidgetService)
39 .name; 40 .name;
40 41
@@ -546,6 +547,14 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ @@ -546,6 +547,14 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
546 547
547 ' }\n\n' + 548 ' }\n\n' +
548 549
  550 + ' self.typeParameters = function() {\n\n' +
  551 + {
  552 + useCustomDatasources: false,
  553 + maxDatasources: -1 //unlimited
  554 + maxDataKeys: -1 //unlimited
  555 + }
  556 + ' }\n\n' +
  557 +
549 ' self.onResize = function() {\n\n' + 558 ' self.onResize = function() {\n\n' +
550 559
551 ' }\n\n' + 560 ' }\n\n' +
@@ -586,10 +595,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ @@ -586,10 +595,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
586 if (angular.isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { 595 if (angular.isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {
587 result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); 596 result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();
588 } 597 }
  598 + if (angular.isFunction(widgetTypeInstance.typeParameters)) {
  599 + result.typeParameters = widgetTypeInstance.typeParameters();
  600 + } else {
  601 + result.typeParameters = {};
  602 + }
589 if (angular.isFunction(widgetTypeInstance.useCustomDatasources)) { 603 if (angular.isFunction(widgetTypeInstance.useCustomDatasources)) {
590 - result.useCustomDatasources = widgetTypeInstance.useCustomDatasources(); 604 + result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources();
591 } else { 605 } else {
592 - result.useCustomDatasources = false; 606 + result.typeParameters.useCustomDatasources = false;
  607 + }
  608 + if (angular.isUndefined(result.typeParameters.maxDatasources)) {
  609 + result.typeParameters.maxDatasources = -1;
  610 + }
  611 + if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
  612 + result.typeParameters.maxDataKeys = -1;
593 } 613 }
594 return result; 614 return result;
595 } catch (e) { 615 } catch (e) {
@@ -629,7 +649,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ @@ -629,7 +649,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
629 if (widgetType.dataKeySettingsSchema) { 649 if (widgetType.dataKeySettingsSchema) {
630 widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema; 650 widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
631 } 651 }
632 - widgetInfo.useCustomDatasources = widgetType.useCustomDatasources; 652 + widgetInfo.typeParameters = widgetType.typeParameters;
633 putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); 653 putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
634 putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem); 654 putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
635 deferred.resolve(widgetInfo); 655 deferred.resolve(widgetInfo);
@@ -156,11 +156,19 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -156,11 +156,19 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
156 }; 156 };
157 157
158 scope.transformTimeseriesDataKeyChip = function (chip) { 158 scope.transformTimeseriesDataKeyChip = function (chip) {
159 - return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries}); 159 + if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
  160 + return null;
  161 + } else {
  162 + return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
  163 + }
160 }; 164 };
161 165
162 scope.transformAttributeDataKeyChip = function (chip) { 166 scope.transformAttributeDataKeyChip = function (chip) {
163 - return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); 167 + if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
  168 + return null;
  169 + } else {
  170 + return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
  171 + }
164 }; 172 };
165 173
166 scope.transformAlarmDataKeyChip = function (chip) { 174 scope.transformAlarmDataKeyChip = function (chip) {
@@ -272,6 +280,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -272,6 +280,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
272 require: "^ngModel", 280 require: "^ngModel",
273 scope: { 281 scope: {
274 widgetType: '=', 282 widgetType: '=',
  283 + maxDataKeys: '=',
275 aliasController: '=', 284 aliasController: '=',
276 datakeySettingsSchema: '=', 285 datakeySettingsSchema: '=',
277 generateDataKey: '&', 286 generateDataKey: '&',
@@ -186,5 +186,10 @@ @@ -186,5 +186,10 @@
186 <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div> 186 <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div>
187 <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div> 187 <div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div>
188 </div> 188 </div>
  189 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" ng-if="maxDataKeys != -1"
  190 + translate="datakey.maximum-timeseries-or-attributes"
  191 + translate-values="{count: maxDataKeys}"
  192 + translate-interpolation="messageformat"
  193 + ></div>
189 </section> 194 </section>
190 </section> 195 </section>
@@ -117,7 +117,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -117,7 +117,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
117 }; 117 };
118 118
119 scope.transformFuncDataKeyChip = function (chip) { 119 scope.transformFuncDataKeyChip = function (chip) {
120 - return scope.generateDataKey({chip: chip, type: types.dataKeyType.function}); 120 + if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
  121 + return null;
  122 + } else {
  123 + return scope.generateDataKey({chip: chip, type: types.dataKeyType.function});
  124 + }
121 }; 125 };
122 126
123 scope.transformAlarmDataKeyChip = function (chip) { 127 scope.transformAlarmDataKeyChip = function (chip) {
@@ -217,6 +221,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -217,6 +221,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
217 require: "^ngModel", 221 require: "^ngModel",
218 scope: { 222 scope: {
219 widgetType: '=', 223 widgetType: '=',
  224 + maxDataKeys: '=',
220 generateDataKey: '&', 225 generateDataKey: '&',
221 datakeySettingsSchema: '=' 226 datakeySettingsSchema: '='
222 }, 227 },
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 --> 17 -->
18 <section class="tb-datasource-func" flex layout='column' 18 <section class="tb-datasource-func" flex layout='column'
19 layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> 19 layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
20 - <md-input-container ng-if="widgetType != types.widgetType.alarm.value" 20 + <md-input-container ng-show="widgetType != types.widgetType.alarm.value"
21 class="tb-datasource-name" md-no-float style="min-width: 200px;"> 21 class="tb-datasource-name" md-no-float style="min-width: 200px;">
22 <input name="datasourceName" 22 <input name="datasourceName"
23 placeholder="{{ 'datasource.name' | translate }}" 23 placeholder="{{ 'datasource.name' | translate }}"
@@ -132,5 +132,10 @@ @@ -132,5 +132,10 @@
132 <div translate ng-message="datasourceKeys" ng-if="widgetType !== types.widgetType.alarm.value" class="tb-error-message">datakey.function-types-required</div> 132 <div translate ng-message="datasourceKeys" ng-if="widgetType !== types.widgetType.alarm.value" class="tb-error-message">datakey.function-types-required</div>
133 <div translate ng-message="datasourceKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div> 133 <div translate ng-message="datasourceKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div>
134 </div> 134 </div>
  135 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" ng-if="maxDataKeys != -1"
  136 + translate="datakey.maximum-function-types"
  137 + translate-values="{count: maxDataKeys}"
  138 + translate-interpolation="messageformat"
  139 + ></div>
135 </section> 140 </section>
136 </section> 141 </section>
@@ -82,6 +82,7 @@ function Datasource($compile, $templateCache, utils, types) { @@ -82,6 +82,7 @@ function Datasource($compile, $templateCache, utils, types) {
82 require: "^ngModel", 82 require: "^ngModel",
83 scope: { 83 scope: {
84 aliasController: '=', 84 aliasController: '=',
  85 + maxDataKeys: '=',
85 widgetType: '=', 86 widgetType: '=',
86 functionsOnly: '=', 87 functionsOnly: '=',
87 datakeySettingsSchema: '=', 88 datakeySettingsSchema: '=',
@@ -27,6 +27,7 @@ @@ -27,6 +27,7 @@
27 <tb-datasource-func flex 27 <tb-datasource-func flex
28 ng-switch-default 28 ng-switch-default
29 ng-model="model" 29 ng-model="model"
  30 + max-data-keys="maxDataKeys"
30 datakey-settings-schema="datakeySettingsSchema" 31 datakey-settings-schema="datakeySettingsSchema"
31 ng-required="model.type === types.datasourceType.function" 32 ng-required="model.type === types.datasourceType.function"
32 widget-type="widgetType" 33 widget-type="widgetType"
@@ -34,6 +35,7 @@ @@ -34,6 +35,7 @@
34 </tb-datasource-func> 35 </tb-datasource-func>
35 <tb-datasource-entity flex 36 <tb-datasource-entity flex
36 ng-model="model" 37 ng-model="model"
  38 + max-data-keys="maxDataKeys"
37 datakey-settings-schema="datakeySettingsSchema" 39 datakey-settings-schema="datakeySettingsSchema"
38 ng-switch-when="entity" 40 ng-switch-when="entity"
39 ng-required="model.type === types.datasourceType.entity" 41 ng-required="model.type === types.datasourceType.entity"
@@ -442,6 +442,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout @@ -442,6 +442,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
442 forceExpandDatasources: '=?', 442 forceExpandDatasources: '=?',
443 isDataEnabled: '=?', 443 isDataEnabled: '=?',
444 widgetType: '=', 444 widgetType: '=',
  445 + typeParameters: '=',
445 widgetSettingsSchema: '=', 446 widgetSettingsSchema: '=',
446 datakeySettingsSchema: '=', 447 datakeySettingsSchema: '=',
447 aliasController: '=', 448 aliasController: '=',
@@ -62,7 +62,14 @@ @@ -62,7 +62,14 @@
62 && isDataEnabled"> 62 && isDataEnabled">
63 <v-pane id="datasources-pane" expanded="true"> 63 <v-pane id="datasources-pane" expanded="true">
64 <v-pane-header> 64 <v-pane-header>
65 - {{ 'widget-config.datasources' | translate }} 65 + <div layout="column">
  66 + <div>{{ 'widget-config.datasources' | translate }}</div>
  67 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" ng-if="typeParameters.maxDatasources != -1"
  68 + translate="widget-config.maximum-datasources"
  69 + translate-values="{count: typeParameters.maxDatasources}"
  70 + translate-interpolation="messageformat"
  71 + ></div>
  72 + </div>
66 </v-pane-header> 73 </v-pane-header>
67 <v-pane-content> 74 <v-pane-content>
68 <div ng-if="datasources.length === 0"> 75 <div ng-if="datasources.length === 0">
@@ -88,6 +95,7 @@ @@ -88,6 +95,7 @@
88 style="padding: 0 0 0 10px; margin: 5px;"> 95 style="padding: 0 0 0 10px; margin: 5px;">
89 <tb-datasource flex ng-model="datasource.value" 96 <tb-datasource flex ng-model="datasource.value"
90 widget-type="widgetType" 97 widget-type="widgetType"
  98 + max-data-keys="typeParameters.maxDataKeys"
91 alias-controller="aliasController" 99 alias-controller="aliasController"
92 functions-only="functionsOnly" 100 functions-only="functionsOnly"
93 datakey-settings-schema="datakeySettingsSchema" 101 datakey-settings-schema="datakeySettingsSchema"
@@ -111,7 +119,7 @@ @@ -111,7 +119,7 @@
111 </div> 119 </div>
112 </div> 120 </div>
113 <div flex layout="row" layout-align="start center"> 121 <div flex layout="row" layout-align="start center">
114 - <md-button ng-disabled="loading" class="md-primary md-raised" 122 + <md-button ng-show="typeParameters.maxDatasources == -1 || datasources.length < typeParameters.maxDatasources" ng-disabled="loading" class="md-primary md-raised"
115 ng-click="addDatasource($event)" aria-label="{{ 'action.add' | translate }}"> 123 ng-click="addDatasource($event)" aria-label="{{ 'action.add' | translate }}">
116 <md-tooltip md-direction="top"> 124 <md-tooltip md-direction="top">
117 {{ 'widget-config.add-datasource' | translate }} 125 {{ 'widget-config.add-datasource' | translate }}
@@ -140,7 +148,7 @@ @@ -140,7 +148,7 @@
140 </v-pane-content> 148 </v-pane-content>
141 </v-pane> 149 </v-pane>
142 </v-accordion> 150 </v-accordion>
143 - <v-accordion id="alarn-source-accordion" control="alarmSourceAccordion" class="vAccordion--default" 151 + <v-accordion id="alarm-source-accordion" control="alarmSourceAccordion" class="vAccordion--default"
144 ng-if="widgetType === types.widgetType.alarm.value && isDataEnabled"> 152 ng-if="widgetType === types.widgetType.alarm.value && isDataEnabled">
145 <v-pane id="alarm-source-pane" expanded="true"> 153 <v-pane id="alarm-source-pane" expanded="true">
146 <v-pane-header> 154 <v-pane-header>
@@ -128,7 +128,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -128,7 +128,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
128 128
129 var widgetTypeInstance; 129 var widgetTypeInstance;
130 130
131 - vm.useCustomDatasources = false; 131 + vm.typeParameters = widgetInfo.typeParameters;
132 132
133 try { 133 try {
134 widgetTypeInstance = new widgetType(widgetContext); 134 widgetTypeInstance = new widgetType(widgetContext);
@@ -154,9 +154,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -154,9 +154,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
154 if (!widgetTypeInstance.onDestroy) { 154 if (!widgetTypeInstance.onDestroy) {
155 widgetTypeInstance.onDestroy = function() {}; 155 widgetTypeInstance.onDestroy = function() {};
156 } 156 }
157 - if (widgetTypeInstance.useCustomDatasources) {  
158 - vm.useCustomDatasources = widgetTypeInstance.useCustomDatasources();  
159 - }  
160 157
161 //TODO: widgets visibility 158 //TODO: widgets visibility
162 159
@@ -502,7 +499,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -502,7 +499,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
502 var subscription = widgetContext.subscriptions[id]; 499 var subscription = widgetContext.subscriptions[id];
503 subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds); 500 subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
504 } 501 }
505 - if (subscriptionChanged && !vm.useCustomDatasources) { 502 + if (subscriptionChanged && !vm.typeParameters.useCustomDatasources) {
506 reInit(); 503 reInit();
507 } 504 }
508 }); 505 });
@@ -513,7 +510,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -513,7 +510,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
513 510
514 configureWidgetElement(); 511 configureWidgetElement();
515 var deferred = $q.defer(); 512 var deferred = $q.defer();
516 - if (!vm.useCustomDatasources) { 513 + if (!vm.typeParameters.useCustomDatasources) {
517 createDefaultSubscription().then( 514 createDefaultSubscription().then(
518 function success() { 515 function success() {
519 subscriptionInited = true; 516 subscriptionInited = true;
@@ -535,7 +532,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -535,7 +532,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
535 function reInit() { 532 function reInit() {
536 onDestroy(); 533 onDestroy();
537 configureWidgetElement(); 534 configureWidgetElement();
538 - if (!vm.useCustomDatasources) { 535 + if (!vm.typeParameters.useCustomDatasources) {
539 createDefaultSubscription().then( 536 createDefaultSubscription().then(
540 function success() { 537 function success() {
541 subscriptionInited = true; 538 subscriptionInited = true;
@@ -575,7 +572,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -575,7 +572,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
575 } catch (e) { 572 } catch (e) {
576 handleWidgetException(e); 573 handleWidgetException(e);
577 } 574 }
578 - if (!vm.useCustomDatasources && widgetContext.defaultSubscription) { 575 + if (!vm.typeParameters.useCustomDatasources && widgetContext.defaultSubscription) {
579 widgetContext.defaultSubscription.subscribe(); 576 widgetContext.defaultSubscription.subscribe();
580 } 577 }
581 } 578 }
@@ -33,6 +33,7 @@ @@ -33,6 +33,7 @@
33 <div class="md-dialog-content" style="padding-top: 0px;"> 33 <div class="md-dialog-content" style="padding-top: 0px;">
34 <fieldset ng-disabled="loading" style="position: relative; height: 600px;"> 34 <fieldset ng-disabled="loading" style="position: relative; height: 600px;">
35 <tb-widget-config widget-type="vm.widget.type" 35 <tb-widget-config widget-type="vm.widget.type"
  36 + type-parameters="vm.widgetInfo.typeParameters"
36 force-expand-datasources="true" 37 force-expand-datasources="true"
37 ng-model="vm.widgetConfig" 38 ng-model="vm.widgetConfig"
38 widget-settings-schema="vm.settingsSchema" 39 widget-settings-schema="vm.settingsSchema"
@@ -918,7 +918,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget @@ -918,7 +918,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
918 } 918 }
919 } 919 }
920 920
921 - if (widgetTypeInfo.useCustomDatasources) { 921 + if (widgetTypeInfo.typeParameters.useCustomDatasources) {
922 addWidget(newWidget); 922 addWidget(newWidget);
923 } else { 923 } else {
924 $mdDialog.show({ 924 $mdDialog.show({
@@ -212,6 +212,7 @@ @@ -212,6 +212,7 @@
212 class="tb-absolute-fill" md-border-bottom> 212 class="tb-absolute-fill" md-border-bottom>
213 <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}"> 213 <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
214 <tb-dashboard 214 <tb-dashboard
  215 + alias-controller="vm.dashboardCtx.aliasController"
215 widgets="vm.timeseriesWidgetTypes" 216 widgets="vm.timeseriesWidgetTypes"
216 is-edit="false" 217 is-edit="false"
217 is-mobile="true" 218 is-mobile="true"
@@ -222,6 +223,7 @@ @@ -222,6 +223,7 @@
222 </md-tab> 223 </md-tab>
223 <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}"> 224 <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
224 <tb-dashboard 225 <tb-dashboard
  226 + alias-controller="vm.dashboardCtx.aliasController"
225 widgets="vm.latestWidgetTypes" 227 widgets="vm.latestWidgetTypes"
226 is-edit="false" 228 is-edit="false"
227 is-mobile="true" 229 is-mobile="true"
@@ -232,6 +234,7 @@ @@ -232,6 +234,7 @@
232 </md-tab> 234 </md-tab>
233 <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}"> 235 <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
234 <tb-dashboard 236 <tb-dashboard
  237 + alias-controller="vm.dashboardCtx.aliasController"
235 widgets="vm.rpcWidgetTypes" 238 widgets="vm.rpcWidgetTypes"
236 is-edit="false" 239 is-edit="false"
237 is-mobile="true" 240 is-mobile="true"
@@ -242,6 +245,7 @@ @@ -242,6 +245,7 @@
242 </md-tab> 245 </md-tab>
243 <md-tab ng-if="vm.alarmWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.alarm' | translate }}"> 246 <md-tab ng-if="vm.alarmWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.alarm' | translate }}">
244 <tb-dashboard 247 <tb-dashboard
  248 + alias-controller="vm.dashboardCtx.aliasController"
245 widgets="vm.alarmWidgetTypes" 249 widgets="vm.alarmWidgetTypes"
246 is-edit="false" 250 is-edit="false"
247 is-mobile="true" 251 is-mobile="true"
@@ -252,6 +256,7 @@ @@ -252,6 +256,7 @@
252 </md-tab> 256 </md-tab>
253 <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}"> 257 <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
254 <tb-dashboard 258 <tb-dashboard
  259 + alias-controller="vm.dashboardCtx.aliasController"
255 widgets="vm.staticWidgetTypes" 260 widgets="vm.staticWidgetTypes"
256 is-edit="false" 261 is-edit="false"
257 is-mobile="true" 262 is-mobile="true"
@@ -40,7 +40,8 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid @@ -40,7 +40,8 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
40 }; 40 };
41 var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; 41 var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
42 var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; 42 var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
43 - scope.isDataEnabled = !widgetInfo.useCustomDatasources; 43 + scope.typeParameters = widgetInfo.typeParameters;
  44 + scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources;
44 if (!settingsSchema || settingsSchema === '') { 45 if (!settingsSchema || settingsSchema === '') {
45 scope.settingsSchema = {}; 46 scope.settingsSchema = {};
46 } else { 47 } else {
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 --> 17 -->
18 <fieldset ng-disabled="loading"> 18 <fieldset ng-disabled="loading">
19 <tb-widget-config widget-type="widget.type" 19 <tb-widget-config widget-type="widget.type"
  20 + type-parameters="typeParameters"
20 ng-model="widgetConfig" 21 ng-model="widgetConfig"
21 is-data-enabled="isDataEnabled" 22 is-data-enabled="isDataEnabled"
22 widget-settings-schema="settingsSchema" 23 widget-settings-schema="settingsSchema"
@@ -499,9 +499,11 @@ export default angular.module('thingsboard.locale', []) @@ -499,9 +499,11 @@ export default angular.module('thingsboard.locale', [])
499 "alarm": "Alarm fields", 499 "alarm": "Alarm fields",
500 "timeseries-required": "Entity timeseries are required.", 500 "timeseries-required": "Entity timeseries are required.",
501 "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", 501 "timeseries-or-attributes-required": "Entity timeseries/attributes are required.",
  502 + "maximum-timeseries-or-attributes": "Maximum { count, select, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }",
502 "alarm-fields-required": "Alarm fields are required.", 503 "alarm-fields-required": "Alarm fields are required.",
503 "function-types": "Function types", 504 "function-types": "Function types",
504 - "function-types-required": "Function types are required." 505 + "function-types-required": "Function types are required.",
  506 + "maximum-function-types": "Maximum { count, select, 1 {1 function type is allowed.} other {# function types are allowed} }"
505 }, 507 },
506 "datasource": { 508 "datasource": {
507 "type": "Datasource type", 509 "type": "Datasource type",
@@ -691,7 +693,13 @@ export default angular.module('thingsboard.locale', []) @@ -691,7 +693,13 @@ export default angular.module('thingsboard.locale', [])
691 "type-alarm": "Alarm", 693 "type-alarm": "Alarm",
692 "type-alarms": "Alarms", 694 "type-alarms": "Alarms",
693 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", 695 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
694 - "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'" 696 + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  697 + "search": "Search entities",
  698 + "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
  699 + "entity-name": "Entity name",
  700 + "details": "Entity details",
  701 + "no-entities-prompt": "No entities found",
  702 + "no-data": "No data to display"
695 }, 703 },
696 "event": { 704 "event": {
697 "event-type": "Event type", 705 "event-type": "Event type",
@@ -1144,6 +1152,7 @@ export default angular.module('thingsboard.locale', []) @@ -1144,6 +1152,7 @@ export default angular.module('thingsboard.locale', [])
1144 "use-dashboard-timewindow": "Use dashboard timewindow", 1152 "use-dashboard-timewindow": "Use dashboard timewindow",
1145 "display-legend": "Display legend", 1153 "display-legend": "Display legend",
1146 "datasources": "Datasources", 1154 "datasources": "Datasources",
  1155 + "maximum-datasources": "Maximum { count, select, 1 {1 datasource is allowed.} other {# datasources are allowed} }",
1147 "datasource-type": "Type", 1156 "datasource-type": "Type",
1148 "datasource-parameters": "Parameters", 1157 "datasource-parameters": "Parameters",
1149 "remove-datasource": "Remove datasource", 1158 "remove-datasource": "Remove datasource",
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import './entities-table-widget.scss';
  18 +
  19 +/* eslint-disable import/no-unresolved, import/default */
  20 +
  21 +import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html';
  22 +//import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html';
  23 +
  24 +/* eslint-enable import/no-unresolved, import/default */
  25 +
  26 +import tinycolor from 'tinycolor2';
  27 +import cssjs from '../../../vendor/css.js/css';
  28 +
  29 +export default angular.module('thingsboard.widgets.entitiesTableWidget', [])
  30 + .directive('tbEntitiesTableWidget', EntitiesTableWidget)
  31 + .name;
  32 +
  33 +/*@ngInject*/
  34 +function EntitiesTableWidget() {
  35 + return {
  36 + restrict: "E",
  37 + scope: true,
  38 + bindToController: {
  39 + tableId: '=',
  40 + ctx: '='
  41 + },
  42 + controller: EntitiesTableWidgetController,
  43 + controllerAs: 'vm',
  44 + templateUrl: entitiesTableWidgetTemplate
  45 + };
  46 +}
  47 +
  48 +/*@ngInject*/
  49 +function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, utils, types) {
  50 + var vm = this;
  51 +
  52 + vm.stylesInfo = {};
  53 + vm.contentsInfo = {};
  54 + vm.columnWidth = {};
  55 +
  56 + vm.showData = true;
  57 + vm.hasData = false;
  58 +
  59 + vm.entities = [];
  60 + vm.entitiesCount = 0;
  61 +
  62 + vm.datasources = null;
  63 + vm.allEntities = null;
  64 +
  65 + vm.currentEntity = null;
  66 +
  67 + vm.displayEntityName = true;
  68 + vm.displayEntityType = true;
  69 + vm.displayActions = false; //TODO: Widget actions
  70 + vm.displayPagination = true;
  71 + vm.defaultPageSize = 10;
  72 + vm.defaultSortOrder = 'entityName';
  73 +
  74 + vm.query = {
  75 + order: vm.defaultSortOrder,
  76 + limit: vm.defaultPageSize,
  77 + page: 1,
  78 + search: null
  79 + };
  80 +
  81 + vm.searchAction = {
  82 + name: 'action.search',
  83 + show: true,
  84 + onAction: function() {
  85 + vm.enterFilterMode();
  86 + },
  87 + icon: 'search'
  88 + };
  89 +
  90 + vm.enterFilterMode = enterFilterMode;
  91 + vm.exitFilterMode = exitFilterMode;
  92 + vm.onReorder = onReorder;
  93 + vm.onPaginate = onPaginate;
  94 + vm.onRowClick = onRowClick;
  95 + vm.isCurrent = isCurrent;
  96 +
  97 + vm.cellStyle = cellStyle;
  98 + vm.cellContent = cellContent;
  99 +
  100 + $scope.$watch('vm.ctx', function() {
  101 + if (vm.ctx) {
  102 + vm.settings = vm.ctx.settings;
  103 + vm.widgetConfig = vm.ctx.widgetConfig;
  104 + vm.subscription = vm.ctx.defaultSubscription;
  105 + vm.datasources = vm.subscription.datasources;
  106 + initializeConfig();
  107 + updateDatasources();
  108 + }
  109 + });
  110 +
  111 + $scope.$watch("vm.query.search", function(newVal, prevVal) {
  112 + if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
  113 + updateEntities();
  114 + }
  115 + });
  116 +
  117 + $scope.$on('entities-table-data-updated', function(event, tableId) {
  118 + if (vm.tableId == tableId) {
  119 + if (vm.subscription) {
  120 + updateEntitiesData(vm.subscription.data);
  121 + updateEntities();
  122 + $scope.$digest();
  123 + }
  124 + }
  125 + });
  126 +
  127 + $scope.$watch(function() { return $mdMedia('gt-xs'); }, function(isGtXs) {
  128 + vm.isGtXs = isGtXs;
  129 + });
  130 +
  131 + $scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) {
  132 + vm.isGtMd = isGtMd;
  133 + if (vm.isGtMd) {
  134 + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
  135 + } else {
  136 + vm.limitOptions = null;
  137 + }
  138 + });
  139 +
  140 + function initializeConfig() {
  141 +
  142 + vm.ctx.widgetActions = [ vm.searchAction ];
  143 +
  144 + if (vm.settings.entitiesTitle && vm.settings.entitiesTitle.length) {
  145 + var translationId = types.translate.customTranslationsPrefix + vm.settings.entitiesTitle;
  146 + var translation = $translate.instant(translationId);
  147 + if (translation != translationId) {
  148 + vm.entitiesTitle = translation + '';
  149 + } else {
  150 + vm.entitiesTitle = vm.settings.entitiesTitle;
  151 + }
  152 + } else {
  153 + vm.entitiesTitle = $translate.instant('entity.entities');
  154 + }
  155 +
  156 + vm.ctx.widgetTitle = vm.entitiesTitle;
  157 +
  158 + vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
  159 + vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true;
  160 + vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true;
  161 + vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
  162 +
  163 + var pageSize = vm.settings.defaultPageSize;
  164 + if (angular.isDefined(pageSize) && Number.isInteger(pageSize) && pageSize > 0) {
  165 + vm.defaultPageSize = pageSize;
  166 + }
  167 +
  168 + if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) {
  169 + vm.defaultSortOrder = vm.settings.defaultSortOrder;
  170 + }
  171 +
  172 + vm.query.order = vm.defaultSortOrder;
  173 + vm.query.limit = vm.defaultPageSize;
  174 + if (vm.isGtMd) {
  175 + vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
  176 + } else {
  177 + vm.limitOptions = null;
  178 + }
  179 +
  180 + var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)';
  181 + var defaultColor = tinycolor(origColor);
  182 + var mdDark = defaultColor.setAlpha(0.87).toRgbString();
  183 + var mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString();
  184 + var mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString();
  185 + //var mdDarkIcon = mdDarkSecondary;
  186 + var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString();
  187 +
  188 + var cssString = 'table.md-table th.md-column {\n'+
  189 + 'color: ' + mdDarkSecondary + ';\n'+
  190 + '}\n'+
  191 + 'table.md-table th.md-column.md-checkbox-column md-checkbox:not(.md-checked) .md-icon {\n'+
  192 + 'border-color: ' + mdDarkSecondary + ';\n'+
  193 + '}\n'+
  194 + 'table.md-table th.md-column md-icon.md-sort-icon {\n'+
  195 + 'color: ' + mdDarkDisabled + ';\n'+
  196 + '}\n'+
  197 + 'table.md-table th.md-column.md-active, table.md-table th.md-column.md-active md-icon {\n'+
  198 + 'color: ' + mdDark + ';\n'+
  199 + '}\n'+
  200 + 'table.md-table td.md-cell {\n'+
  201 + 'color: ' + mdDark + ';\n'+
  202 + 'border-top: 1px '+mdDarkDivider+' solid;\n'+
  203 + '}\n'+
  204 + 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
  205 + 'border-color: ' + mdDarkSecondary + ';\n'+
  206 + '}\n'+
  207 + 'table.md-table td.md-cell.md-placeholder {\n'+
  208 + 'color: ' + mdDarkDisabled + ';\n'+
  209 + '}\n'+
  210 + 'table.md-table td.md-cell md-select > .md-select-value > span.md-select-icon {\n'+
  211 + 'color: ' + mdDarkSecondary + ';\n'+
  212 + '}\n'+
  213 + '.md-table-pagination {\n'+
  214 + 'color: ' + mdDarkSecondary + ';\n'+
  215 + 'border-top: 1px '+mdDarkDivider+' solid;\n'+
  216 + '}\n'+
  217 + '.md-table-pagination .buttons md-icon {\n'+
  218 + 'color: ' + mdDarkSecondary + ';\n'+
  219 + '}\n'+
  220 + '.md-table-pagination md-select:not([disabled]):focus .md-select-value {\n'+
  221 + 'color: ' + mdDarkSecondary + ';\n'+
  222 + '}';
  223 +
  224 + var cssParser = new cssjs();
  225 + cssParser.testMode = false;
  226 + var namespace = 'entities-table-' + hashCode(cssString);
  227 + cssParser.cssPreviewNamespace = namespace;
  228 + cssParser.createStyleElement(namespace, cssString);
  229 + $element.addClass(namespace);
  230 +
  231 + function hashCode(str) {
  232 + var hash = 0;
  233 + var i, char;
  234 + if (str.length === 0) return hash;
  235 + for (i = 0; i < str.length; i++) {
  236 + char = str.charCodeAt(i);
  237 + hash = ((hash << 5) - hash) + char;
  238 + hash = hash & hash;
  239 + }
  240 + return hash;
  241 + }
  242 + }
  243 +
  244 + function enterFilterMode () {
  245 + vm.query.search = '';
  246 + vm.ctx.hideTitlePanel = true;
  247 + }
  248 +
  249 + function exitFilterMode () {
  250 + vm.query.search = null;
  251 + updateEntities();
  252 + vm.ctx.hideTitlePanel = false;
  253 + }
  254 +
  255 + function onReorder () {
  256 + updateEntities();
  257 + }
  258 +
  259 + function onPaginate () {
  260 + updateEntities();
  261 + }
  262 +
  263 + function onRowClick($event, entity) {
  264 + if (vm.currentEntity != entity) {
  265 + vm.currentEntity = entity;
  266 + }
  267 + }
  268 +
  269 + function isCurrent(entity) {
  270 + return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) &&
  271 + (vm.currentEntity.id.id === entity.id.id);
  272 + }
  273 +
  274 + function updateEntities() {
  275 + var result = $filter('orderBy')(vm.allEntities, vm.query.order);
  276 + if (vm.query.search != null) {
  277 + result = $filter('filter')(result, {$: vm.query.search});
  278 + }
  279 + vm.entitiesCount = result.length;
  280 +
  281 + if (vm.displayPagination) {
  282 + var startIndex = vm.query.limit * (vm.query.page - 1);
  283 + vm.entities = result.slice(startIndex, startIndex + vm.query.limit);
  284 + } else {
  285 + vm.entities = result;
  286 + }
  287 + }
  288 +
  289 + function cellStyle(entity, key) {
  290 + var style = {};
  291 + if (entity && key) {
  292 + var styleInfo = vm.stylesInfo[key.label];
  293 + var value = getEntityValue(entity, key);
  294 + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
  295 + try {
  296 + style = styleInfo.cellStyleFunction(value);
  297 + } catch (e) {
  298 + style = {};
  299 + }
  300 + } else {
  301 + style = defaultStyle(key, value);
  302 + }
  303 + }
  304 + if (!style.width) {
  305 + var columnWidth = vm.columnWidth[key.label];
  306 + style.width = columnWidth;
  307 + }
  308 + return style;
  309 + }
  310 +
  311 + function cellContent(entity, key) {
  312 + var strContent = '';
  313 + if (entity && key) {
  314 + var contentInfo = vm.contentsInfo[key.label];
  315 + var value = getEntityValue(entity, key);
  316 + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
  317 + if (angular.isDefined(value)) {
  318 + strContent = '' + value;
  319 + }
  320 + var content = strContent;
  321 + try {
  322 + content = contentInfo.cellContentFunction(value, entity, $filter);
  323 + } catch (e) {
  324 + content = strContent;
  325 + }
  326 + } else {
  327 + content = defaultContent(key, value);
  328 + }
  329 + return content;
  330 + } else {
  331 + return strContent;
  332 + }
  333 + }
  334 +
  335 + function defaultContent(key, value) {
  336 + if (angular.isDefined(value)) {
  337 + return value;
  338 + } else {
  339 + return '';
  340 + }
  341 + }
  342 +
  343 + function defaultStyle(/*key, value*/) {
  344 + return {};
  345 + }
  346 +
  347 + const getDescendantProp = (obj, path) => (
  348 + path.split('.').reduce((acc, part) => acc && acc[part], obj)
  349 + );
  350 +
  351 + function getEntityValue(entity, key) {
  352 + return getDescendantProp(entity, key.name);
  353 + }
  354 +
  355 + function updateEntitiesData(data) {
  356 + if (vm.allEntities) {
  357 + for (var i=0;i<vm.allEntities.length;i++) {
  358 + var entity = vm.allEntities[i];
  359 + for (var a=0;a<vm.dataKeys.length;a++) {
  360 + var dataKey = vm.dataKeys[a];
  361 + var index = i * vm.dataKeys.length + a;
  362 + var keyData = data[index].data;
  363 + if (keyData && keyData.length && keyData[0].length > 1) {
  364 + var value = keyData[0][1];
  365 + entity[dataKey.name] = value;
  366 + } else {
  367 + entity[dataKey.name] = '';
  368 + }
  369 + }
  370 + }
  371 + }
  372 + }
  373 +
  374 + function updateDatasources() {
  375 +
  376 + vm.stylesInfo = {};
  377 + vm.contentsInfo = {};
  378 + vm.columnWidth = {};
  379 + vm.dataKeys = [];
  380 + vm.allEntities = [];
  381 +
  382 + var datasource;
  383 + var dataKey;
  384 +
  385 + datasource = vm.datasources[0];
  386 +
  387 + vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle);
  388 +
  389 + for (var d = 0; d < datasource.dataKeys.length; d++ ) {
  390 + dataKey = angular.copy(datasource.dataKeys[d]);
  391 + if (dataKey.type == types.dataKeyType.function) {
  392 + dataKey.name = dataKey.label;
  393 + }
  394 + vm.dataKeys.push(dataKey);
  395 +
  396 + var translationId = types.translate.customTranslationsPrefix + dataKey.label;
  397 + var translation = $translate.instant(translationId);
  398 + if (translation != translationId) {
  399 + dataKey.title = translation + '';
  400 + } else {
  401 + dataKey.title = dataKey.label;
  402 + }
  403 +
  404 + var keySettings = dataKey.settings;
  405 +
  406 + var cellStyleFunction = null;
  407 + var useCellStyleFunction = false;
  408 +
  409 + if (keySettings.useCellStyleFunction === true) {
  410 + if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {
  411 + try {
  412 + cellStyleFunction = new Function('value', keySettings.cellStyleFunction);
  413 + useCellStyleFunction = true;
  414 + } catch (e) {
  415 + cellStyleFunction = null;
  416 + useCellStyleFunction = false;
  417 + }
  418 + }
  419 + }
  420 +
  421 + vm.stylesInfo[dataKey.label] = {
  422 + useCellStyleFunction: useCellStyleFunction,
  423 + cellStyleFunction: cellStyleFunction
  424 + };
  425 +
  426 + var cellContentFunction = null;
  427 + var useCellContentFunction = false;
  428 +
  429 + if (keySettings.useCellContentFunction === true) {
  430 + if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {
  431 + try {
  432 + cellContentFunction = new Function('value, entity, filter', keySettings.cellContentFunction);
  433 + useCellContentFunction = true;
  434 + } catch (e) {
  435 + cellContentFunction = null;
  436 + useCellContentFunction = false;
  437 + }
  438 + }
  439 + }
  440 +
  441 + vm.contentsInfo[dataKey.label] = {
  442 + useCellContentFunction: useCellContentFunction,
  443 + cellContentFunction: cellContentFunction
  444 + };
  445 +
  446 + var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
  447 + vm.columnWidth[dataKey.label] = columnWidth;
  448 + }
  449 +
  450 + for (var i=0;i<vm.datasources.length;i++) {
  451 + datasource = vm.datasources[i];
  452 + var entity = {
  453 + id: {}
  454 + };
  455 + entity.entityName = datasource.entityName;
  456 + if (datasource.entityId) {
  457 + entity.id.id = datasource.entityId;
  458 + }
  459 + if (datasource.entityType) {
  460 + entity.id.entityType = datasource.entityType;
  461 + entity.entityType = $translate.instant(types.entityTypeTranslations[datasource.entityType].type) + '';
  462 + } else {
  463 + entity.entityType = '';
  464 + }
  465 + for (d = 0; d < vm.dataKeys.length; d++) {
  466 + dataKey = vm.dataKeys[d];
  467 + entity[dataKey.name] = '';
  468 + }
  469 + vm.allEntities.push(entity);
  470 + }
  471 +
  472 + }
  473 +
  474 +}
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +.tb-has-timewindow {
  18 + .tb-entities-table {
  19 + md-toolbar {
  20 + min-height: 60px;
  21 + max-height: 60px;
  22 + &.md-table-toolbar {
  23 + .md-toolbar-tools {
  24 + max-height: 60px;
  25 + }
  26 + }
  27 + }
  28 + }
  29 +}
  30 +
  31 +.tb-entities-table {
  32 +
  33 + md-toolbar {
  34 + min-height: 39px;
  35 + max-height: 39px;
  36 + &.md-table-toolbar {
  37 + .md-toolbar-tools {
  38 + max-height: 39px;
  39 + }
  40 + }
  41 + }
  42 +
  43 + &.tb-data-table {
  44 + table.md-table, table.md-table.md-row-select {
  45 + tbody {
  46 + tr {
  47 + td {
  48 + &.tb-action-cell {
  49 + min-width: 36px;
  50 + max-width: 36px;
  51 + width: 36px;
  52 + }
  53 + }
  54 + }
  55 + }
  56 + }
  57 + }
  58 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-absolute-fill tb-entities-table tb-data-table" layout="column">
  19 + <div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column">
  20 + <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
  21 + <div class="md-toolbar-tools">
  22 + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
  23 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
  24 + <md-tooltip md-direction="top">
  25 + {{'entity.search' | translate}}
  26 + </md-tooltip>
  27 + </md-button>
  28 + <md-input-container flex>
  29 + <label>&nbsp;</label>
  30 + <input ng-model="vm.query.search" placeholder="{{'entity.search' | translate}}"/>
  31 + </md-input-container>
  32 + <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
  33 + <md-icon aria-label="Close" class="material-icons">close</md-icon>
  34 + <md-tooltip md-direction="top">
  35 + {{ 'action.close' | translate }}
  36 + </md-tooltip>
  37 + </md-button>
  38 + </div>
  39 + </md-toolbar>
  40 + <md-table-container flex>
  41 + <table md-table>
  42 + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
  43 + <tr md-row>
  44 + <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span translate>entity.entity-name</span></th>
  45 + <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
  46 + <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
  47 + <th md-column ng-if="vm.displayActions"><span>&nbsp</span></th>
  48 + </tr>
  49 + </thead>
  50 + <tbody md-body>
  51 + <tr ng-show="vm.entities.length" md-row md-select="entity"
  52 + md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities"
  53 + ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}">
  54 + <td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td>
  55 + <td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td>
  56 + <td md-cell flex ng-repeat="key in vm.dataKeys"
  57 + ng-style="vm.cellStyle(entity, key)"
  58 + ng-bind-html="vm.cellContent(entity, key)">
  59 + </td>
  60 + <td md-cell ng-if="vm.displayActions" class="tb-action-cell">
  61 + <!--md-button class="md-icon-button" aria-label="{{ 'entity.details' | translate }}"
  62 + ng-click="vm.openEntityDetails($event, entity)">
  63 + <md-icon aria-label="{{ 'entity.details' | translate }}" class="material-icons">more_horiz</md-icon>
  64 + <md-tooltip md-direction="top">
  65 + {{ 'entity.details' | translate }}
  66 + </md-tooltip>
  67 + </md-button-->
  68 + </td>
  69 + </tr>
  70 + </tbody>
  71 + </table>
  72 + <md-divider></md-divider>
  73 + <span ng-show="!vm.entities.length"
  74 + layout-align="center center"
  75 + class="no-data-found" translate>entity.no-entities-prompt</span>
  76 + </md-table-container>
  77 + <md-table-pagination ng-if="vm.displayPagination" md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions"
  78 + md-page="vm.query.page" md-total="{{vm.entitiesCount}}"
  79 + md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd">
  80 + </md-table-pagination>
  81 + </div>
  82 + <span ng-show="!vm.showData"
  83 + layout-align="center center"
  84 + style="text-transform: uppercase; display: flex;"
  85 + class="tb-absolute-fill" translate>entity.no-data</span>
  86 +</div>
@@ -13,6 +13,9 @@ @@ -13,6 +13,9 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
  16 +
  17 +import AliasController from '../api/alias-controller';
  18 +
16 /* eslint-disable import/no-unresolved, import/default */ 19 /* eslint-disable import/no-unresolved, import/default */
17 20
18 import selectWidgetTypeTemplate from './select-widget-type.tpl.html'; 21 import selectWidgetTypeTemplate from './select-widget-type.tpl.html';
@@ -21,7 +24,8 @@ import selectWidgetTypeTemplate from './select-widget-type.tpl.html'; @@ -21,7 +24,8 @@ import selectWidgetTypeTemplate from './select-widget-type.tpl.html';
21 24
22 /*@ngInject*/ 25 /*@ngInject*/
23 export default function WidgetLibraryController($scope, $rootScope, $q, widgetService, userService, importExport, 26 export default function WidgetLibraryController($scope, $rootScope, $q, widgetService, userService, importExport,
24 - $state, $stateParams, $document, $mdDialog, $translate, $filter, types) { 27 + $state, $stateParams, $document, $mdDialog, $translate, $filter,
  28 + utils, types, entityService) {
25 29
26 var vm = this; 30 var vm = this;
27 31
@@ -31,6 +35,14 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe @@ -31,6 +35,14 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
31 vm.widgetTypes = []; 35 vm.widgetTypes = [];
32 vm.dashboardInitComplete = false; 36 vm.dashboardInitComplete = false;
33 37
  38 + var stateController = {
  39 + getStateParams: function() {
  40 + return {};
  41 + }
  42 + };
  43 + vm.aliasController = new AliasController($scope, $q, $filter, utils,
  44 + types, entityService, stateController, {});
  45 +
34 vm.noData = noData; 46 vm.noData = noData;
35 vm.dashboardInited = dashboardInited; 47 vm.dashboardInited = dashboardInited;
36 vm.dashboardInitFailed = dashboardInitFailed; 48 vm.dashboardInitFailed = dashboardInitFailed;
@@ -28,6 +28,7 @@ @@ -28,6 +28,7 @@
28 class="md-headline tb-absolute-fill">widgets-bundle.empty</span> 28 class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
29 </section> 29 </section>
30 <tb-dashboard 30 <tb-dashboard
  31 + alias-controller="vm.aliasController"
31 widgets="vm.widgetTypes" 32 widgets="vm.widgetTypes"
32 is-edit="false" 33 is-edit="false"
33 is-edit-action-enabled="true" 34 is-edit-action-enabled="true"