446 lines
16 KiB
JavaScript
446 lines
16 KiB
JavaScript
/*
|
|
* Copyright © 2016-2019 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 './timeseries-table-widget.scss';
|
|
|
|
/* eslint-disable import/no-unresolved, import/default */
|
|
|
|
import timeseriesTableWidgetTemplate from './timeseries-table-widget.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.timeseriesTableWidget', [])
|
|
.directive('tbTimeseriesTableWidget', TimeseriesTableWidget)
|
|
.name;
|
|
|
|
/*@ngInject*/
|
|
function TimeseriesTableWidget() {
|
|
return {
|
|
restrict: "E",
|
|
scope: true,
|
|
bindToController: {
|
|
tableId: '=',
|
|
ctx: '='
|
|
},
|
|
controller: TimeseriesTableWidgetController,
|
|
controllerAs: 'vm',
|
|
templateUrl: timeseriesTableWidgetTemplate
|
|
};
|
|
}
|
|
|
|
/*@ngInject*/
|
|
function TimeseriesTableWidgetController($element, $scope, $filter, $timeout, types) {
|
|
var vm = this;
|
|
let dateFormatFilter;
|
|
|
|
vm.sources = [];
|
|
vm.sourceIndex = 0;
|
|
vm.defaultPageSize = 10;
|
|
vm.defaultSortOrder = '-0';
|
|
vm.query = {
|
|
"search": null
|
|
};
|
|
|
|
vm.enterFilterMode = enterFilterMode;
|
|
vm.exitFilterMode = exitFilterMode;
|
|
vm.onRowClick = onRowClick;
|
|
vm.onActionButtonClick = onActionButtonClick;
|
|
vm.actionCellDescriptors = [];
|
|
|
|
function enterFilterMode () {
|
|
vm.query.search = '';
|
|
vm.ctx.hideTitlePanel = true;
|
|
$timeout(()=>{
|
|
angular.element(vm.ctx.$container).find('.searchInput').focus();
|
|
})
|
|
}
|
|
|
|
function exitFilterMode () {
|
|
vm.query.search = null;
|
|
vm.ctx.hideTitlePanel = false;
|
|
}
|
|
|
|
vm.searchAction = {
|
|
name: 'action.search',
|
|
show: true,
|
|
onAction: function() {
|
|
vm.enterFilterMode();
|
|
},
|
|
icon: 'search'
|
|
};
|
|
|
|
$scope.$watch('vm.ctx', function() {
|
|
if (vm.ctx) {
|
|
vm.settings = vm.ctx.settings;
|
|
vm.widgetConfig = vm.ctx.widgetConfig;
|
|
vm.data = vm.ctx.data;
|
|
vm.datasources = vm.ctx.datasources;
|
|
initialize();
|
|
}
|
|
});
|
|
|
|
function initialize() {
|
|
vm.ctx.widgetActions = [ vm.searchAction ];
|
|
vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
|
|
vm.showTimestamp = vm.settings.showTimestamp !== false;
|
|
dateFormatFilter = (vm.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.sss';
|
|
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-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-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 = 'ts-table-' + hashCode(cssString);
|
|
cssParser.cssPreviewNamespace = namespace;
|
|
cssParser.createStyleElement(namespace, cssString);
|
|
$element.addClass(namespace);
|
|
|
|
vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
|
|
|
|
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;
|
|
}
|
|
updateDatasources();
|
|
}
|
|
|
|
$scope.$on('timeseries-table-data-updated', function(event, tableId) {
|
|
if (vm.tableId == tableId) {
|
|
dataUpdated();
|
|
}
|
|
});
|
|
|
|
function dataUpdated() {
|
|
for (var s=0; s < vm.sources.length; s++) {
|
|
var source = vm.sources[s];
|
|
source.rawData = vm.data.slice(source.keyStartIndex, source.keyEndIndex);
|
|
}
|
|
updateSourceData(vm.sources[vm.sourceIndex]);
|
|
$scope.$digest();
|
|
}
|
|
|
|
vm.onPaginate = function(source) {
|
|
updatePage(source);
|
|
}
|
|
|
|
vm.onReorder = function(source) {
|
|
reorder(source);
|
|
updatePage(source);
|
|
}
|
|
|
|
function onRowClick($event, row) {
|
|
if ($event) {
|
|
$event.stopPropagation();
|
|
}
|
|
var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
|
|
if (descriptors.length) {
|
|
var entityId = vm.ctx.activeEntityInfo.entityId;
|
|
var entityName = vm.ctx.activeEntityInfo.entityName;
|
|
var entityLabel = vm.ctx.activeEntityInfo.entityLabel;
|
|
vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, row, entityLabel);
|
|
}
|
|
}
|
|
|
|
function onActionButtonClick($event, row, actionDescriptor) {
|
|
if ($event) {
|
|
$event.stopPropagation();
|
|
}
|
|
var entityId = vm.ctx.activeEntityInfo.entityId;
|
|
var entityName = vm.ctx.activeEntityInfo.entityName;
|
|
var entityLabel = vm.ctx.activeEntityInfo.entityLabel;
|
|
vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel);
|
|
}
|
|
|
|
|
|
vm.cellStyle = function(source, index, value) {
|
|
var style = {};
|
|
if (index > 0) {
|
|
var styleInfo = source.ts.stylesInfo[index-1];
|
|
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
|
|
try {
|
|
style = styleInfo.cellStyleFunction(value);
|
|
} catch (e) {
|
|
style = {};
|
|
}
|
|
}
|
|
}
|
|
return style;
|
|
}
|
|
|
|
vm.cellContent = function(source, index, row, value) {
|
|
if (index === 0) {
|
|
return $filter('date')(value, dateFormatFilter);
|
|
} else {
|
|
var strContent = '';
|
|
if (angular.isDefined(value)) {
|
|
strContent = ''+value;
|
|
}
|
|
var content = strContent;
|
|
var contentInfo = source.ts.contentsInfo[index-1];
|
|
if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {
|
|
try {
|
|
var rowData = source.ts.rowDataTemplate;
|
|
rowData['Timestamp'] = row[0];
|
|
for (var h=0; h < source.ts.header.length; h++) {
|
|
var headerInfo = source.ts.header[h];
|
|
rowData[headerInfo.dataKey.name] = row[headerInfo.index];
|
|
}
|
|
content = contentInfo.cellContentFunction(value, rowData, $filter);
|
|
} catch (e) {
|
|
content = strContent;
|
|
}
|
|
} else {
|
|
var decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : vm.widgetConfig.decimals;
|
|
var units = contentInfo.units || vm.widgetConfig.units;
|
|
content = vm.ctx.utils.formatValue(value, decimals, units, true);
|
|
}
|
|
return content;
|
|
}
|
|
}
|
|
|
|
$scope.$watch('vm.sourceIndex', function(newIndex, oldIndex) {
|
|
if (newIndex != oldIndex) {
|
|
updateSourceData(vm.sources[vm.sourceIndex]);
|
|
updateActiveEntityInfo();
|
|
}
|
|
});
|
|
|
|
function updateActiveEntityInfo() {
|
|
var source = vm.sources[vm.sourceIndex];
|
|
var activeEntityInfo = null;
|
|
if (source) {
|
|
var datasource = source.datasource;
|
|
if (datasource.type === types.datasourceType.entity &&
|
|
datasource.entityType && datasource.entityId) {
|
|
activeEntityInfo = {
|
|
entityId: {
|
|
entityType: datasource.entityType,
|
|
id: datasource.entityId
|
|
},
|
|
entityName: datasource.entityName
|
|
};
|
|
}
|
|
}
|
|
vm.ctx.activeEntityInfo = activeEntityInfo;
|
|
}
|
|
|
|
function updateDatasources() {
|
|
vm.sources = [];
|
|
vm.sourceIndex = 0;
|
|
var keyOffset = 0;
|
|
if (vm.datasources) {
|
|
for (var ds = 0; ds < vm.datasources.length; ds++) {
|
|
var source = {};
|
|
var datasource = vm.datasources[ds];
|
|
source.keyStartIndex = keyOffset;
|
|
keyOffset += datasource.dataKeys.length;
|
|
source.keyEndIndex = keyOffset;
|
|
source.datasource = datasource;
|
|
source.data = [];
|
|
source.rawData = [];
|
|
source.query = {
|
|
limit: vm.settings.defaultPageSize || 10,
|
|
page: 1,
|
|
order: '-0'
|
|
}
|
|
source.ts = {
|
|
header: [],
|
|
count: 0,
|
|
data: [],
|
|
stylesInfo: [],
|
|
contentsInfo: [],
|
|
rowDataTemplate: {}
|
|
}
|
|
source.ts.rowDataTemplate['Timestamp'] = null;
|
|
for (var a = 0; a < datasource.dataKeys.length; a++ ) {
|
|
var dataKey = datasource.dataKeys[a];
|
|
var keySettings = dataKey.settings;
|
|
source.ts.header.push({
|
|
index: a+1,
|
|
dataKey: dataKey
|
|
});
|
|
source.ts.rowDataTemplate[dataKey.label] = null;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
source.ts.stylesInfo.push({
|
|
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, rowData, filter', keySettings.cellContentFunction);
|
|
useCellContentFunction = true;
|
|
} catch (e) {
|
|
cellContentFunction = null;
|
|
useCellContentFunction = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
source.ts.contentsInfo.push({
|
|
useCellContentFunction: useCellContentFunction,
|
|
cellContentFunction: cellContentFunction,
|
|
units: dataKey.units,
|
|
decimals: dataKey.decimals
|
|
});
|
|
|
|
}
|
|
vm.sources.push(source);
|
|
}
|
|
}
|
|
updateActiveEntityInfo();
|
|
}
|
|
|
|
function updatePage(source) {
|
|
var startIndex = source.query.limit * (source.query.page - 1);
|
|
source.ts.data = source.data.slice(startIndex, startIndex + source.query.limit);
|
|
}
|
|
|
|
function reorder(source) {
|
|
let searchRegExp = new RegExp(vm.query.search);
|
|
|
|
source.data = $filter('orderBy')(source.data, source.query.order);
|
|
if (vm.query.search !== null) {
|
|
source.data = source.data.filter(function(item){
|
|
for (let i = 0; i < item.length; i++) {
|
|
if (vm.showTimestamp) {
|
|
if (i === 0) {
|
|
if (searchRegExp.test($filter('date')(item[i], dateFormatFilter))) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (searchRegExp.test(item[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
if (searchRegExp.test(item[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function convertData(data) {
|
|
var rowsMap = {};
|
|
for (var d = 0; d < data.length; d++) {
|
|
var columnData = data[d].data;
|
|
for (var i = 0; i < columnData.length; i++) {
|
|
var cellData = columnData[i];
|
|
var timestamp = cellData[0];
|
|
var row = rowsMap[timestamp];
|
|
if (!row) {
|
|
row = [];
|
|
row[0] = timestamp;
|
|
for (var c = 0; c < data.length; c++) {
|
|
row[c+1] = undefined;
|
|
}
|
|
rowsMap[timestamp] = row;
|
|
}
|
|
row[d+1] = cellData[1];
|
|
}
|
|
}
|
|
var rows = [];
|
|
for (var t in rowsMap) {
|
|
if (vm.settings.hideEmptyLines)
|
|
{
|
|
var hideLine = true;
|
|
for (var _c = 0; (_c < data.length) && hideLine; _c++) {
|
|
if (rowsMap[t][_c+1])
|
|
hideLine = false;
|
|
}
|
|
if (!hideLine)
|
|
rows.push(rowsMap[t]);
|
|
}
|
|
else
|
|
rows.push(rowsMap[t]);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
function updateSourceData(source) {
|
|
source.data = convertData(source.rawData);
|
|
source.ts.count = source.data.length;
|
|
reorder(source);
|
|
updatePage(source);
|
|
}
|
|
|
|
}
|