Browse Source

UI: Implement legend component.

pull/56/head
Igor Kulikov 9 years ago
parent
commit
fe7ce47be7
  1. 200
      dao/src/main/resources/system-data.cql
  2. 36
      extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionState.java
  3. 2
      ui/src/app/admin/general-settings.tpl.html
  4. 2
      ui/src/app/admin/outgoing-mail-settings.tpl.html
  5. 2
      ui/src/app/api/datasource.service.js
  6. 10
      ui/src/app/api/device.service.js
  7. 24
      ui/src/app/api/telemetry-websocket.service.js
  8. 2
      ui/src/app/app.config.js
  9. 18
      ui/src/app/common/types.constant.js
  10. 2
      ui/src/app/component/component-dialog.tpl.html
  11. 4
      ui/src/app/components/dashboard.tpl.html
  12. 2
      ui/src/app/components/datakey-config-dialog.tpl.html
  13. 21
      ui/src/app/components/legend-config-button.tpl.html
  14. 35
      ui/src/app/components/legend-config-panel.controller.js
  15. 47
      ui/src/app/components/legend-config-panel.tpl.html
  16. 151
      ui/src/app/components/legend-config.directive.js
  17. 49
      ui/src/app/components/legend-config.scss
  18. 85
      ui/src/app/components/legend.directive.js
  19. 53
      ui/src/app/components/legend.scss
  20. 42
      ui/src/app/components/legend.tpl.html
  21. 2
      ui/src/app/components/react/json-form-checkbox.jsx
  22. 48
      ui/src/app/components/widget-config.directive.js
  23. 215
      ui/src/app/components/widget-config.tpl.html
  24. 176
      ui/src/app/components/widget.controller.js
  25. 105
      ui/src/app/components/widget.directive.js
  26. 2
      ui/src/app/customer/add-customer.tpl.html
  27. 2
      ui/src/app/dashboard/add-dashboard.tpl.html
  28. 2
      ui/src/app/dashboard/add-dashboards-to-customer.tpl.html
  29. 2
      ui/src/app/dashboard/add-widget.tpl.html
  30. 2
      ui/src/app/dashboard/assign-to-customer.tpl.html
  31. 29
      ui/src/app/dashboard/dashboard-settings.scss
  32. 2
      ui/src/app/dashboard/dashboard-settings.tpl.html
  33. 21
      ui/src/app/dashboard/dashboard.controller.js
  34. 3
      ui/src/app/dashboard/dashboard.scss
  35. 2
      ui/src/app/dashboard/device-aliases.tpl.html
  36. 2
      ui/src/app/device/add-device.tpl.html
  37. 2
      ui/src/app/device/add-devices-to-customer.tpl.html
  38. 2
      ui/src/app/device/assign-to-customer.tpl.html
  39. 2
      ui/src/app/device/attribute/add-attribute-dialog.tpl.html
  40. 2
      ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html
  41. 10
      ui/src/app/device/attribute/attribute-table.directive.js
  42. 2
      ui/src/app/device/device-credentials.tpl.html
  43. 2
      ui/src/app/event/event-table.tpl.html
  44. 29
      ui/src/app/import-export/import-dialog.scss
  45. 2
      ui/src/app/import-export/import-dialog.tpl.html
  46. 2
      ui/src/app/layout/home.tpl.html
  47. 22
      ui/src/app/locale/locale.constant.js
  48. 2
      ui/src/app/login/create-password.tpl.html
  49. 2
      ui/src/app/login/login.tpl.html
  50. 2
      ui/src/app/login/reset-password-request.tpl.html
  51. 2
      ui/src/app/login/reset-password.tpl.html
  52. 2
      ui/src/app/plugin/add-plugin.tpl.html
  53. 2
      ui/src/app/profile/change-password.tpl.html
  54. 2
      ui/src/app/profile/profile.tpl.html
  55. 2
      ui/src/app/rule/add-rule.tpl.html
  56. 2
      ui/src/app/tenant/add-tenant.tpl.html
  57. 2
      ui/src/app/user/add-user.tpl.html
  58. 2
      ui/src/app/widget/add-widgets-bundle.tpl.html
  59. 16
      ui/src/app/widget/lib/analogue-linear-gauge.js
  60. 16
      ui/src/app/widget/lib/analogue-radial-gauge.js
  61. 17
      ui/src/app/widget/lib/canvas-digital-gauge.js
  62. 198
      ui/src/app/widget/lib/flot-widget.js
  63. 2
      ui/src/app/widget/save-widget-type-as.tpl.html
  64. 2
      ui/src/app/widget/select-widget-type.tpl.html
  65. 4
      ui/src/app/widget/widget-editor.tpl.html
  66. 39
      ui/src/scss/main.scss

200
dao/src/main/resources/system-data.cql

File diff suppressed because one or more lines are too long

36
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionState.java

@ -17,6 +17,7 @@ package org.thingsboard.server.extensions.core.plugin.telemetry.sub;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.common.data.id.DeviceId;
import java.util.Map;
@ -24,16 +25,37 @@ import java.util.Map;
/**
* @author Andrew Shvayka
*/
@Data
@AllArgsConstructor
public class SubscriptionState {
private final String wsSessionId;
private final int subscriptionId;
private final DeviceId deviceId;
private final SubscriptionType type;
private final boolean allKeys;
private final Map<String, Long> keyStates;
@Getter private final String wsSessionId;
@Getter private final int subscriptionId;
@Getter private final DeviceId deviceId;
@Getter private final SubscriptionType type;
@Getter private final boolean allKeys;
@Getter private final Map<String, Long> keyStates;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SubscriptionState that = (SubscriptionState) o;
if (subscriptionId != that.subscriptionId) return false;
if (wsSessionId != null ? !wsSessionId.equals(that.wsSessionId) : that.wsSessionId != null) return false;
if (deviceId != null ? !deviceId.equals(that.deviceId) : that.deviceId != null) return false;
return type == that.type;
}
@Override
public int hashCode() {
int result = wsSessionId != null ? wsSessionId.hashCode() : 0;
result = 31 * result + subscriptionId;
result = 31 * result + (deviceId != null ? deviceId.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
@Override
public String toString() {

2
ui/src/app/admin/general-settings.tpl.html

@ -22,7 +22,7 @@
<span translate class="md-headline">admin.general-settings</span>
</md-card-title-text>
</md-card-title>
<md-progress-linear md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-card-content>
<form name="vm.settingsForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="vm.settingsForm">

2
ui/src/app/admin/outgoing-mail-settings.tpl.html

@ -24,7 +24,7 @@
<div id="help-container"></div>
</md-card-title-text>
</md-card-title>
<md-progress-linear md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-card-content>
<form name="vm.settingsForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="vm.settingsForm">

2
ui/src/app/api/datasource.service.js

@ -557,7 +557,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
data.push(series);
}
}
if (data.length > 0 || (startTs && endTs)) {
if (data || (startTs && endTs)) {
datasourceData[datasourceKey].data = data;
for (var i2 in listeners) {
var listener = listeners[i2];

10
ui/src/app/api/device.service.js

@ -212,7 +212,7 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
return deferred.promise;
}
function processDeviceAttributes(attributes, query, deferred, successCallback, update) {
function processDeviceAttributes(attributes, query, deferred, successCallback, update, apply) {
attributes = $filter('orderBy')(attributes, query.order);
if (query.search != null) {
attributes = $filter('filter')(attributes, {key: query.search});
@ -222,7 +222,7 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
}
var startIndex = query.limit * (query.page - 1);
responseData.data = attributes.slice(startIndex, startIndex + query.limit);
successCallback(responseData, update);
successCallback(responseData, update, apply);
if (deferred) {
deferred.resolve();
}
@ -236,13 +236,13 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
if (das.attributes) {
processDeviceAttributes(das.attributes, query, deferred, successCallback);
das.subscriptionCallback = function(attributes) {
processDeviceAttributes(attributes, query, null, successCallback, true);
processDeviceAttributes(attributes, query, null, successCallback, true, true);
}
} else {
das.subscriptionCallback = function(attributes) {
processDeviceAttributes(attributes, query, deferred, successCallback);
processDeviceAttributes(attributes, query, deferred, successCallback, false, true);
das.subscriptionCallback = function(attributes) {
processDeviceAttributes(attributes, query, null, successCallback, true);
processDeviceAttributes(attributes, query, null, successCallback, true, true);
}
}
}

24
ui/src/app/api/telemetry-websocket.service.js

@ -132,6 +132,16 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
if (data.subscriptionId) {
var subscriber = subscribers[data.subscriptionId];
if (subscriber && data) {
var keys = fetchKeys(subscriber);
if (!data.data) {
data.data = {};
}
for (var k in keys) {
var key = keys[k];
if (!data.data[key]) {
data.data[key] = [];
}
}
subscriber.onData(data);
}
}
@ -139,6 +149,20 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
checkToClose();
}
function fetchKeys(subscriber) {
var command;
if (angular.isDefined(subscriber.subscriptionCommand)) {
command = subscriber.subscriptionCommand;
} else {
command = subscriber.historyCommand;
}
if (command && command.keys && command.keys.length > 0) {
return command.keys.split(",");
} else {
return [];
}
}
function nextCmdId () {
lastCmdId++;
return lastCmdId;

2
ui/src/app/app.config.js

@ -142,7 +142,7 @@ export default function AppConfig($provide,
.backgroundPalette('tb-primary');
$mdThemingProvider.setDefaultTheme('default');
$mdThemingProvider.alwaysWatchTheme(true);
//$mdThemingProvider.alwaysWatchTheme(true);
}
}

18
ui/src/app/common/types.constant.js

@ -59,6 +59,24 @@ export default angular.module('thingsboard.types', [])
name: "aggregation.none"
}
},
position: {
top: {
value: "top",
name: "position.top"
},
bottom: {
value: "bottom",
name: "position.bottom"
},
left: {
value: "left",
name: "position.left"
},
right: {
value: "right",
name: "position.right"
}
},
datasourceType: {
function: "function",
device: "device"

2
ui/src/app/component/component-dialog.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content tb-filter">

4
ui/src/app/components/dashboard.tpl.html

@ -17,7 +17,7 @@
-->
<md-content flex layout="column" class="tb-progress-cover" layout-align="center center"
ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit">
<md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
<md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loading() && !vm.dashboardLoading || vm.isEdit" class="md-warn" md-diameter="100"></md-progress-circular>
</md-content>
<md-menu md-position-mode="target target" tb-mousepoint-menu>
<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)">
@ -25,7 +25,7 @@
<div id="gridster-child" gridster="vm.gridsterOpts">
<ul>
<!-- ng-click="widgetClicked($event, widget)" -->
<li gridster-item="vm.widgetItemMap" ng-repeat="widget in vm.widgets">
<li gridster-item="vm.widgetItemMap" class="tb-noselect" ng-repeat="widget in vm.widgets">
<md-menu md-position-mode="target target" tb-mousepoint-menu>
<div tb-expand-fullscreen
fullscreen-background-style="vm.dashboardStyle"

2
ui/src/app/components/datakey-config-dialog.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<tb-datakey-config ng-model="vm.dataKey"

21
ui/src/app/components/legend-config-button.tpl.html

@ -0,0 +1,21 @@
<!--
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.
-->
<md-button ng-disabled="disabled" class="md-raised md-primary" ng-click="openEditMode($event)">
<ng-md-icon icon="toc"></ng-md-icon>
<span translate>legend.settings</span>
</md-button>

35
ui/src/app/components/legend-config-panel.controller.js

@ -0,0 +1,35 @@
/*
* 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.
*/
/*@ngInject*/
export default function LegendConfigPanelController(mdPanelRef, $scope, types, legendConfig, onLegendConfigUpdate) {
var vm = this;
vm._mdPanelRef = mdPanelRef;
vm.legendConfig = legendConfig;
vm.onLegendConfigUpdate = onLegendConfigUpdate;
vm.positions = types.position;
vm._mdPanelRef.config.onOpenComplete = function () {
$scope.theForm.$setPristine();
}
$scope.$watch('vm.legendConfig', function () {
if (onLegendConfigUpdate) {
onLegendConfigUpdate(vm.legendConfig);
}
}, true);
}

47
ui/src/app/components/legend-config-panel.tpl.html

@ -0,0 +1,47 @@
<!--
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.
-->
<form name="theForm" ng-submit="vm.update()">
<fieldset ng-disabled="loading">
<md-content style="height: 100%" flex layout="column">
<section layout="column">
<md-content class="md-padding" layout="column">
<md-input-container>
<label translate>legend.position</label>
<md-select ng-model="vm.legendConfig.position" style="min-width: 150px;">
<md-option ng-repeat="pos in vm.positions" ng-value="pos.value">
{{pos.name | translate}}
</md-option>
</md-select>
</md-input-container>
<md-checkbox flex aria-label="{{ 'legend.show-min' | translate }}"
ng-model="vm.legendConfig.showMin">{{ 'legend.show-min' | translate }}
</md-checkbox>
<md-checkbox flex aria-label="{{ 'legend.show-max' | translate }}"
ng-model="vm.legendConfig.showMax">{{ 'legend.show-max' | translate }}
</md-checkbox>
<md-checkbox flex aria-label="{{ 'legend.show-avg' | translate }}"
ng-model="vm.legendConfig.showAvg">{{ 'legend.show-avg' | translate }}
</md-checkbox>
<md-checkbox flex aria-label="{{ 'legend.show-total' | translate }}"
ng-model="vm.legendConfig.showTotal">{{ 'legend.show-total' | translate }}
</md-checkbox>
</md-content>
</section>
</md-content>
</fieldset>
</form>

151
ui/src/app/components/legend-config.directive.js

@ -0,0 +1,151 @@
/*
* 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 './legend-config.scss';
import $ from 'jquery';
/* eslint-disable import/no-unresolved, import/default */
import legendConfigButtonTemplate from './legend-config-button.tpl.html';
import legendConfigPanelTemplate from './legend-config-panel.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import LegendConfigPanelController from './legend-config-panel.controller';
export default angular.module('thingsboard.directives.legendConfig', [])
.controller('LegendConfigPanelController', LegendConfigPanelController)
.directive('tbLegendConfig', LegendConfig)
.name;
/* eslint-disable angular/angularelement */
/*@ngInject*/
function LegendConfig($compile, $templateCache, types, $mdPanel, $document) {
var linker = function (scope, element, attrs, ngModelCtrl) {
/* tbLegendConfig (ng-model)
* {
* position: types.position.bottom.value,
* showMin: false,
* showMax: false,
* showAvg: true,
* showTotal: false
* }
*/
var template = $templateCache.get(legendConfigButtonTemplate);
element.html(template);
scope.openEditMode = function (event) {
if (scope.disabled) {
return;
}
var position;
var panelHeight = 220;
var panelWidth = 220;
var offset = element[0].getBoundingClientRect();
var bottomY = offset.bottom - $(window).scrollTop(); //eslint-disable-line
var leftX = offset.left - $(window).scrollLeft(); //eslint-disable-line
var yPosition;
var xPosition;
if (bottomY + panelHeight > $( window ).height()) { //eslint-disable-line
yPosition = $mdPanel.yPosition.ABOVE;
} else {
yPosition = $mdPanel.yPosition.BELOW;
}
if (leftX + panelWidth > $( window ).width()) { //eslint-disable-line
xPosition = $mdPanel.xPosition.ALIGN_END;
} else {
xPosition = $mdPanel.xPosition.ALIGN_START;
}
position = $mdPanel.newPanelPosition()
.relativeTo(element)
.addPanelPosition(xPosition, yPosition);
var config = {
attachTo: angular.element($document[0].body),
controller: 'LegendConfigPanelController',
controllerAs: 'vm',
templateUrl: legendConfigPanelTemplate,
panelClass: 'tb-legend-config-panel',
position: position,
fullscreen: false,
locals: {
'legendConfig': angular.copy(scope.model),
'onLegendConfigUpdate': function (legendConfig) {
scope.model = legendConfig;
scope.updateView();
}
},
openFrom: event,
clickOutsideToClose: true,
escapeToClose: true,
focusOnOpen: false
};
$mdPanel.open(config);
}
scope.updateView = function () {
var value = {};
var model = scope.model;
value.position = model.position;
value.showMin = model.showMin;
value.showMax = model.showMax;
value.showAvg = model.showAvg;
value.showTotal = model.showTotal;
ngModelCtrl.$setViewValue(value);
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
if (!scope.model) {
scope.model = {};
}
var model = scope.model;
model.position = value.position || types.position.bottom.value;
model.showMin = angular.isDefined(value.showMin) ? value.showMin : false;
model.showMax = angular.isDefined(value.showMax) ? value.showMax : false;
model.showAvg = angular.isDefined(value.showAvg) ? value.showAvg : true;
model.showTotal = angular.isDefined(value.showTotal) ? value.showTotal : false;
} else {
scope.model = {
position: types.position.bottom.value,
showMin: false,
showMax: false,
showAvg: true,
showTotal: false
}
}
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
scope: {
disabled:'=ngDisabled'
},
link: linker
};
}
/* eslint-enable angular/angularelement */

49
ui/src/app/components/legend-config.scss

@ -0,0 +1,49 @@
/**
* 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.
*/
.md-panel {
&.tb-legend-config-panel {
position: absolute;
}
}
.tb-legend-config-panel {
max-height: 220px;
min-width: 220px;
background: white;
border-radius: 4px;
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
overflow: hidden;
form, fieldset {
height: 100%;
}
md-content {
background-color: #fff;
overflow: hidden;
}
.md-padding {
padding: 0 16px;
}
}
tb-legend-config {
span {
pointer-events: all;
cursor: pointer;
}
}

85
ui/src/app/components/legend.directive.js

@ -0,0 +1,85 @@
/*
* 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 './legend.scss';
/* eslint-disable import/no-unresolved, import/default */
import legendTemplate from './legend.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.legend', [])
.directive('tbLegend', Legend)
.name;
/*@ngInject*/
function Legend($compile, $templateCache, types) {
var linker = function (scope, element) {
var template = $templateCache.get(legendTemplate);
element.html(template);
scope.displayHeader = function() {
return scope.legendConfig.showMin === true ||
scope.legendConfig.showMax === true ||
scope.legendConfig.showAvg === true ||
scope.legendConfig.showTotal === true;
}
scope.isHorizontal = scope.legendConfig.position === types.position.bottom.value ||
scope.legendConfig.position === types.position.top.value;
scope.$on('legendDataUpdated', function () {
scope.$digest();
});
scope.toggleHideData = function(index) {
scope.legendData.data[index].hidden = !scope.legendData.data[index].hidden;
scope.$emit('legendDataHiddenChanged', index);
}
$compile(element.contents())(scope);
}
/* scope.legendData = {
keys: [],
data: []
key: {
label: '',
color: ''
dataIndex: 0
}
data: {
min: null,
max: null,
avg: null,
total: null
}
};*/
return {
restrict: "E",
link: linker,
scope: {
legendConfig: '=',
legendData: '='
}
};
}

53
ui/src/app/components/legend.scss

@ -0,0 +1,53 @@
/**
* 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.
*/
table.tb-legend {
width: 100%;
font-size: 12px;
.tb-legend-header, .tb-legend-value {
text-align: right;
}
.tb-legend-header {
th {
color: rgb(255,110,64);
white-space: nowrap;
padding: 0 10px 1px 0;
}
}
.tb-legend-keys {
td.tb-legend-label, td.tb-legend-value {
white-space: nowrap;
padding: 2px 10px;
}
.tb-legend-line {
width: 15px;
height: 3px;
display: inline-block;
vertical-align: middle;
}
.tb-legend-label {
text-align: left;
outline: none;
&.tb-horizontal {
width: 95%;
}
&.tb-hidden-label {
text-decoration: line-through;
opacity: 0.6;
}
}
}
}

42
ui/src/app/components/legend.tpl.html

@ -0,0 +1,42 @@
<!--
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.
-->
<table class="tb-legend">
<thead>
<tr class="tb-legend-header">
<th colspan="2"></th>
<th ng-if="legendConfig.showMin === true">{{ 'legend.min' | translate }}</th>
<th ng-if="legendConfig.showMax === true">{{ 'legend.max' | translate }}</th>
<th ng-if="legendConfig.showAvg === true">{{ 'legend.avg' | translate }}</th>
<th ng-if="legendConfig.showTotal === true">{{ 'legend.total' | translate }}</th>
</tr>
</thead>
<tbody>
<tr class="tb-legend-keys" ng-repeat="legendKey in legendData.keys">
<td><span class="tb-legend-line" ng-style="{backgroundColor: legendKey.color}"></span></td>
<td class="tb-legend-label"
ng-click="toggleHideData(legendKey.dataIndex)"
ng-class="{ 'tb-hidden-label': legendData.data[legendKey.dataIndex].hidden, 'tb-horizontal': isHorizontal }">
{{ legendKey.label }}
</td>
<td class="tb-legend-value" ng-if="legendConfig.showMin === true">{{ legendData.data[legendKey.dataIndex].min }}</td>
<td class="tb-legend-value" ng-if="legendConfig.showMax === true">{{ legendData.data[legendKey.dataIndex].max }}</td>
<td class="tb-legend-value" ng-if="legendConfig.showAvg === true">{{ legendData.data[legendKey.dataIndex].avg }}</td>
<td class="tb-legend-value" ng-if="legendConfig.showTotal === true">{{ legendData.data[legendKey.dataIndex].total }}</td>
</tr>
</tbody>
</table>

2
ui/src/app/components/react/json-form-checkbox.jsx

@ -27,7 +27,7 @@ class ThingsboardCheckbox extends React.Component {
label={this.props.form.title}
disabled={this.props.form.readonly}
onCheck={(e, checked) => {this.props.onChangeValidate(e)}}
style={{paddingTop: '20px'}}
style={{paddingTop: '14px'}}
/>
);
}

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

@ -19,6 +19,7 @@ import thingsboardUtils from '../common/utils.service';
import thingsboardDeviceAliasSelect from './device-alias-select.directive';
import thingsboardDatasource from './datasource.directive';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardLegendConfig from './legend-config.directive';
import thingsboardJsonForm from "./json-form.directive";
import 'angular-ui-ace';
@ -36,6 +37,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar
thingsboardDeviceAliasSelect,
thingsboardDatasource,
thingsboardTimewindow,
thingsboardLegendConfig,
'ui.ace'])
.directive('tbWidgetConfig', WidgetConfig)
.name;
@ -98,9 +100,14 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}, true);
scope.mobileOrder = ngModelCtrl.$viewValue.mobileOrder;
scope.mobileHeight = ngModelCtrl.$viewValue.mobileHeight;
scope.units = ngModelCtrl.$viewValue.units;
scope.decimals = ngModelCtrl.$viewValue.decimals;
scope.useDashboardTimewindow = angular.isDefined(ngModelCtrl.$viewValue.useDashboardTimewindow) ?
ngModelCtrl.$viewValue.useDashboardTimewindow : true;
scope.timewindow = ngModelCtrl.$viewValue.timewindow;
scope.showLegend = angular.isDefined(ngModelCtrl.$viewValue.showLegend) ?
ngModelCtrl.$viewValue.showLegend : scope.widgetType === types.widgetType.timeseries.value;
scope.legendConfig = ngModelCtrl.$viewValue.legendConfig;
if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value) {
if (scope.datasources) {
scope.datasources.splice(0, scope.datasources.length);
@ -129,8 +136,6 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
scope.settings = ngModelCtrl.$viewValue.settings;
scope.updateSchemaForm();
scope.updateDatasourcesAccordionState();
}
};
@ -150,12 +155,6 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}
}
scope.$on('datasources-accordion:onReady', function () {
if (scope.updateDatasourcesAccordionStatePending) {
scope.updateDatasourcesAccordionState();
}
});
scope.updateValidity = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
@ -177,7 +176,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
};
scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' +
'padding + titleStyle + mobileOrder + mobileHeight + useDashboardTimewindow', function () {
'padding + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + showLegend', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
value.title = scope.title;
@ -194,7 +193,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}
value.mobileOrder = angular.isNumber(scope.mobileOrder) ? scope.mobileOrder : undefined;
value.mobileHeight = scope.mobileHeight;
value.units = scope.units;
value.decimals = scope.decimals;
value.useDashboardTimewindow = scope.useDashboardTimewindow;
value.showLegend = scope.showLegend;
ngModelCtrl.$setViewValue(value);
scope.updateValidity();
}
@ -216,6 +218,14 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}
}, true);
scope.$watch('legendConfig', function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
value.legendConfig = scope.legendConfig;
ngModelCtrl.$setViewValue(value);
}
}, true);
scope.$watch('datasources', function () {
if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value
&& scope.widgetType !== types.widgetType.static.value) {
@ -275,26 +285,6 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
}
};
scope.updateDatasourcesAccordionState = function () {
if (scope.widgetType !== types.widgetType.rpc.value &&
scope.widgetType !== types.widgetType.static.value) {
if (scope.datasourcesAccordion) {
scope.updateDatasourcesAccordionStatePending = false;
var expand = scope.datasources && scope.datasources.length < 4;
if (expand) {
$timeout(function() {
scope.datasourcesAccordion.expand('datasources-pane');
});
} else {
scope.datasourcesAccordion.collapse('datasources-pane');
}
} else {
scope.updateDatasourcesAccordionStatePending = true;
}
}
}
scope.generateDataKey = function (chip, type) {
if (angular.isObject(chip)) {

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

@ -15,11 +15,108 @@
limitations under the License.
-->
<md-tabs ng-class="{'tb-headless': !displayAdvanced()}" id="tabs" md-border-bottom flex class="tb-absolute-fill"
<md-tabs ng-class="{'tb-headless': (widgetType === types.widgetType.static.value && !displayAdvanced())}" id="tabs" md-border-bottom flex class="tb-absolute-fill"
md-selected="selectedTab">
<md-tab label="{{ 'widget-config.data' | translate }}"
ng-if="widgetType !== types.widgetType.static.value">
<md-content class="md-padding" layout="column">
<div ng-show="widgetType === types.widgetType.timeseries.value" layout='column' layout-align="center"
layout-gt-sm='row' layout-align-gt-sm="start center">
<md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}"
ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }}
</md-checkbox>
<section flex layout="row" layout-align="start center" style="margin-bottom: 16px;">
<span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span>
<tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow>
</section>
</div>
<v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value">
<v-pane id="datasources-pane" expanded="true">
<v-pane-header>
{{ 'widget-config.datasources' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="datasources.length === 0">
<span translate layout-align="center center"
class="tb-prompt">datasource.add-datasource-prompt</span>
</div>
<div ng-if="datasources.length > 0">
<div flex layout="row" layout-align="start center">
<span flex="5"></span>
<div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate style="min-width: 110px;">widget-config.datasource-type</span>
<span hide show-gt-sm translate flex
style="padding-left: 10px;">widget-config.datasource-parameters</span>
<span style="min-width: 40px;"></span>
</div>
</div>
<div style="overflow: auto; padding-bottom: 15px;">
<div flex layout="row" layout-align="start center"
ng-repeat="datasource in datasources">
<span flex="5">{{$index + 1}}.</span>
<div class="md-whiteframe-4dp" flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<tb-datasource flex ng-model="datasource.value"
widget-type="widgetType"
device-aliases="deviceAliases"
functions-only="functionsOnly"
datakey-settings-schema="datakeySettingsSchema"
generate-data-key="generateDataKey(chip,type)"
fetch-device-keys="fetchDeviceKeys({deviceAliasId: deviceAliasId, query: query, type: type})"
on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})"></tb-datasource>
<md-button ng-disabled="loading" class="md-icon-button md-primary"
style="min-width: 40px;"
ng-click="removeDatasource($event, datasource)"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget-config.remove-datasource' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>
</div>
</div>
</div>
<div flex layout="row" layout-align="start center">
<md-button 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 }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.add</span>
</md-button>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
<v-accordion id="target-devices-accordion" control="targetDevicesAccordion" class="vAccordion--default"
ng-show="widgetType === types.widgetType.rpc.value">
<v-pane id="target-devices-pane" expanded="true">
<v-pane-header>
{{ 'widget-config.target-device' | translate }}
</v-pane-header>
<v-pane-content style="padding: 0 5px;">
<tb-device-alias-select flex
tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
device-aliases="deviceAliases"
ng-model="targetDeviceAlias.value"
on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
</tb-device-alias-select>
</v-pane-content>
</v-pane>
</v-accordion>
</md-content>
</md-tab>
<md-tab label="{{ 'widget-config.settings' | translate }}">
<div id="settings-tab">
<md-content class="md-padding" layout="column">
<span translate>widget-config.general-settings</span>
<div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container flex class="md-block">
<label translate>widget-config.title</label>
@ -31,7 +128,6 @@
</div>
</div>
</div>
<span translate>widget-config.general-settings</span>
<div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<div layout="row" layout-padding>
<md-checkbox flex aria-label="{{ 'widget-config.display-title' | translate }}"
@ -77,117 +173,46 @@
<input ng-model="padding">
</md-input-container>
</div>
<span translate>widget-config.mobile-mode-settings</span>
<div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container flex>
<label translate>widget-config.order</label>
<input ng-model="mobileOrder" type="number" step="1" ng-pattern="/^-?[0-9]+$/">
<label translate>widget-config.units</label>
<input name="units" ng-model="units">
</md-input-container>
<md-input-container flex>
<label translate>widget-config.height</label>
<input ng-model="mobileHeight" type="number">
<label translate>widget-config.decimals</label>
<input ng-model="decimals" type="number" min="0" max="15" step="1" ng-pattern="/^\d*$/">
</md-input-container>
</div>
<div ng-show="widgetType === types.widgetType.timeseries.value" layout='column' layout-align="center"
<div ng-show="widgetType === types.widgetType.timeseries.value ||
widgetType === types.widgetType.latest.value" layout='column' layout-align="center"
layout-gt-sm='row' layout-align-gt-sm="start center">
<md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}"
ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }}
<md-checkbox flex aria-label="{{ 'widget-config.display-legend' | translate }}"
ng-model="showLegend">{{ 'widget-config.display-legend' | translate }}
</md-checkbox>
<section flex layout="row" layout-align="start center" style="margin-bottom: 16px;">
<span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span>
<tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow>
<tb-legend-config ng-disabled="!showLegend" flex ng-model="legendConfig"></tb-legend-config>
</section>
</div>
<v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value">
<v-pane id="datasources-pane" expanded="forceExpandDatasources">
<v-pane-header>
{{ 'widget-config.datasources' | translate }}
</v-pane-header>
<v-pane-content>
<div ng-if="datasources.length === 0">
<span translate layout-align="center center"
class="tb-prompt">datasource.add-datasource-prompt</span>
</div>
<div ng-if="datasources.length > 0">
<div flex layout="row" layout-align="start center">
<span flex="5"></span>
<div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate style="min-width: 110px;">widget-config.datasource-type</span>
<span hide show-gt-sm translate flex
style="padding-left: 10px;">widget-config.datasource-parameters</span>
<span style="min-width: 40px;"></span>
</div>
</div>
<div style="max-height: 300px; overflow: auto; padding-bottom: 15px;">
<div flex layout="row" layout-align="start center"
ng-repeat="datasource in datasources">
<span flex="5">{{$index + 1}}.</span>
<div class="md-whiteframe-4dp" flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<tb-datasource flex ng-model="datasource.value"
widget-type="widgetType"
device-aliases="deviceAliases"
functions-only="functionsOnly"
datakey-settings-schema="datakeySettingsSchema"
generate-data-key="generateDataKey(chip,type)"
fetch-device-keys="fetchDeviceKeys({deviceAliasId: deviceAliasId, query: query, type: type})"
on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})"></tb-datasource>
<md-button ng-disabled="loading" class="md-icon-button md-primary"
style="min-width: 40px;"
ng-click="removeDatasource($event, datasource)"
aria-label="{{ 'action.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget-config.remove-datasource' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.delete' | translate }}"
class="material-icons">
close
</md-icon>
</md-button>
</div>
</div>
</div>
</div>
<div flex layout="row" layout-align="start center">
<md-button 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 }}
</md-tooltip>
<md-icon class="material-icons">add</md-icon>
<span translate>action.add</span>
</md-button>
</div>
</v-pane-content>
</v-pane>
</v-accordion>
<v-accordion id="target-devices-accordion" control="targetDevicesAccordion" class="vAccordion--default"
ng-show="widgetType === types.widgetType.rpc.value">
<v-pane id="target-devices-pane" expanded="true">
<v-pane-header>
{{ 'widget-config.target-device' | translate }}
</v-pane-header>
<v-pane-content style="padding: 0 5px;">
<tb-device-alias-select flex
tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
device-aliases="deviceAliases"
ng-model="targetDeviceAlias.value"
on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
</tb-device-alias-select>
</v-pane-content>
</v-pane>
</v-accordion>
<span translate>widget-config.mobile-mode-settings</span>
<div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container flex>
<label translate>widget-config.order</label>
<input ng-model="mobileOrder" type="number" step="1" ng-pattern="/^-?[0-9]+$/">
</md-input-container>
<md-input-container flex>
<label translate>widget-config.height</label>
<input ng-model="mobileHeight" min="1" max="10" type="number" step="1" ng-pattern="/^\d*$/">
</md-input-container>
</div>
</md-content>
</div>
</md-tab>
<md-tab ng-if="displayAdvanced()" label="{{ 'widget-config.advanced' | translate }}">
<md-content class="md-padding" layout="column">
<ng-form name="ngform"
<ng-form flex name="ngform"
layout="column"
layout-padding>
<tb-json-form schema="currentSettingsSchema"
<tb-json-form flex schema="currentSettingsSchema"
form="currentSettingsForm"
model="currentSettings"
form-control="ngform">

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

@ -19,7 +19,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
/* eslint-disable angular/angularelement */
/*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils, timeService,
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService,
datasourceService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
dashboardTimewindowApi, widget, deviceAliasList, widgetType) {
@ -66,8 +66,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
isEdit: isEdit,
isMobile: false,
settings: widget.config.settings,
units: widget.config.units || '',
decimals: angular.isDefined(widget.config.decimals) ? widget.config.decimals : 2,
datasources: widget.config.datasources,
data: [],
hiddenData: [],
timeWindow: {
stDiff: stDiff
},
@ -82,6 +85,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
sendTwoWayCommand: function(method, params, timeout) {
return sendCommand(false, method, params, timeout);
}
},
utils: {
formatValue: formatValue
}
};
@ -137,21 +143,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.widgetErrorData = utils.processWidgetException(e);
}
function notifyDataLoaded(apply) {
function notifyDataLoaded() {
if ($scope.loadingData === true) {
$scope.loadingData = false;
if (apply) {
$scope.$digest();
}
}
}
function notifyDataLoading(apply) {
function notifyDataLoading() {
if ($scope.loadingData === false) {
$scope.loadingData = true;
if (apply) {
$scope.$digest();
}
}
}
@ -292,7 +292,33 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
onInit();
}
/* scope.legendData = {
keys: [],
data: []
key: {
label: '',
color: ''
dataIndex: 0
}
data: {
min: null,
max: null,
avg: null,
total: null
}
};*/
function initialize() {
$scope.caulculateLegendData = $scope.displayLegend &&
widget.type === types.widgetType.timeseries.value &&
($scope.legendConfig.showMin === true ||
$scope.legendConfig.showMax === true ||
$scope.legendConfig.showAvg === true ||
$scope.legendConfig.showTotal === true);
if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
for (var i in widget.config.datasources) {
var datasource = angular.copy(widget.config.datasources[i]);
@ -304,8 +330,40 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
data: []
};
widgetContext.data.push(datasourceData);
widgetContext.hiddenData.push({data: []});
if ($scope.displayLegend) {
var legendKey = {
label: dataKey.label,
color: dataKey.color,
dataIndex: Number(i) + Number(a)
};
$scope.legendData.keys.push(legendKey);
var legendKeyData = {
min: null,
max: null,
avg: null,
total: null,
hidden: false
};
$scope.legendData.data.push(legendKeyData);
}
}
}
if ($scope.displayLegend) {
$scope.legendData.keys = $filter('orderBy')($scope.legendData.keys, '+label');
$scope.$on('legendDataHiddenChanged', function (event, index) {
event.stopPropagation();
var hidden = $scope.legendData.data[index].hidden;
if (hidden) {
widgetContext.hiddenData[index].data = widgetContext.data[index].data;
widgetContext.data[index].data = [];
} else {
widgetContext.data[index].data = widgetContext.hiddenData[index].data;
widgetContext.hiddenData[index].data = [];
}
onDataUpdated();
});
}
} else if (widget.type === types.widgetType.rpc.value) {
if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
@ -527,10 +585,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
function dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
notifyDataLoaded(apply);
notifyDataLoaded();
var update = true;
var currentData;
if ($scope.displayLegend && $scope.legendData.data[datasourceIndex + dataKeyIndex].hidden) {
currentData = widgetContext.hiddenData[datasourceIndex + dataKeyIndex];
} else {
currentData = widgetContext.data[datasourceIndex + dataKeyIndex];
}
if (widget.type === types.widgetType.latest.value) {
var prevData = widgetContext.data[datasourceIndex + dataKeyIndex].data;
var prevData = currentData.data;
if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) {
var prevValue = prevData[0][1];
if (prevValue === sourceData.data[0][1]) {
@ -542,8 +606,96 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (subscriptionTimewindow && subscriptionTimewindow.realtimeWindowMs) {
updateTimewindow();
}
widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData.data;
currentData.data = sourceData.data;
onDataUpdated();
if ($scope.caulculateLegendData) {
updateLegend(datasourceIndex + dataKeyIndex, sourceData.data);
}
}
if (apply) {
$scope.$digest();
}
}
function updateLegend(dataIndex, data) {
var legendKeyData = $scope.legendData.data[dataIndex];
if ($scope.legendConfig.showMin) {
legendKeyData.min = formatValue(calculateMin(data), widgetContext.decimals, widgetContext.units);
}
if ($scope.legendConfig.showMax) {
legendKeyData.max = formatValue(calculateMax(data), widgetContext.decimals, widgetContext.units);
}
if ($scope.legendConfig.showAvg) {
legendKeyData.avg = formatValue(calculateAvg(data), widgetContext.decimals, widgetContext.units);
}
if ($scope.legendConfig.showTotal) {
legendKeyData.total = formatValue(calculateTotal(data), widgetContext.decimals, widgetContext.units);
}
$scope.$broadcast('legendDataUpdated');
}
function isNumeric(val) {
return (val - parseFloat( val ) + 1) >= 0;
}
function formatValue(value, dec, units) {
if (angular.isDefined(value) &&
value !== null && isNumeric(value)) {
var formatted = value;
if (angular.isDefined(dec)) {
formatted = formatted.toFixed(dec);
}
formatted = (formatted * 1).toString();
if (angular.isDefined(units) && units.length > 0) {
formatted += ' ' + units;
}
return formatted;
} else {
return '';
}
}
function calculateMin(data) {
if (data.length > 0) {
var result = Number(data[0][1]);
for (var i=1;i<data.length;i++) {
result = Math.min(result, Number(data[i][1]));
}
return result;
} else {
return null;
}
}
function calculateMax(data) {
if (data.length > 0) {
var result = Number(data[0][1]);
for (var i=1;i<data.length;i++) {
result = Math.max(result, Number(data[i][1]));
}
return result;
} else {
return null;
}
}
function calculateAvg(data) {
if (data.length > 0) {
return calculateTotal(data)/data.length;
} else {
return null;
}
}
function calculateTotal(data) {
if (data.length > 0) {
var result = 0;
for (var i = 0; i < data.length; i++) {
result += Number(data[i][1]);
}
return result;
} else {
return null;
}
}

105
ui/src/app/components/widget.directive.js

@ -16,18 +16,19 @@
import './widget.scss';
import thingsboardLegend from './legend.directive';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiDatasource from '../api/datasource.service';
import WidgetController from './widget.controller';
export default angular.module('thingsboard.directives.widget', [thingsboardTypes, thingsboardApiDatasource])
export default angular.module('thingsboard.directives.widget', [thingsboardLegend, thingsboardTypes, thingsboardApiDatasource])
.controller('WidgetController', WidgetController)
.directive('tbWidget', Widget)
.name;
/*@ngInject*/
function Widget($controller, $compile, widgetService) {
function Widget($controller, $compile, types, widgetService) {
return {
scope: true,
link: function (scope, elem, attrs) {
@ -53,10 +54,14 @@ function Widget($controller, $compile, widgetService) {
}
});
elem.html('<div flex layout="column" layout-align="center center" style="height: 100%;">' +
' <md-progress-circular md-mode="indeterminate" class="md-accent md-hue-2" md-diameter="120"></md-progress-circular>' +
'</div>');
$compile(elem.contents())(scope);
//TODO:
//elem.html('<div id="progress-cover" flex layout="column" layout-align="center center" style="height: 100%;">' +
// ' <md-progress-circular md-mode="indeterminate" class="md-accent md-hue-2" md-diameter="120"></md-progress-circular>' +
// '</div>');
//var progressElement = angular.element(elem[0].querySelector('#progress-cover'));
//var progressScope = scope.$new();
//$compile(elem.contents())(progressScope);
widgetService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).then(
function(widgetInfo) {
@ -75,19 +80,91 @@ function Widget($controller, $compile, widgetService) {
+ widget.typeAlias;
elem.addClass(widgetNamespace);
elem.html('<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
'<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
'</div>' +
'<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
'<md-progress-circular md-mode="indeterminate" class="md-accent" md-diameter="40"></md-progress-circular>' +
'</div>' +
'<div id="container">' + widgetInfo.templateHtml + '</div>');
var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
'<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
'</div>' +
'<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
'<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
'</div>';
scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
if (scope.displayLegend) {
scope.legendConfig = widget.config.legendConfig ||
{
position: types.position.bottom.value,
showMin: false,
showMax: false,
showAvg: widget.type === types.widgetType.timeseries.value,
showTotal: false
};
scope.legendData = {
keys: [],
data: []
};
var layoutType;
if (scope.legendConfig.position === types.position.top.value ||
scope.legendConfig.position === types.position.bottom.value) {
layoutType = 'column';
} else {
layoutType = 'row';
}
var legendStyle;
switch(scope.legendConfig.position) {
case types.position.top.value:
legendStyle = 'padding-bottom: 8px;';
break;
case types.position.bottom.value:
legendStyle = 'padding-top: 8px;';
break;
case types.position.left.value:
legendStyle = 'padding-right: 0px;';
break;
case types.position.right.value:
legendStyle = 'padding-left: 0px;';
break;
}
var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
if (scope.legendConfig.position === types.position.top.value ||
scope.legendConfig.position === types.position.left.value) {
html += legendHtml;
html += containerHtml;
} else {
html += containerHtml;
html += legendHtml;
}
html += '</div>';
} else {
html += containerHtml;
}
//TODO:
/*if (progressElement) {
progressScope.$destroy();
progressScope = null;
progressElement.remove();
progressElement = null;
}*/
elem.html(html);
var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem;
$compile(elem.contents())(scope);
var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
angular.extend(locals, {$scope: scope, $element: elem, widgetType: widgetType});
angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType});
widgetController = $controller('WidgetController', locals);

2
ui/src/app/customer/add-customer.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/dashboard/add-dashboard.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/dashboard/add-dashboards-to-customer.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

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

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content" style="padding-top: 0px;">

2
ui/src/app/dashboard/assign-to-customer.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

29
ui/src/app/dashboard/dashboard-settings.scss

@ -15,16 +15,6 @@
*/
$previewSize: 100px;
.file-input {
display: none;
}
.tb-container {
position: relative;
margin-top: 32px;
padding: 10px 0;
}
.tb-image-select-container {
position: relative;
height: $previewSize;
@ -59,25 +49,6 @@ $previewSize: 100px;
}
}
.tb-flow-drop {
position: relative;
border: dashed 2px;
height: $previewSize;
vertical-align: top;
padding: 0 8px;
overflow: hidden;
min-width: 300px;
label {
width: 100%;
font-size: 24px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
.tb-image-clear-container {
width: 48px;
height: $previewSize;

2
ui/src/app/dashboard/dashboard-settings.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

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

@ -32,7 +32,7 @@ export default function DashboardController(types, widgetService, userService,
vm.dashboard = null;
vm.editingWidget = null;
vm.editingWidgetIndex = null;
vm.editingWidgetOriginal = null;
vm.editingWidgetSubtitle = null;
vm.forceDashboardMobileMode = false;
vm.isAddingWidget = false;
@ -304,13 +304,12 @@ export default function DashboardController(types, widgetService, userService,
function editWidget($event, widget) {
$event.stopPropagation();
var newEditingIndex = vm.widgets.indexOf(widget);
if (vm.editingWidgetIndex === newEditingIndex) {
if (vm.editingWidgetOriginal === widget) {
$timeout(onEditWidgetClosed());
} else {
var transition = !vm.forceDashboardMobileMode;
vm.editingWidgetIndex = vm.widgets.indexOf(widget);
vm.editingWidget = angular.copy(widget);
vm.editingWidgetOriginal = widget;
vm.editingWidget = angular.copy(vm.editingWidgetOriginal);
vm.editingWidgetSubtitle = widgetService.getInstantWidgetInfo(vm.editingWidget).widgetName;
vm.forceDashboardMobileMode = true;
vm.isEditingWidget = true;
@ -319,7 +318,7 @@ export default function DashboardController(types, widgetService, userService,
var delayOffset = transition ? 350 : 0;
var delay = transition ? 400 : 300;
$timeout(function () {
vm.dashboardContainer.highlightWidget(widget, delay);
vm.dashboardContainer.highlightWidget(vm.editingWidgetOriginal, delay);
}, delayOffset, false);
}
}
@ -513,19 +512,21 @@ export default function DashboardController(types, widgetService, userService,
function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) {
widgetForm.$setPristine();
vm.editingWidget = angular.copy(vm.widgets[vm.editingWidgetIndex]);
vm.editingWidget = angular.copy(vm.editingWidgetOriginal);
}
}
function saveWidget(widgetForm) {
widgetForm.$setPristine();
var widget = angular.copy(vm.editingWidget);
vm.widgets[vm.editingWidgetIndex] = widget;
vm.dashboardContainer.highlightWidget(widget, 0);
var index = vm.widgets.indexOf(vm.editingWidgetOriginal);
vm.widgets[index] = widget;
vm.editingWidgetOriginal = widget;
vm.dashboardContainer.highlightWidget(vm.editingWidgetOriginal, 0);
}
function onEditWidgetClosed() {
vm.editingWidgetIndex = null;
vm.editingWidgetOriginal = null;
vm.editingWidget = null;
vm.editingWidgetSubtitle = null;
vm.isEditingWidget = false;

3
ui/src/app/dashboard/dashboard.scss

@ -95,6 +95,8 @@ section.tb-dashboard-toolbar {
opacity: 0.5;
@include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
md-icon {
position: absolute;
top: 25%;
margin: 0;
line-height: 18px;
height: 18px;
@ -111,6 +113,7 @@ section.tb-dashboard-toolbar {
min-height: 36px;
height: 36px;
md-fab-actions {
margin-top: 0px;
.close-action {
margin-right: -18px;
}

2
ui/src/app/dashboard/device-aliases.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/device/add-device.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/device/add-devices-to-customer.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/device/assign-to-customer.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/device/attribute/add-attribute-dialog.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

10
ui/src/app/device/attribute/attribute-table.directive.js

@ -107,12 +107,14 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
}
});
function success(attributes, update) {
function success(attributes, update, apply) {
scope.attributes = attributes;
if (!update) {
scope.selectedAttributes = [];
}
scope.$digest();
if (apply) {
scope.$digest();
}
}
scope.getDeviceAttributes = function(forceUpdate) {
@ -126,8 +128,8 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
};
scope.checkSubscription();
scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value,
scope.query, function(attributes, update) {
success(attributes, update || forceUpdate);
scope.query, function(attributes, update, apply) {
success(attributes, update || forceUpdate, apply);
}
);
} else {

2
ui/src/app/device/device-credentials.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/event/event-table.tpl.html

@ -30,7 +30,7 @@
<md-list flex layout="column" class="md-whiteframe-z1 tb-table">
<md-list class="tb-row tb-header" layout="row" tb-event-header event-type="{{eventType}}">
</md-list>
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate"
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!loading()"
ng-show="loading()"></md-progress-linear>
<md-divider></md-divider>
<span translate layout-align="center center"

29
ui/src/app/import-export/import-dialog.scss

@ -15,16 +15,6 @@
*/
$previewSize: 100px;
.file-input {
display: none;
}
.tb-container {
position: relative;
margin-top: 32px;
padding: 10px 0;
}
.tb-file-select-container {
position: relative;
height: $previewSize;
@ -38,25 +28,6 @@ $previewSize: 100px;
height: auto;
}
.tb-flow-drop {
position: relative;
border: dashed 2px;
height: $previewSize;
vertical-align: top;
padding: 0 8px;
overflow: hidden;
min-width: 300px;
label {
width: 100%;
font-size: 24px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
.tb-file-clear-container {
width: 48px;
height: $previewSize;

2
ui/src/app/import-export/import-dialog.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/layout/home.tpl.html

@ -88,7 +88,7 @@
</md-menu>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" style="z-index: 10; max-height: 0px; width: 100%;" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" style="z-index: 10; max-height: 0px; width: 100%;" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<div flex layout="column" id="toast-parent" style="position: relative;">
<md-content ng-cloak flex layout="column" class="page-content" ui-view name="content"></md-content>

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

@ -431,6 +431,18 @@ export default angular.module('thingsboard.locale', [])
"no-return-error": "Function must return value!",
"return-type-mismatch": "Function must return value of '{{type}}' type!"
},
"legend": {
"position": "Legend position",
"show-max": "Show max value",
"show-min": "Show min value",
"show-avg": "Show average value",
"show-total": "Show total value",
"settings": "Legend settings",
"min": "min",
"max": "max",
"avg": "avg",
"total": "total"
},
"login": {
"login": "Login",
"request-password-reset": "Request Password Reset",
@ -481,6 +493,12 @@ export default angular.module('thingsboard.locale', [])
"events": "Events",
"details": "Details"
},
"position": {
"top": "Top",
"bottom": "Bottom",
"left": "Left",
"right": "Right"
},
"profile": {
"profile": "Profile",
"change-password": "Change Password",
@ -690,6 +708,7 @@ export default angular.module('thingsboard.locale', [])
"system": "System"
},
"widget-config": {
"data": "Data",
"settings": "Settings",
"advanced": "Advanced",
"title": "Title",
@ -704,8 +723,11 @@ export default angular.module('thingsboard.locale', [])
"mobile-mode-settings": "Mobile mode settings",
"order": "Order",
"height": "Height",
"units": "Special symbol to show next to value",
"decimals": "Number of digits after floating point",
"timewindow": "Timewindow",
"use-dashboard-timewindow": "Use dashboard timewindow",
"display-legend": "Display legend",
"datasources": "Datasources",
"datasource-type": "Type",
"datasource-parameters": "Parameters",

2
ui/src/app/login/create-password.tpl.html

@ -23,7 +23,7 @@
</md-card-title-text>
</md-card-title>
<md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
md-mode="indeterminate" ng-show="loading"></md-progress-linear>
md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<md-card-content>
<form class="create-password-form" ng-submit="vm.createPassword()">
<div layout="column" layout-padding="" id="toast-parent">

2
ui/src/app/login/login.tpl.html

@ -23,7 +23,7 @@
</md-card-title-text>
</md-card-title>
<md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
md-mode="indeterminate" ng-show="loading"></md-progress-linear>
md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<md-card-content>
<form class="login-form" ng-submit="vm.login()">
<div layout="column" layout-padding="" id="toast-parent">

2
ui/src/app/login/reset-password-request.tpl.html

@ -23,7 +23,7 @@
</md-card-title-text>
</md-card-title>
<md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
md-mode="indeterminate" ng-show="loading"></md-progress-linear>
md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<md-card-content>
<form class="request-password-reset-form" ng-submit="vm.sendResetPasswordLink()">
<div layout="column" layout-padding="" id="toast-parent">

2
ui/src/app/login/reset-password.tpl.html

@ -23,7 +23,7 @@
</md-card-title-text>
</md-card-title>
<md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
md-mode="indeterminate" ng-show="loading"></md-progress-linear>
md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<md-card-content>
<form class="password-reset-form" ng-submit="vm.resetPassword()">
<div layout="column" layout-padding="" id="toast-parent">

2
ui/src/app/plugin/add-plugin.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/profile/change-password.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/profile/profile.tpl.html

@ -23,7 +23,7 @@
<span style='opacity: 0.7;'>{{ vm.profileUser.email }}</span>
</md-card-title-text>
</md-card-title>
<md-progress-linear md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-card-content>
<form name="theForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="theForm">

2
ui/src/app/rule/add-rule.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/tenant/add-tenant.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/user/add-user.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/widget/add-widgets-bundle.tpl.html

@ -27,7 +27,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

16
ui/src/app/widget/lib/analogue-linear-gauge.js

@ -42,7 +42,7 @@ export default class TbAnalogueLinearGauge {
var valueInt = settings.valueInt || 3;
var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
? settings.valueDec : 2;
? settings.valueDec : ctx.decimals;
step = parseFloat(parseFloat(step).toFixed(valueDec));
@ -92,7 +92,7 @@ export default class TbAnalogueLinearGauge {
maxValue: maxValue,
majorTicks: majorTicks,
minorTicks: settings.minorTicks || 2,
units: settings.units,
units: angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units,
title: ((settings.showUnitTitle !== false) ?
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : ''),
@ -227,11 +227,6 @@ export default class TbAnalogueLinearGauge {
"type": "boolean",
"default": true
},
"units": {
"title": "Units",
"type": "string",
"default": ""
},
"majorTicksCount": {
"title": "Major ticks count",
"type": "number",
@ -252,11 +247,6 @@ export default class TbAnalogueLinearGauge {
"type": "number",
"default": 3
},
"valueDec": {
"title": "Digits count for decimal part of value",
"type": "number",
"default": 2
},
"defaultColor": {
"title": "Default color",
"type": "string",
@ -553,12 +543,10 @@ export default class TbAnalogueLinearGauge {
"maxValue",
"unitTitle",
"showUnitTitle",
"units",
"majorTicksCount",
"minorTicks",
"valueBox",
"valueInt",
"valueDec",
{
"key": "defaultColor",
"type": "color"

16
ui/src/app/widget/lib/analogue-radial-gauge.js

@ -43,7 +43,7 @@ export default class TbAnalogueRadialGauge {
var valueInt = settings.valueInt || 3;
var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
? settings.valueDec : 2;
? settings.valueDec : ctx.decimals;
step = parseFloat(parseFloat(step).toFixed(valueDec));
@ -89,7 +89,7 @@ export default class TbAnalogueRadialGauge {
maxValue: maxValue,
majorTicks: majorTicks,
minorTicks: settings.minorTicks || 2,
units: settings.units,
units: angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units,
title: ((settings.showUnitTitle !== false) ?
(settings.unitTitle && settings.unitTitle.length > 0 ?
settings.unitTitle : dataKey.label) : ''),
@ -238,11 +238,6 @@ export default class TbAnalogueRadialGauge {
"type": "boolean",
"default": true
},
"units": {
"title": "Units",
"type": "string",
"default": ""
},
"majorTicksCount": {
"title": "Major ticks count",
"type": "number",
@ -263,11 +258,6 @@ export default class TbAnalogueRadialGauge {
"type": "number",
"default": 3
},
"valueDec": {
"title": "Digits count for decimal part of value",
"type": "number",
"default": 2
},
"defaultColor": {
"title": "Default color",
"type": "string",
@ -530,12 +520,10 @@ export default class TbAnalogueRadialGauge {
"maxValue",
"unitTitle",
"showUnitTitle",
"units",
"majorTicksCount",
"minorTicks",
"valueBox",
"valueInt",
"valueDec",
{
"key": "defaultColor",
"type": "color"

17
ui/src/app/widget/lib/canvas-digital-gauge.js

@ -55,8 +55,9 @@ export default class TbCanvasDigitalGauge {
}
this.localSettings.decimals = (angular.isDefined(settings.decimals) && settings.decimals !== null)
? settings.decimals : 0;
this.localSettings.units = settings.units || '';
? settings.decimals : ctx.decimals;
this.localSettings.units = angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units;
this.localSettings.hideValue = settings.showValue !== true;
this.localSettings.hideMinMax = settings.showMinMax !== true;
@ -313,16 +314,6 @@ export default class TbCanvasDigitalGauge {
"type": "string",
"default": "linear"
},
"decimals": {
"title": "Number of digits after floating point",
"type": "number",
"default": 0
},
"units": {
"title": "Special symbol to show next to value",
"type": "string",
"default": ""
},
"titleFont": {
"title": "Gauge title font",
"type": "object",
@ -556,8 +547,6 @@ export default class TbCanvasDigitalGauge {
}
]
},
"decimals",
"units",
{
"key": "titleFont",
"items": [

198
ui/src/app/widget/lib/flot-widget.js

@ -109,10 +109,7 @@ export default class TbFlot {
});
}
divElement.append(labelSpan);
var valueContent = value.toFixed(trackDecimals);
if (units) {
valueContent += ' ' + units;
}
var valueContent = tbFlot.ctx.utils.formatValue(value, trackDecimals, units);
if (angular.isNumber(percent)) {
valueContent += ' (' + Math.round(percent) + ' %)';
}
@ -134,7 +131,7 @@ export default class TbFlot {
if (this.chartType === 'pie') {
ctx.tooltipFormatter = function(item) {
var divElement = seriesInfoDiv(item.series.label, item.series.color,
item.datapoint[1][0][1], tbFlot.ctx.settings.units, tbFlot.ctx.trackDecimals, true, item.series.percent);
item.datapoint[1][0][1], tbFlot.ctx.trackUnits, tbFlot.ctx.trackDecimals, true, item.series.percent);
return divElement.prop('outerHTML');
};
} else {
@ -157,7 +154,7 @@ export default class TbFlot {
continue;
}
var divElement = seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color,
seriesHoverInfo.value, tbFlot.ctx.settings.units, tbFlot.ctx.trackDecimals, seriesHoverInfo.index === seriesIndex);
seriesHoverInfo.value, tbFlot.ctx.trackUnits, tbFlot.ctx.trackDecimals, seriesHoverInfo.index === seriesIndex);
content += divElement.prop('outerHTML');
}
return content;
@ -165,7 +162,11 @@ export default class TbFlot {
}
var settings = ctx.settings;
ctx.trackDecimals = angular.isDefined(settings.decimals) ? settings.decimals : 1;
ctx.trackDecimals = angular.isDefined(settings.decimals) ?
settings.decimals : ctx.decimals;
ctx.trackUnits = angular.isDefined(settings.units) ? settings.units : ctx.units;
ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false);
ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false;
@ -188,22 +189,9 @@ export default class TbFlot {
},
selection : { mode : ctx.isMobile ? null : 'x' },
legend : {
show: true,
position : 'nw',
labelBoxBorderColor: '#CCCCCC',
backgroundColor : '#F0F0F0',
backgroundOpacity: 0.85,
font: angular.copy(font)
show: false
}
};
if (settings.legend) {
options.legend.show = settings.legend.show !== false;
options.legend.position = settings.legend.position || 'nw';
options.legend.labelBoxBorderColor = settings.legend.labelBoxBorderColor || null;
options.legend.backgroundColor = settings.legend.backgroundColor || null;
options.legend.backgroundOpacity = angular.isDefined(settings.legend.backgroundOpacity) ?
settings.legend.backgroundOpacity : 0.85;
}
if (this.chartType === 'line' || this.chartType === 'bar') {
options.xaxis = {
@ -233,7 +221,7 @@ export default class TbFlot {
options.yaxis.tickFormatter = function() {
return '';
};
} else if (settings.units && settings.units.length > 0) {
} else if (ctx.trackUnits && ctx.trackUnits.length > 0) {
options.yaxis.tickFormatter = function(value, axis) {
var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1,
formatted = "" + Math.round(value * factor) / factor;
@ -245,7 +233,7 @@ export default class TbFlot {
formatted = (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
}
}
formatted += ' ' + tbFlot.ctx.settings.units;
formatted += ' ' + tbFlot.ctx.trackUnits;
return formatted;
};
}
@ -446,47 +434,6 @@ export default class TbFlot {
"title": "Font size",
"type": "number",
"default": 10
},
"decimals": {
"title": "Number of digits after floating point",
"type": "number",
"default": 1
},
"units": {
"title": "Special symbol to show next to value",
"type": "string",
"default": ""
},
"legend": {
"title": "Legend settings",
"type": "object",
"properties": {
"show": {
"title": "Show legend",
"type": "boolean",
"default": true
},
"position": {
"title": "Position",
"type": "string",
"default": "nw"
},
"labelBoxBorderColor": {
"title": "Label box border color",
"type": "string",
"default": "#CCCCCC"
},
"backgroundColor": {
"title": "Background color",
"type": "string",
"default": "#F0F0F0"
},
"backgroundOpacity": {
"title": "Background opacity",
"type": "number",
"default": 0.85
}
}
}
},
"required": []
@ -511,47 +458,7 @@ export default class TbFlot {
"key": "fontColor",
"type": "color"
},
"fontSize",
"decimals",
"units",
{
"key": "legend",
"items": [
"legend.show",
{
"key": "legend.position",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "nw",
"label": "North-west"
},
{
"value": "ne",
"label": "North-east"
},
{
"value": "sw",
"label": "South-west"
},
{
"value": "se",
"label": "Soth-east"
}
]
},
{
"key": "legend.labelBoxBorderColor",
"type": "color"
},
{
"key": "legend.backgroundColor",
"type": "color"
},
"legend.backgroundOpacity"
]
}
"fontSize"
]
}
}
@ -582,16 +489,6 @@ export default class TbFlot {
"type": "number",
"default": 10
},
"decimals": {
"title": "Number of digits after floating point",
"type": "number",
"default": 1
},
"units": {
"title": "Special symbol to show next to value",
"type": "string",
"default": ""
},
"tooltipIndividual": {
"title": "Hover individual points",
"type": "boolean",
@ -638,37 +535,6 @@ export default class TbFlot {
}
}
},
"legend": {
"title": "Legend settings",
"type": "object",
"properties": {
"show": {
"title": "Show legend",
"type": "boolean",
"default": true
},
"position": {
"title": "Position",
"type": "string",
"default": "nw"
},
"labelBoxBorderColor": {
"title": "Label box border color",
"type": "string",
"default": "#CCCCCC"
},
"backgroundColor": {
"title": "Background color",
"type": "string",
"default": "#F0F0F0"
},
"backgroundOpacity": {
"title": "Background opacity",
"type": "number",
"default": 0.85
}
}
},
"xaxis": {
"title": "X axis settings",
"type": "object",
@ -732,8 +598,6 @@ export default class TbFlot {
"type": "color"
},
"fontSize",
"decimals",
"units",
"tooltipIndividual",
"tooltipCumulative",
{
@ -756,44 +620,6 @@ export default class TbFlot {
"grid.horizontalLines"
]
},
{
"key": "legend",
"items": [
"legend.show",
{
"key": "legend.position",
"type": "rc-select",
"multiple": false,
"items": [
{
"value": "nw",
"label": "North-west"
},
{
"value": "ne",
"label": "North-east"
},
{
"value": "sw",
"label": "South-west"
},
{
"value": "se",
"label": "Soth-east"
}
]
},
{
"key": "legend.labelBoxBorderColor",
"type": "color"
},
{
"key": "legend.backgroundColor",
"type": "color"
},
"legend.backgroundOpacity"
]
},
{
"key": "xaxis",
"items": [

2
ui/src/app/widget/save-widget-type-as.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

2
ui/src/app/widget/select-widget-type.tpl.html

@ -26,7 +26,7 @@
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">

4
ui/src/app/widget/widget-editor.tpl.html

@ -237,7 +237,7 @@
<div id="frame_panel" class="tb-split tb-content" style="overflow-y: hidden; position: relative;">
<md-content flex layout="column" class="tb-progress-cover" layout-align="center center"
ng-show="!vm.iframeWidgetEditModeInited">
<md-progress-circular md-mode="indeterminate" class="md-warn"
<md-progress-circular ng-disabled="vm.iframeWidgetEditModeInited" md-mode="indeterminate" class="md-warn"
md-diameter="100"></md-progress-circular>
</md-content>
<div tb-expand-fullscreen expand-button-id="expand-button" style="width: 100%; height: 100%;">
@ -253,6 +253,6 @@
</div>
</div>
<md-content flex layout="column" class="tb-progress-cover" layout-align="center center" ng-show="!vm.layoutInited">
<md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
<md-progress-circular md-mode="indeterminate" ng-disabled="vm.layoutInited" class="md-warn" md-diameter="100"></md-progress-circular>
</md-content>
</div>

39
ui/src/scss/main.scss

@ -203,6 +203,16 @@ md-sidenav {
* THINGSBOARD SPECIFIC
***********************/
.tb-noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome and Opera */
}
.tb-readonly-label {
color: rgba(0,0,0,0.54);
}
@ -219,6 +229,35 @@ label {
}
}
/***********************
* Flow
***********************/
$previewSize: 100px;
.file-input {
display: none;
}
.tb-flow-drop {
position: relative;
border: dashed 2px;
height: $previewSize;
display: flex;
align-items: center;
overflow: hidden;
label {
width: 100%;
font-size: 24px;
text-align: center;
}
}
.tb-container {
position: relative;
margin-top: 32px;
padding: 10px 0;
}
/***********************
* Prompt
***********************/

Loading…
Cancel
Save