Commit 4e8c05f6707dd93d6b8c7e3da4772319f14d44ca
Committed by
GitHub
Merge pull request #175 from thingsboard/feature/TB-62
TB-62: Implement Entities table widget
Showing
21 changed files
with
725 additions
and
24 deletions
@@ -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> </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> </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" |