diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 85227e76a9..450b9761e9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.controller; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -49,6 +51,11 @@ public class DashboardController extends BaseController { public static final String DASHBOARD_ID = "dashboardId"; + @Value("${dashboard.max_datapoints_limit}") + @Getter + private long maxDatapointsLimit; + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) @ResponseBody @@ -56,6 +63,13 @@ public class DashboardController extends BaseController { return System.currentTimeMillis(); } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET) + @ResponseBody + public long getMaxDatapointsLimit() throws ThingsboardException { + return maxDatapointsLimit; + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) @ResponseBody diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1054007c98..5eef1bdda0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -77,6 +77,11 @@ security: # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}" +# Dashboard parameters +dashboard: + # Maximum allowed datapoints fetched by widgets + max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" + # Device communication protocol parameters http: request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js index b8d0c41cbc..56447aa212 100644 --- a/ui/src/app/api/time.service.js +++ b/ui/src/app/api/time.service.js @@ -26,15 +26,17 @@ const MIN_INTERVAL = SECOND; const MAX_INTERVAL = 365 * 20 * DAY; const MIN_LIMIT = 10; -const AVG_LIMIT = 200; -const MAX_LIMIT = 500; +//const AVG_LIMIT = 200; +//const MAX_LIMIT = 500; /*@ngInject*/ -function TimeService($translate, types) { +function TimeService($translate, $http, $q, types) { var predefIntervals; + var maxDatapointsLimit; var service = { + loadMaxDatapointsLimit: loadMaxDatapointsLimit, minIntervalLimit: minIntervalLimit, maxIntervalLimit: maxIntervalLimit, boundMinInterval: boundMinInterval, @@ -45,20 +47,38 @@ function TimeService($translate, types) { defaultTimewindow: defaultTimewindow, toHistoryTimewindow: toHistoryTimewindow, createSubscriptionTimewindow: createSubscriptionTimewindow, - avgAggregationLimit: function () { - return AVG_LIMIT; + getMaxDatapointsLimit: function () { + return maxDatapointsLimit; + }, + getMinDatapointsLimit: function () { + return MIN_LIMIT; } } return service; + function loadMaxDatapointsLimit() { + var deferred = $q.defer(); + var url = '/api/dashboard/maxDatapointsLimit'; + $http.get(url, {ignoreLoading: true}).then(function success(response) { + maxDatapointsLimit = response.data; + if (!maxDatapointsLimit || maxDatapointsLimit <= MIN_LIMIT) { + maxDatapointsLimit = MIN_LIMIT + 1; + } + deferred.resolve(); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + function minIntervalLimit(timewindow) { - var min = timewindow / MAX_LIMIT; + var min = timewindow / 500; return boundMinInterval(min); } function avgInterval(timewindow) { - var avg = timewindow / AVG_LIMIT; + var avg = timewindow / 200; return boundMinInterval(avg); } @@ -230,7 +250,7 @@ function TimeService($translate, types) { }, aggregation: { type: types.aggregation.avg.value, - limit: AVG_LIMIT + limit: Math.floor(maxDatapointsLimit / 2) } } return timewindow; @@ -246,22 +266,27 @@ function TimeService($translate, types) { } var aggType; + var limit; if (timewindow.aggregation) { aggType = timewindow.aggregation.type || types.aggregation.avg.value; + limit = timewindow.aggregation.limit || maxDatapointsLimit; } else { aggType = types.aggregation.avg.value; + limit = maxDatapointsLimit; } + var historyTimewindow = { history: { fixedTimewindow: { startTimeMs: startTimeMs, endTimeMs: endTimeMs }, - interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, aggType) + interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, types.aggregation.avg.value) }, aggregation: { - type: aggType + type: aggType, + limit: limit } } @@ -275,7 +300,7 @@ function TimeService($translate, types) { realtimeWindowMs: null, aggregation: { interval: SECOND, - limit: AVG_LIMIT, + limit: maxDatapointsLimit, type: types.aggregation.avg.value } }; @@ -283,14 +308,14 @@ function TimeService($translate, types) { if (stateData) { subscriptionTimewindow.aggregation = { interval: SECOND, - limit: MAX_LIMIT, + limit: maxDatapointsLimit, type: types.aggregation.none.value, stateData: true }; } else { subscriptionTimewindow.aggregation = { interval: SECOND, - limit: AVG_LIMIT, + limit: maxDatapointsLimit, type: types.aggregation.avg.value }; } @@ -298,7 +323,7 @@ function TimeService($translate, types) { if (angular.isDefined(timewindow.aggregation) && !stateData) { subscriptionTimewindow.aggregation = { type: timewindow.aggregation.type || types.aggregation.avg.value, - limit: timewindow.aggregation.limit || AVG_LIMIT + limit: timewindow.aggregation.limit || maxDatapointsLimit }; } if (angular.isDefined(timewindow.realtime)) { diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js index fa4d63c8e2..a49c4a040a 100644 --- a/ui/src/app/api/user.service.js +++ b/ui/src/app/api/user.service.js @@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin, .name; /*@ngInject*/ -function UserService($http, $q, $rootScope, adminService, dashboardService, loginService, toast, store, jwtHelper, $translate, $state, $location) { +function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location) { var currentUser = null, currentUserDetails = null, lastPublicDashboardId = null, @@ -390,6 +390,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi function loadSystemParams() { var promises = []; promises.push(loadIsUserTokenAccessEnabled()); + promises.push(timeService.loadMaxDatapointsLimit()); return $q.all(promises); } diff --git a/ui/src/app/components/timewindow-panel.controller.js b/ui/src/app/components/timewindow-panel.controller.js index 3cf368b6db..d48355c3d4 100644 --- a/ui/src/app/components/timewindow-panel.controller.js +++ b/ui/src/app/components/timewindow-panel.controller.js @@ -31,6 +31,8 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic vm.maxRealtimeAggInterval = maxRealtimeAggInterval; vm.minHistoryAggInterval = minHistoryAggInterval; vm.maxHistoryAggInterval = maxHistoryAggInterval; + vm.minDatapointsLimit = minDatapointsLimit; + vm.maxDatapointsLimit = maxDatapointsLimit; if (vm.historyOnly) { vm.timewindow.selectedTab = 1; @@ -86,6 +88,14 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic return timeService.maxIntervalLimit(currentHistoryTimewindow()); } + function minDatapointsLimit () { + return timeService.getMinDatapointsLimit(); + } + + function maxDatapointsLimit () { + return timeService.getMaxDatapointsLimit(); + } + function currentHistoryTimewindow() { if (vm.timewindow.history.historyType === 0) { return vm.timewindow.history.timewindowMs; diff --git a/ui/src/app/components/timewindow-panel.tpl.html b/ui/src/app/components/timewindow-panel.tpl.html index 271ac4cc24..50888ac129 100644 --- a/ui/src/app/components/timewindow-panel.tpl.html +++ b/ui/src/app/components/timewindow-panel.tpl.html @@ -60,19 +60,22 @@ - + aggregation.limit - + - - + + - - diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js index 651367179c..4a6b72e7f2 100644 --- a/ui/src/app/components/timewindow.directive.js +++ b/ui/src/app/components/timewindow.directive.js @@ -228,7 +228,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM if (angular.isDefined(value.aggregation.type) && value.aggregation.type.length > 0) { model.aggregation.type = value.aggregation.type; } - model.aggregation.limit = value.aggregation.limit || timeService.avgAggregationLimit(); + model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); } } scope.updateDisplayValue();