Browse Source

TB-62: Implement Entities table widget

pull/175/head
Igor Kulikov 9 years ago
parent
commit
d69e98e8b9
  1. 28
      ui/src/app/api/widget.service.js
  2. 13
      ui/src/app/components/datasource-entity.directive.js
  3. 5
      ui/src/app/components/datasource-entity.tpl.html
  4. 7
      ui/src/app/components/datasource-func.directive.js
  5. 7
      ui/src/app/components/datasource-func.tpl.html
  6. 1
      ui/src/app/components/datasource.directive.js
  7. 2
      ui/src/app/components/datasource.tpl.html
  8. 1
      ui/src/app/components/widget-config.directive.js
  9. 14
      ui/src/app/components/widget-config.tpl.html
  10. 13
      ui/src/app/components/widget.controller.js
  11. 1
      ui/src/app/dashboard/add-widget.tpl.html
  12. 2
      ui/src/app/dashboard/dashboard.controller.js
  13. 5
      ui/src/app/dashboard/dashboard.tpl.html
  14. 3
      ui/src/app/dashboard/edit-widget.directive.js
  15. 1
      ui/src/app/dashboard/edit-widget.tpl.html
  16. 13
      ui/src/app/locale/locale.constant.js
  17. 474
      ui/src/app/widget/lib/entities-table-widget.js
  18. 58
      ui/src/app/widget/lib/entities-table-widget.scss
  19. 86
      ui/src/app/widget/lib/entities-table-widget.tpl.html
  20. 14
      ui/src/app/widget/widget-library.controller.js
  21. 1
      ui/src/app/widget/widget-library.tpl.html

28
ui/src/app/api/widget.service.js

@ -20,6 +20,7 @@ import tinycolor from 'tinycolor2';
import thingsboardLedLight from '../components/led-light.directive';
import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget';
import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget';
import TbFlot from '../widget/lib/flot-widget';
import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
@ -34,7 +35,7 @@ import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
thingsboardAlarmsTableWidget, thingsboardTypes, thingsboardUtils])
thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardTypes, thingsboardUtils])
.factory('widgetService', WidgetService)
.name;
@ -546,6 +547,14 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
' }\n\n' +
' self.typeParameters = function() {\n\n' +
{
useCustomDatasources: false,
maxDatasources: -1 //unlimited
maxDataKeys: -1 //unlimited
}
' }\n\n' +
' self.onResize = function() {\n\n' +
' }\n\n' +
@ -586,10 +595,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
if (angular.isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {
result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();
}
if (angular.isFunction(widgetTypeInstance.typeParameters)) {
result.typeParameters = widgetTypeInstance.typeParameters();
} else {
result.typeParameters = {};
}
if (angular.isFunction(widgetTypeInstance.useCustomDatasources)) {
result.useCustomDatasources = widgetTypeInstance.useCustomDatasources();
result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources();
} else {
result.useCustomDatasources = false;
result.typeParameters.useCustomDatasources = false;
}
if (angular.isUndefined(result.typeParameters.maxDatasources)) {
result.typeParameters.maxDatasources = -1;
}
if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
result.typeParameters.maxDataKeys = -1;
}
return result;
} catch (e) {
@ -629,7 +649,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
if (widgetType.dataKeySettingsSchema) {
widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
}
widgetInfo.useCustomDatasources = widgetType.useCustomDatasources;
widgetInfo.typeParameters = widgetType.typeParameters;
putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
deferred.resolve(widgetInfo);

13
ui/src/app/components/datasource-entity.directive.js

@ -156,11 +156,19 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
};
scope.transformTimeseriesDataKeyChip = function (chip) {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
return null;
} else {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
}
};
scope.transformAttributeDataKeyChip = function (chip) {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
return null;
} else {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
}
};
scope.transformAlarmDataKeyChip = function (chip) {
@ -272,6 +280,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
require: "^ngModel",
scope: {
widgetType: '=',
maxDataKeys: '=',
aliasController: '=',
datakeySettingsSchema: '=',
generateDataKey: '&',

5
ui/src/app/components/datasource-entity.tpl.html

@ -186,5 +186,10 @@
<div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div>
<div translate ng-message="entityKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div>
</div>
<div class="md-caption" style="color: rgba(0,0,0,0.57);" ng-if="maxDataKeys != -1"
translate="datakey.maximum-timeseries-or-attributes"
translate-values="{count: maxDataKeys}"
translate-interpolation="messageformat"
></div>
</section>
</section>

7
ui/src/app/components/datasource-func.directive.js

@ -117,7 +117,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
};
scope.transformFuncDataKeyChip = function (chip) {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.function});
if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
return null;
} else {
return scope.generateDataKey({chip: chip, type: types.dataKeyType.function});
}
};
scope.transformAlarmDataKeyChip = function (chip) {
@ -217,6 +221,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
require: "^ngModel",
scope: {
widgetType: '=',
maxDataKeys: '=',
generateDataKey: '&',
datakeySettingsSchema: '='
},

7
ui/src/app/components/datasource-func.tpl.html

@ -17,7 +17,7 @@
-->
<section class="tb-datasource-func" flex layout='column'
layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container ng-if="widgetType != types.widgetType.alarm.value"
<md-input-container ng-show="widgetType != types.widgetType.alarm.value"
class="tb-datasource-name" md-no-float style="min-width: 200px;">
<input name="datasourceName"
placeholder="{{ 'datasource.name' | translate }}"
@ -132,5 +132,10 @@
<div translate ng-message="datasourceKeys" ng-if="widgetType !== types.widgetType.alarm.value" class="tb-error-message">datakey.function-types-required</div>
<div translate ng-message="datasourceKeys" ng-if="widgetType === types.widgetType.alarm.value" class="tb-error-message">datakey.alarm-fields-required</div>
</div>
<div class="md-caption" style="color: rgba(0,0,0,0.57);" ng-if="maxDataKeys != -1"
translate="datakey.maximum-function-types"
translate-values="{count: maxDataKeys}"
translate-interpolation="messageformat"
></div>
</section>
</section>

1
ui/src/app/components/datasource.directive.js

@ -82,6 +82,7 @@ function Datasource($compile, $templateCache, utils, types) {
require: "^ngModel",
scope: {
aliasController: '=',
maxDataKeys: '=',
widgetType: '=',
functionsOnly: '=',
datakeySettingsSchema: '=',

2
ui/src/app/components/datasource.tpl.html

@ -27,6 +27,7 @@
<tb-datasource-func flex
ng-switch-default
ng-model="model"
max-data-keys="maxDataKeys"
datakey-settings-schema="datakeySettingsSchema"
ng-required="model.type === types.datasourceType.function"
widget-type="widgetType"
@ -34,6 +35,7 @@
</tb-datasource-func>
<tb-datasource-entity flex
ng-model="model"
max-data-keys="maxDataKeys"
datakey-settings-schema="datakeySettingsSchema"
ng-switch-when="entity"
ng-required="model.type === types.datasourceType.entity"

1
ui/src/app/components/widget-config.directive.js

@ -442,6 +442,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
forceExpandDatasources: '=?',
isDataEnabled: '=?',
widgetType: '=',
typeParameters: '=',
widgetSettingsSchema: '=',
datakeySettingsSchema: '=',
aliasController: '=',

14
ui/src/app/components/widget-config.tpl.html

@ -62,7 +62,14 @@
&& isDataEnabled">
<v-pane id="datasources-pane" expanded="true">
<v-pane-header>
{{ 'widget-config.datasources' | translate }}
<div layout="column">
<div>{{ 'widget-config.datasources' | translate }}</div>
<div class="md-caption" style="color: rgba(0,0,0,0.57);" ng-if="typeParameters.maxDatasources != -1"
translate="widget-config.maximum-datasources"
translate-values="{count: typeParameters.maxDatasources}"
translate-interpolation="messageformat"
></div>
</div>
</v-pane-header>
<v-pane-content>
<div ng-if="datasources.length === 0">
@ -88,6 +95,7 @@
style="padding: 0 0 0 10px; margin: 5px;">
<tb-datasource flex ng-model="datasource.value"
widget-type="widgetType"
max-data-keys="typeParameters.maxDataKeys"
alias-controller="aliasController"
functions-only="functionsOnly"
datakey-settings-schema="datakeySettingsSchema"
@ -111,7 +119,7 @@
</div>
</div>
<div flex layout="row" layout-align="start center">
<md-button ng-disabled="loading" class="md-primary md-raised"
<md-button ng-show="typeParameters.maxDatasources == -1 || datasources.length < typeParameters.maxDatasources" ng-disabled="loading" class="md-primary md-raised"
ng-click="addDatasource($event)" aria-label="{{ 'action.add' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget-config.add-datasource' | translate }}
@ -140,7 +148,7 @@
</v-pane-content>
</v-pane>
</v-accordion>
<v-accordion id="alarn-source-accordion" control="alarmSourceAccordion" class="vAccordion--default"
<v-accordion id="alarm-source-accordion" control="alarmSourceAccordion" class="vAccordion--default"
ng-if="widgetType === types.widgetType.alarm.value && isDataEnabled">
<v-pane id="alarm-source-pane" expanded="true">
<v-pane-header>

13
ui/src/app/components/widget.controller.js

@ -128,7 +128,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var widgetTypeInstance;
vm.useCustomDatasources = false;
vm.typeParameters = widgetInfo.typeParameters;
try {
widgetTypeInstance = new widgetType(widgetContext);
@ -154,9 +154,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (!widgetTypeInstance.onDestroy) {
widgetTypeInstance.onDestroy = function() {};
}
if (widgetTypeInstance.useCustomDatasources) {
vm.useCustomDatasources = widgetTypeInstance.useCustomDatasources();
}
//TODO: widgets visibility
@ -502,7 +499,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var subscription = widgetContext.subscriptions[id];
subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
}
if (subscriptionChanged && !vm.useCustomDatasources) {
if (subscriptionChanged && !vm.typeParameters.useCustomDatasources) {
reInit();
}
});
@ -513,7 +510,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
configureWidgetElement();
var deferred = $q.defer();
if (!vm.useCustomDatasources) {
if (!vm.typeParameters.useCustomDatasources) {
createDefaultSubscription().then(
function success() {
subscriptionInited = true;
@ -535,7 +532,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function reInit() {
onDestroy();
configureWidgetElement();
if (!vm.useCustomDatasources) {
if (!vm.typeParameters.useCustomDatasources) {
createDefaultSubscription().then(
function success() {
subscriptionInited = true;
@ -575,7 +572,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
} catch (e) {
handleWidgetException(e);
}
if (!vm.useCustomDatasources && widgetContext.defaultSubscription) {
if (!vm.typeParameters.useCustomDatasources && widgetContext.defaultSubscription) {
widgetContext.defaultSubscription.subscribe();
}
}

1
ui/src/app/dashboard/add-widget.tpl.html

@ -33,6 +33,7 @@
<div class="md-dialog-content" style="padding-top: 0px;">
<fieldset ng-disabled="loading" style="position: relative; height: 600px;">
<tb-widget-config widget-type="vm.widget.type"
type-parameters="vm.widgetInfo.typeParameters"
force-expand-datasources="true"
ng-model="vm.widgetConfig"
widget-settings-schema="vm.settingsSchema"

2
ui/src/app/dashboard/dashboard.controller.js

@ -918,7 +918,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
}
}
if (widgetTypeInfo.useCustomDatasources) {
if (widgetTypeInfo.typeParameters.useCustomDatasources) {
addWidget(newWidget);
} else {
$mdDialog.show({

5
ui/src/app/dashboard/dashboard.tpl.html

@ -212,6 +212,7 @@
class="tb-absolute-fill" md-border-bottom>
<md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
<tb-dashboard
alias-controller="vm.dashboardCtx.aliasController"
widgets="vm.timeseriesWidgetTypes"
is-edit="false"
is-mobile="true"
@ -222,6 +223,7 @@
</md-tab>
<md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
<tb-dashboard
alias-controller="vm.dashboardCtx.aliasController"
widgets="vm.latestWidgetTypes"
is-edit="false"
is-mobile="true"
@ -232,6 +234,7 @@
</md-tab>
<md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
<tb-dashboard
alias-controller="vm.dashboardCtx.aliasController"
widgets="vm.rpcWidgetTypes"
is-edit="false"
is-mobile="true"
@ -242,6 +245,7 @@
</md-tab>
<md-tab ng-if="vm.alarmWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.alarm' | translate }}">
<tb-dashboard
alias-controller="vm.dashboardCtx.aliasController"
widgets="vm.alarmWidgetTypes"
is-edit="false"
is-mobile="true"
@ -252,6 +256,7 @@
</md-tab>
<md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
<tb-dashboard
alias-controller="vm.dashboardCtx.aliasController"
widgets="vm.staticWidgetTypes"
is-edit="false"
is-mobile="true"

3
ui/src/app/dashboard/edit-widget.directive.js

@ -40,7 +40,8 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
};
var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
scope.isDataEnabled = !widgetInfo.useCustomDatasources;
scope.typeParameters = widgetInfo.typeParameters;
scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources;
if (!settingsSchema || settingsSchema === '') {
scope.settingsSchema = {};
} else {

1
ui/src/app/dashboard/edit-widget.tpl.html

@ -17,6 +17,7 @@
-->
<fieldset ng-disabled="loading">
<tb-widget-config widget-type="widget.type"
type-parameters="typeParameters"
ng-model="widgetConfig"
is-data-enabled="isDataEnabled"
widget-settings-schema="settingsSchema"

13
ui/src/app/locale/locale.constant.js

@ -499,9 +499,11 @@ export default angular.module('thingsboard.locale', [])
"alarm": "Alarm fields",
"timeseries-required": "Entity timeseries are required.",
"timeseries-or-attributes-required": "Entity timeseries/attributes are required.",
"maximum-timeseries-or-attributes": "Maximum { count, select, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }",
"alarm-fields-required": "Alarm fields are required.",
"function-types": "Function types",
"function-types-required": "Function types are required."
"function-types-required": "Function types are required.",
"maximum-function-types": "Maximum { count, select, 1 {1 function type is allowed.} other {# function types are allowed} }"
},
"datasource": {
"type": "Datasource type",
@ -691,7 +693,13 @@ export default angular.module('thingsboard.locale', [])
"type-alarm": "Alarm",
"type-alarms": "Alarms",
"list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
"alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'"
"alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
"search": "Search entities",
"selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
"entity-name": "Entity name",
"details": "Entity details",
"no-entities-prompt": "No entities found",
"no-data": "No data to display"
},
"event": {
"event-type": "Event type",
@ -1144,6 +1152,7 @@ export default angular.module('thingsboard.locale', [])
"use-dashboard-timewindow": "Use dashboard timewindow",
"display-legend": "Display legend",
"datasources": "Datasources",
"maximum-datasources": "Maximum { count, select, 1 {1 datasource is allowed.} other {# datasources are allowed} }",
"datasource-type": "Type",
"datasource-parameters": "Parameters",
"remove-datasource": "Remove datasource",

474
ui/src/app/widget/lib/entities-table-widget.js

@ -0,0 +1,474 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './entities-table-widget.scss';
/* eslint-disable import/no-unresolved, import/default */
import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html';
//import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import tinycolor from 'tinycolor2';
import cssjs from '../../../vendor/css.js/css';
export default angular.module('thingsboard.widgets.entitiesTableWidget', [])
.directive('tbEntitiesTableWidget', EntitiesTableWidget)
.name;
/*@ngInject*/
function EntitiesTableWidget() {
return {
restrict: "E",
scope: true,
bindToController: {
tableId: '=',
ctx: '='
},
controller: EntitiesTableWidgetController,
controllerAs: 'vm',
templateUrl: entitiesTableWidgetTemplate
};
}
/*@ngInject*/
function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, utils, types) {
var vm = this;
vm.stylesInfo = {};
vm.contentsInfo = {};
vm.columnWidth = {};
vm.showData = true;
vm.hasData = false;
vm.entities = [];
vm.entitiesCount = 0;
vm.datasources = null;
vm.allEntities = null;
vm.currentEntity = null;
vm.displayEntityName = true;
vm.displayEntityType = true;
vm.displayActions = false; //TODO: Widget actions
vm.displayPagination = true;
vm.defaultPageSize = 10;
vm.defaultSortOrder = 'entityName';
vm.query = {
order: vm.defaultSortOrder,
limit: vm.defaultPageSize,
page: 1,
search: null
};
vm.searchAction = {
name: 'action.search',
show: true,
onAction: function() {
vm.enterFilterMode();
},
icon: 'search'
};
vm.enterFilterMode = enterFilterMode;
vm.exitFilterMode = exitFilterMode;
vm.onReorder = onReorder;
vm.onPaginate = onPaginate;
vm.onRowClick = onRowClick;
vm.isCurrent = isCurrent;
vm.cellStyle = cellStyle;
vm.cellContent = cellContent;
$scope.$watch('vm.ctx', function() {
if (vm.ctx) {
vm.settings = vm.ctx.settings;
vm.widgetConfig = vm.ctx.widgetConfig;
vm.subscription = vm.ctx.defaultSubscription;
vm.datasources = vm.subscription.datasources;
initializeConfig();
updateDatasources();
}
});
$scope.$watch("vm.query.search", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
updateEntities();
}
});
$scope.$on('entities-table-data-updated', function(event, tableId) {
if (vm.tableId == tableId) {
if (vm.subscription) {
updateEntitiesData(vm.subscription.data);
updateEntities();
$scope.$digest();
}
}
});
$scope.$watch(function() { return $mdMedia('gt-xs'); }, function(isGtXs) {
vm.isGtXs = isGtXs;
});
$scope.$watch(function() { return $mdMedia('gt-md'); }, function(isGtMd) {
vm.isGtMd = isGtMd;
if (vm.isGtMd) {
vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
} else {
vm.limitOptions = null;
}
});
function initializeConfig() {
vm.ctx.widgetActions = [ vm.searchAction ];
if (vm.settings.entitiesTitle && vm.settings.entitiesTitle.length) {
var translationId = types.translate.customTranslationsPrefix + vm.settings.entitiesTitle;
var translation = $translate.instant(translationId);
if (translation != translationId) {
vm.entitiesTitle = translation + '';
} else {
vm.entitiesTitle = vm.settings.entitiesTitle;
}
} else {
vm.entitiesTitle = $translate.instant('entity.entities');
}
vm.ctx.widgetTitle = vm.entitiesTitle;
vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true;
vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true;
vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
var pageSize = vm.settings.defaultPageSize;
if (angular.isDefined(pageSize) && Number.isInteger(pageSize) && pageSize > 0) {
vm.defaultPageSize = pageSize;
}
if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) {
vm.defaultSortOrder = vm.settings.defaultSortOrder;
}
vm.query.order = vm.defaultSortOrder;
vm.query.limit = vm.defaultPageSize;
if (vm.isGtMd) {
vm.limitOptions = [vm.defaultPageSize, vm.defaultPageSize*2, vm.defaultPageSize*3];
} else {
vm.limitOptions = null;
}
var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)';
var defaultColor = tinycolor(origColor);
var mdDark = defaultColor.setAlpha(0.87).toRgbString();
var mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString();
var mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString();
//var mdDarkIcon = mdDarkSecondary;
var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString();
var cssString = 'table.md-table th.md-column {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table th.md-column.md-checkbox-column md-checkbox:not(.md-checked) .md-icon {\n'+
'border-color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table th.md-column md-icon.md-sort-icon {\n'+
'color: ' + mdDarkDisabled + ';\n'+
'}\n'+
'table.md-table th.md-column.md-active, table.md-table th.md-column.md-active md-icon {\n'+
'color: ' + mdDark + ';\n'+
'}\n'+
'table.md-table td.md-cell {\n'+
'color: ' + mdDark + ';\n'+
'border-top: 1px '+mdDarkDivider+' solid;\n'+
'}\n'+
'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
'border-color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'table.md-table td.md-cell.md-placeholder {\n'+
'color: ' + mdDarkDisabled + ';\n'+
'}\n'+
'table.md-table td.md-cell md-select > .md-select-value > span.md-select-icon {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'.md-table-pagination {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'border-top: 1px '+mdDarkDivider+' solid;\n'+
'}\n'+
'.md-table-pagination .buttons md-icon {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}\n'+
'.md-table-pagination md-select:not([disabled]):focus .md-select-value {\n'+
'color: ' + mdDarkSecondary + ';\n'+
'}';
var cssParser = new cssjs();
cssParser.testMode = false;
var namespace = 'entities-table-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString);
$element.addClass(namespace);
function hashCode(str) {
var hash = 0;
var i, char;
if (str.length === 0) return hash;
for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash;
}
}
function enterFilterMode () {
vm.query.search = '';
vm.ctx.hideTitlePanel = true;
}
function exitFilterMode () {
vm.query.search = null;
updateEntities();
vm.ctx.hideTitlePanel = false;
}
function onReorder () {
updateEntities();
}
function onPaginate () {
updateEntities();
}
function onRowClick($event, entity) {
if (vm.currentEntity != entity) {
vm.currentEntity = entity;
}
}
function isCurrent(entity) {
return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) &&
(vm.currentEntity.id.id === entity.id.id);
}
function updateEntities() {
var result = $filter('orderBy')(vm.allEntities, vm.query.order);
if (vm.query.search != null) {
result = $filter('filter')(result, {$: vm.query.search});
}
vm.entitiesCount = result.length;
if (vm.displayPagination) {
var startIndex = vm.query.limit * (vm.query.page - 1);
vm.entities = result.slice(startIndex, startIndex + vm.query.limit);
} else {
vm.entities = result;
}
}
function cellStyle(entity, key) {
var style = {};
if (entity && key) {
var styleInfo = vm.stylesInfo[key.label];
var value = getEntityValue(entity, key);
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
try {
style = styleInfo.cellStyleFunction(value);
} catch (e) {
style = {};
}
} else {
style = defaultStyle(key, value);
}
}
if (!style.width) {
var columnWidth = vm.columnWidth[key.label];
style.width = columnWidth;
}
return style;
}
function cellContent(entity, key) {
var strContent = '';
if (entity && key) {
var contentInfo = vm.contentsInfo[key.label];
var value = getEntityValue(entity, key);
if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
if (angular.isDefined(value)) {
strContent = '' + value;
}
var content = strContent;
try {
content = contentInfo.cellContentFunction(value, entity, $filter);
} catch (e) {
content = strContent;
}
} else {
content = defaultContent(key, value);
}
return content;
} else {
return strContent;
}
}
function defaultContent(key, value) {
if (angular.isDefined(value)) {
return value;
} else {
return '';
}
}
function defaultStyle(/*key, value*/) {
return {};
}
const getDescendantProp = (obj, path) => (
path.split('.').reduce((acc, part) => acc && acc[part], obj)
);
function getEntityValue(entity, key) {
return getDescendantProp(entity, key.name);
}
function updateEntitiesData(data) {
if (vm.allEntities) {
for (var i=0;i<vm.allEntities.length;i++) {
var entity = vm.allEntities[i];
for (var a=0;a<vm.dataKeys.length;a++) {
var dataKey = vm.dataKeys[a];
var index = i * vm.dataKeys.length + a;
var keyData = data[index].data;
if (keyData && keyData.length && keyData[0].length > 1) {
var value = keyData[0][1];
entity[dataKey.name] = value;
} else {
entity[dataKey.name] = '';
}
}
}
}
}
function updateDatasources() {
vm.stylesInfo = {};
vm.contentsInfo = {};
vm.columnWidth = {};
vm.dataKeys = [];
vm.allEntities = [];
var datasource;
var dataKey;
datasource = vm.datasources[0];
vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle);
for (var d = 0; d < datasource.dataKeys.length; d++ ) {
dataKey = angular.copy(datasource.dataKeys[d]);
if (dataKey.type == types.dataKeyType.function) {
dataKey.name = dataKey.label;
}
vm.dataKeys.push(dataKey);
var translationId = types.translate.customTranslationsPrefix + dataKey.label;
var translation = $translate.instant(translationId);
if (translation != translationId) {
dataKey.title = translation + '';
} else {
dataKey.title = dataKey.label;
}
var keySettings = dataKey.settings;
var cellStyleFunction = null;
var useCellStyleFunction = false;
if (keySettings.useCellStyleFunction === true) {
if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {
try {
cellStyleFunction = new Function('value', keySettings.cellStyleFunction);
useCellStyleFunction = true;
} catch (e) {
cellStyleFunction = null;
useCellStyleFunction = false;
}
}
}
vm.stylesInfo[dataKey.label] = {
useCellStyleFunction: useCellStyleFunction,
cellStyleFunction: cellStyleFunction
};
var cellContentFunction = null;
var useCellContentFunction = false;
if (keySettings.useCellContentFunction === true) {
if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {
try {
cellContentFunction = new Function('value, entity, filter', keySettings.cellContentFunction);
useCellContentFunction = true;
} catch (e) {
cellContentFunction = null;
useCellContentFunction = false;
}
}
}
vm.contentsInfo[dataKey.label] = {
useCellContentFunction: useCellContentFunction,
cellContentFunction: cellContentFunction
};
var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
vm.columnWidth[dataKey.label] = columnWidth;
}
for (var i=0;i<vm.datasources.length;i++) {
datasource = vm.datasources[i];
var entity = {
id: {}
};
entity.entityName = datasource.entityName;
if (datasource.entityId) {
entity.id.id = datasource.entityId;
}
if (datasource.entityType) {
entity.id.entityType = datasource.entityType;
entity.entityType = $translate.instant(types.entityTypeTranslations[datasource.entityType].type) + '';
} else {
entity.entityType = '';
}
for (d = 0; d < vm.dataKeys.length; d++) {
dataKey = vm.dataKeys[d];
entity[dataKey.name] = '';
}
vm.allEntities.push(entity);
}
}
}

58
ui/src/app/widget/lib/entities-table-widget.scss

@ -0,0 +1,58 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-has-timewindow {
.tb-entities-table {
md-toolbar {
min-height: 60px;
max-height: 60px;
&.md-table-toolbar {
.md-toolbar-tools {
max-height: 60px;
}
}
}
}
}
.tb-entities-table {
md-toolbar {
min-height: 39px;
max-height: 39px;
&.md-table-toolbar {
.md-toolbar-tools {
max-height: 39px;
}
}
}
&.tb-data-table {
table.md-table, table.md-table.md-row-select {
tbody {
tr {
td {
&.tb-action-cell {
min-width: 36px;
max-width: 36px;
width: 36px;
}
}
}
}
}
}
}

86
ui/src/app/widget/lib/entities-table-widget.tpl.html

@ -0,0 +1,86 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-absolute-fill tb-entities-table tb-data-table" layout="column">
<div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column">
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
<div class="md-toolbar-tools">
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
<md-tooltip md-direction="top">
{{'entity.search' | translate}}
</md-tooltip>
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="vm.query.search" placeholder="{{'entity.search' | translate}}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
<md-tooltip md-direction="top">
{{ 'action.close' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<md-table-container flex>
<table md-table>
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span translate>entity.entity-name</span></th>
<th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
<th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
<th md-column ng-if="vm.displayActions"><span>&nbsp</span></th>
</tr>
</thead>
<tbody md-body>
<tr ng-show="vm.entities.length" md-row md-select="entity"
md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities"
ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}">
<td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td>
<td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td>
<td md-cell flex ng-repeat="key in vm.dataKeys"
ng-style="vm.cellStyle(entity, key)"
ng-bind-html="vm.cellContent(entity, key)">
</td>
<td md-cell ng-if="vm.displayActions" class="tb-action-cell">
<!--md-button class="md-icon-button" aria-label="{{ 'entity.details' | translate }}"
ng-click="vm.openEntityDetails($event, entity)">
<md-icon aria-label="{{ 'entity.details' | translate }}" class="material-icons">more_horiz</md-icon>
<md-tooltip md-direction="top">
{{ 'entity.details' | translate }}
</md-tooltip>
</md-button-->
</td>
</tr>
</tbody>
</table>
<md-divider></md-divider>
<span ng-show="!vm.entities.length"
layout-align="center center"
class="no-data-found" translate>entity.no-entities-prompt</span>
</md-table-container>
<md-table-pagination ng-if="vm.displayPagination" md-boundary-links md-limit="vm.query.limit" md-limit-options="vm.limitOptions"
md-page="vm.query.page" md-total="{{vm.entitiesCount}}"
md-on-paginate="vm.onPaginate" md-page-select="vm.isGtMd">
</md-table-pagination>
</div>
<span ng-show="!vm.showData"
layout-align="center center"
style="text-transform: uppercase; display: flex;"
class="tb-absolute-fill" translate>entity.no-data</span>
</div>

14
ui/src/app/widget/widget-library.controller.js

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import AliasController from '../api/alias-controller';
/* eslint-disable import/no-unresolved, import/default */
import selectWidgetTypeTemplate from './select-widget-type.tpl.html';
@ -21,7 +24,8 @@ import selectWidgetTypeTemplate from './select-widget-type.tpl.html';
/*@ngInject*/
export default function WidgetLibraryController($scope, $rootScope, $q, widgetService, userService, importExport,
$state, $stateParams, $document, $mdDialog, $translate, $filter, types) {
$state, $stateParams, $document, $mdDialog, $translate, $filter,
utils, types, entityService) {
var vm = this;
@ -31,6 +35,14 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
vm.widgetTypes = [];
vm.dashboardInitComplete = false;
var stateController = {
getStateParams: function() {
return {};
}
};
vm.aliasController = new AliasController($scope, $q, $filter, utils,
types, entityService, stateController, {});
vm.noData = noData;
vm.dashboardInited = dashboardInited;
vm.dashboardInitFailed = dashboardInitFailed;

1
ui/src/app/widget/widget-library.tpl.html

@ -28,6 +28,7 @@
class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
</section>
<tb-dashboard
alias-controller="vm.aliasController"
widgets="vm.widgetTypes"
is-edit="false"
is-edit-action-enabled="true"

Loading…
Cancel
Save