Merge pull request #502 from jktu2870/improvement/timeseriesTable/search
"Timeseries table" widget improvements (search)
This commit is contained in:
commit
627c7bd1bc
@ -112,9 +112,9 @@
|
||||
"templateHtml": "<tb-timeseries-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-timeseries-table-widget>",
|
||||
"templateCss": "",
|
||||
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('timeseries-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}",
|
||||
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\"\n ]\n}",
|
||||
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"displayPagination\",\n \"defaultPageSize\"\n ]\n}",
|
||||
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
|
||||
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false}"
|
||||
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1213,6 +1213,7 @@ export default angular.module('thingsboard.locale', [])
|
||||
"remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?",
|
||||
"remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.",
|
||||
"timeseries": "Time series",
|
||||
"search-data": "Search data",
|
||||
"latest-values": "Latest values",
|
||||
"rpc": "Control widget",
|
||||
"alarm": "Alarm widget",
|
||||
|
||||
@ -47,9 +47,37 @@ function TimeseriesTableWidget() {
|
||||
/*@ngInject*/
|
||||
function TimeseriesTableWidgetController($element, $scope, $filter) {
|
||||
var vm = this;
|
||||
let dateFormatFilter = 'yyyy-MM-dd HH:mm:ss';
|
||||
|
||||
vm.sources = [];
|
||||
vm.sourceIndex = 0;
|
||||
vm.defaultPageSize = 10;
|
||||
vm.defaultSortOrder = '-0';
|
||||
vm.query = {
|
||||
"search": null
|
||||
};
|
||||
|
||||
vm.enterFilterMode = enterFilterMode;
|
||||
vm.exitFilterMode = exitFilterMode;
|
||||
|
||||
function enterFilterMode () {
|
||||
vm.query.search = '';
|
||||
vm.ctx.hideTitlePanel = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -62,6 +90,7 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
|
||||
});
|
||||
|
||||
function initialize() {
|
||||
vm.ctx.widgetActions = [ vm.searchAction ];
|
||||
vm.showTimestamp = vm.settings.showTimestamp !== false;
|
||||
var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)';
|
||||
var defaultColor = tinycolor(origColor);
|
||||
@ -108,6 +137,8 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
|
||||
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;
|
||||
@ -163,7 +194,7 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
|
||||
|
||||
vm.cellContent = function(source, index, row, value) {
|
||||
if (index === 0) {
|
||||
return $filter('date')(value, 'yyyy-MM-dd HH:mm:ss');
|
||||
return $filter('date')(value, dateFormatFilter);
|
||||
} else {
|
||||
var strContent = '';
|
||||
if (angular.isDefined(value)) {
|
||||
@ -211,7 +242,7 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
|
||||
source.data = [];
|
||||
source.rawData = [];
|
||||
source.query = {
|
||||
limit: 5,
|
||||
limit: vm.settings.defaultPageSize || 10,
|
||||
page: 1,
|
||||
order: '-0'
|
||||
}
|
||||
@ -287,7 +318,30 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@ -26,4 +26,8 @@ tb-timeseries-table-widget {
|
||||
.md-table-pagination>* {
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.tb-data-table md-toolbar {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,29 +15,71 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div class="tb-absolute-fill tb-entities-table tb-data-table timeseriesWidget" layout="column">
|
||||
<div flex class="tb-absolute-fill" layout="column">
|
||||
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search !== null">
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
|
||||
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
|
||||
<md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
|
||||
{{'entity.search' | translate}}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<md-input-container flex>
|
||||
<label> </label>
|
||||
<input ng-model="vm.query.search" placeholder="{{'widget.search-data' | translate}}" md-autofocus/>
|
||||
</md-input-container>
|
||||
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
|
||||
<md-icon aria-label="Close" class="material-icons">close</md-icon>
|
||||
<md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
|
||||
{{ 'action.close' | translate }}
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-tabs md-selected="vm.sourceIndex" ng-class="{'tb-headless': vm.sources.length === 1}"
|
||||
id="tabs" md-border-bottom flex class="tb-absolute-fill">
|
||||
<md-tab ng-repeat="source in vm.sources" label="{{ source.datasource.name }}">
|
||||
<md-table-container>
|
||||
<table md-table>
|
||||
<thead md-head md-order="source.query.order" md-on-reorder="vm.onReorder(source)">
|
||||
<tr md-row>
|
||||
<th ng-show="vm.showTimestamp" md-column md-order-by="0"><span>Timestamp</span></th>
|
||||
<th md-column md-order-by="{{ h.index }}" ng-repeat="h in source.ts.header"><span>{{ h.dataKey.label }}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody md-body>
|
||||
<tr md-row ng-repeat="row in source.ts.data">
|
||||
<td ng-show="$index > 0 || ($index === 0 && vm.showTimestamp)" md-cell ng-repeat="d in row track by $index" ng-style="vm.cellStyle(source, $index, d)" ng-bind-html="vm.cellContent(source, $index, row, d)">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</md-table-container>
|
||||
<md-table-pagination md-limit="source.query.limit" md-limit-options="[5, 10, 15]"
|
||||
md-page="source.query.page" md-total="{{source.ts.count}}"
|
||||
md-on-paginate="vm.onPaginate(source)" md-page-select>
|
||||
</md-table-pagination>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
<md-tabs flex md-selected="vm.sourceIndex" ng-class="{'tb-headless': vm.sources.length === 1}"
|
||||
id="tabs" md-border-bottom flex>
|
||||
<md-tab ng-repeat="source in vm.sources" label="{{ source.datasource.name }}">
|
||||
<md-table-container>
|
||||
<table md-table>
|
||||
<thead md-head md-order="source.query.order" md-on-reorder="vm.onReorder(source)">
|
||||
<tr md-row>
|
||||
<th ng-show="vm.showTimestamp"
|
||||
md-column md-order-by="0"
|
||||
>
|
||||
<span>Timestamp</span>
|
||||
</th>
|
||||
<th md-column
|
||||
md-order-by="{{ h.index }}"
|
||||
ng-repeat="h in source.ts.header"
|
||||
>
|
||||
<span>{{ h.dataKey.label }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody md-body>
|
||||
<tr md-row ng-repeat="row in source.ts.data track by $index">
|
||||
<td ng-show="$index > 0 || ($index === 0 && vm.showTimestamp)"
|
||||
md-cell
|
||||
ng-repeat="d in row track by $index"
|
||||
ng-style="vm.cellStyle(source, $index, d)"
|
||||
ng-bind-html="vm.cellContent(source, $index, row, d)"
|
||||
></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</md-table-container>
|
||||
<md-table-pagination ng-if="vm.displayPagination"
|
||||
md-limit="source.query.limit"
|
||||
md-limit-options="vm.limitOptions"
|
||||
md-page="source.query.page"
|
||||
md-total="{{source.data.length}}"
|
||||
md-on-paginate="vm.onPaginate(source)"
|
||||
md-page-select>
|
||||
</md-table-pagination>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user