thingsboard-aplcache

Details

diff --git a/ui/src/app/api/data-aggregator.js b/ui/src/app/api/data-aggregator.js
index 31baff1..76d3b1c 100644
--- a/ui/src/app/api/data-aggregator.js
+++ b/ui/src/app/api/data-aggregator.js
@@ -74,7 +74,7 @@ export default class DataAggregator {
         }, this.aggregationTimeout, false);
     }
 
-    onData(data, update, history) {
+    onData(data, update, history, apply) {
         if (!this.dataReceived || this.resetPending) {
             var updateIntervalScheduledTime = true;
             if (!this.dataReceived) {
@@ -96,18 +96,18 @@ export default class DataAggregator {
             if (updateIntervalScheduledTime) {
                 this.intervalScheduledTime = currentTime();
             }
-            this.onInterval(history);
+            this.onInterval(history, apply);
         } else {
             updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
                 this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs);
             if (history) {
                 this.intervalScheduledTime = currentTime();
-                this.onInterval(history);
+                this.onInterval(history, apply);
             }
         }
     }
 
-    onInterval(history) {
+    onInterval(history, apply) {
         var now = currentTime();
         this.elapsed += now - this.intervalScheduledTime;
         this.intervalScheduledTime = now;
@@ -127,7 +127,7 @@ export default class DataAggregator {
             this.data = toData(this.tsKeyNames, this.aggregationMap, this.startTs, this.endTs, this.$filter, this.limit);
         }
         if (this.onDataCb) {
-            this.onDataCb(this.data, this.startTs, this.endTs);
+            this.onDataCb(this.data, this.startTs, this.endTs, apply);
         }
 
         var self = this;
diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js
index 7d2c1be..5458ada 100644
--- a/ui/src/app/api/datasource.service.js
+++ b/ui/src/app/api/datasource.service.js
@@ -197,7 +197,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                     var datasourceKey = key + '_' + i;
                     listener.dataUpdated(datasourceData[datasourceKey],
                         listener.datasourceIndex,
-                        dataKey.index);
+                        dataKey.index, false);
                 }
             }
         } else {
@@ -205,7 +205,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                 dataKey = dataKeys[key];
                 listener.dataUpdated(datasourceData[key],
                     listener.datasourceIndex,
-                    dataKey.index);
+                    dataKey.index, false);
             }
         }
     }
@@ -264,7 +264,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                         type: types.dataKeyType.timeseries,
                         onData: function (data) {
                             if (data.data) {
-                                onData(data.data, types.dataKeyType.timeseries);
+                                onData(data.data, types.dataKeyType.timeseries, null, null, true);
                             }
                         },
                         onReconnected: function() {}
@@ -287,9 +287,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
 
                     if (datasourceSubscription.type === types.widgetType.timeseries.value) {
                         updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw);
-                        dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames);
+                        dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames, types.dataKeyType.timeseries);
                         subscriber.onData = function(data) {
-                            dataAggregator.onData(data);
+                            dataAggregator.onData(data, false, false, true);
                         }
                         subscriber.onReconnected = function() {
                             var newSubsTw = null;
@@ -308,7 +308,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                         subscriber.onReconnected = function() {}
                         subscriber.onData = function(data) {
                             if (data.data) {
-                                onData(data.data, types.dataKeyType.timeseries);
+                                onData(data.data, types.dataKeyType.timeseries, null, null, true);
                             }
                         }
                     }
@@ -331,7 +331,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                     type: types.dataKeyType.attribute,
                     onData: function (data) {
                         if (data.data) {
-                            onData(data.data, types.dataKeyType.attribute);
+                            onData(data.data, types.dataKeyType.attribute, null, null, true);
                         }
                     },
                     onReconnected: function() {}
@@ -351,33 +351,24 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                         tsKeyNames.push(dataKey.name+'_'+dataKey.index);
                     }
                 }
-                dataAggregator = new DataAggregator(
-                    function (data, startTs, endTs) {
-                        onData(data, types.dataKeyType.function, startTs, endTs);
-                    },
-                    tsKeyNames,
-                    subsTw.startTs,
-                    subsTw.aggregation.limit,
-                    subsTw.aggregation.type,
-                    subsTw.aggregation.timeWindow,
-                    subsTw.aggregation.interval,
-                    types,
-                    $timeout,
-                    $filter
-                );
+                dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames, types.dataKeyType.function);
             }
             if (history) {
-                onTick();
+                onTick(false);
             } else {
-                timer = $timeout(onTick, 0, false);
+                timer = $timeout(
+                    function() {onTick(true)},
+                    0,
+                    false
+                );
             }
         }
     }
 
-    function createRealtimeDataAggregator(subsTw, tsKeyNames) {
+    function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) {
         return new DataAggregator(
-            function(data, startTs, endTs) {
-                onData(data, types.dataKeyType.timeseries, startTs, endTs);
+            function(data, startTs, endTs, apply) {
+                onData(data, dataKeyType, startTs, endTs, apply);
             },
             tsKeyNames,
             subsTw.startTs,
@@ -443,7 +434,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
         return data;
     }
 
-    function generateLatest(dataKey) {
+    function generateLatest(dataKey, apply) {
         var prevSeries;
         var datasourceKeyData = datasourceData[dataKey.key].data;
         if (datasourceKeyData.length > 0) {
@@ -461,11 +452,11 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
             var listener = listeners[i];
             listener.dataUpdated(datasourceData[dataKey.key],
                 listener.datasourceIndex,
-                dataKey.index);
+                dataKey.index, apply);
         }
     }
 
-    function onTick() {
+    function onTick(apply) {
         var key;
         if (datasourceSubscription.type === types.widgetType.timeseries.value) {
             var startTime;
@@ -495,15 +486,15 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                     generatedData.data[dataKey.name+'_'+dataKey.index] = data;
                 }
             }
-            dataAggregator.onData(generatedData, true, history);
+            dataAggregator.onData(generatedData, true, history, apply);
         } else if (datasourceSubscription.type === types.widgetType.latest.value) {
             for (key in dataKeys) {
-                generateLatest(dataKeys[key]);
+                generateLatest(dataKeys[key], apply);
             }
         }
 
         if (!history) {
-            timer = $timeout(onTick, frequency / 2, false);
+            timer = $timeout(function() {onTick(true)}, frequency / 2, false);
         }
     }
 
@@ -519,7 +510,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
         }
     }
 
-    function onData(sourceData, type, startTs, endTs) {
+    function onData(sourceData, type, startTs, endTs, apply) {
         for (var keyName in sourceData) {
             var keyData = sourceData[keyName];
             var key = keyName + '_' + type;
@@ -572,7 +563,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                             var listener = listeners[i2];
                             listener.dataUpdated(datasourceData[datasourceKey],
                                 listener.datasourceIndex,
-                                dataKey.index);
+                                dataKey.index, apply);
                         }
                     }
                 }
diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js
index a4bb571..14f4c63 100644
--- a/ui/src/app/api/time.service.js
+++ b/ui/src/app/api/time.service.js
@@ -206,9 +206,9 @@ function TimeService($translate, types) {
     function defaultTimewindow() {
         var currentTime = (new Date).getTime();
         var timewindow = {
-                displayValue: "",
-                selectedTab: 0,
-                realtime: {
+            displayValue: "",
+            selectedTab: 0,
+            realtime: {
                 interval: SECOND,
                 timewindowMs: MINUTE // 1 min by default
             },
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index 3889339..1141add 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -52,6 +52,7 @@ function Dashboard() {
         bindToController: {
             widgets: '=',
             deviceAliasList: '=',
+            dashboardTimewindow: '=?',
             columns: '=',
             margins: '=',
             isEdit: '=',
@@ -71,7 +72,8 @@ function Dashboard() {
             getStDiff: '&?',
             onInit: '&?',
             onInitFailed: '&?',
-            dashboardStyle: '=?'
+            dashboardStyle: '=?',
+            dashboardClass: '=?'
         },
         controller: DashboardController,
         controllerAs: 'vm',
@@ -80,7 +82,7 @@ function Dashboard() {
 }
 
 /*@ngInject*/
-function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $log, toast, types) {
+function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types) {
 
     var highlightedMode = false;
     var highlightedWidget = null;
@@ -99,6 +101,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
 
     vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
 
+    if (!('dashboardTimewindow' in vm)) {
+        vm.dashboardTimewindow = timeService.defaultTimewindow();
+    }
+
     vm.dashboardLoading = true;
     vm.visibleRect = {
         top: 0,
@@ -176,6 +182,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     vm.widgetContextMenuItems = [];
     vm.widgetContextMenuEvent = null;
 
+    vm.dashboardTimewindowApi = {
+        onResetTimewindow: function() {
+            if (vm.originalDashboardTimewindow) {
+                vm.dashboardTimewindow = angular.copy(vm.originalDashboardTimewindow);
+                vm.originalDashboardTimewindow = null;
+            }
+        },
+        onUpdateTimewindow: function(startTimeMs, endTimeMs) {
+            if (!vm.originalDashboardTimewindow) {
+                vm.originalDashboardTimewindow = angular.copy(vm.dashboardTimewindow);
+            }
+            vm.dashboardTimewindow = timeService.toHistoryTimewindow(vm.dashboardTimewindow, startTimeMs, endTimeMs);
+        }
+    };
+
     //$element[0].onmousemove=function(){
     //    widgetMouseMove();
    // }
@@ -656,7 +677,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
     }
 
     function hasTimewindow(widget) {
-        return widget.type === types.widgetType.timeseries.value;
+        if (widget.type === types.widgetType.timeseries.value) {
+            return angular.isDefined(widget.config.useDashboardTimewindow) ?
+                !widget.config.useDashboardTimewindow : false;
+        } else {
+            return false;
+        }
     }
 
     function adoptMaxRows() {
@@ -673,6 +699,9 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
 
     function dashboardLoaded() {
         $timeout(function () {
+            $scope.$watch('vm.dashboardTimewindow', function () {
+                $scope.$broadcast('dashboardTimewindowChanged', vm.dashboardTimewindow);
+            }, true);
             adoptMaxRows();
             vm.dashboardLoading = false;
             $timeout(function () {
diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss
index 4c4af47..cf56c15 100644
--- a/ui/src/app/components/dashboard.scss
+++ b/ui/src/app/components/dashboard.scss
@@ -51,7 +51,7 @@ div.tb-widget {
       height: 32px;
       min-width: 32px;
       min-height: 32px;
-      md-icon {
+      md-icon, ng-md-icon {
         width: 20px;
         height: 20px;
         min-width: 20px;
@@ -93,6 +93,7 @@ md-content.tb-dashboard-content {
   right: 0;
   bottom: 0;
   outline: none;
+  background: none;
   .gridster-item {
       @include transition(none);
   }
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index dc46e35..114460d 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -21,7 +21,7 @@
 </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)">
-		<div ng-style="vm.dashboardStyle" id="gridster-background" style="height: auto; min-height: 100%;">
+		<div ng-class="vm.dashboardClass" id="gridster-background" style="height: auto; min-height: 100%;">
 			<div id="gridster-child" gridster="vm.gridsterOpts">
 				<ul>
 		<!-- 			    			ng-click="widgetClicked($event, widget)"  -->
@@ -30,6 +30,7 @@
 							<div tb-expand-fullscreen
 								 		fullscreen-background-style="vm.dashboardStyle"
 								        expand-button-id="expand-button"
+								 		expand-button-size="20"
 								 		on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)"
 								 		layout="column"
 								 		class="tb-widget"
@@ -45,55 +46,55 @@
             									   color: vm.widgetColor(widget),
             									   backgroundColor: vm.widgetBackgroundColor(widget),
             									   padding: vm.widgetPadding(widget)}">
-								<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
+								<div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
 									<span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span>
-									<tb-timewindow button-color="vm.widgetColor(widget)" aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
+									<tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
 								</div>
 								<div class="tb-widget-actions" layout="row" layout-align="start center">
 									<md-button id="expand-button"
 											   ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)"
 											   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
-											   class="md-icon-button md-primary"></md-button>
+											   class="md-icon-button"></md-button>
 									<md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
 											   ng-disabled="vm.loading()"
-											   class="md-icon-button md-primary"
+											   class="md-icon-button"
 											   ng-click="vm.editWidget($event, widget)"
 											   aria-label="{{ 'widget.edit' | translate }}">
 										<md-tooltip md-direction="top">
 											{{ 'widget.edit' | translate }}
 										</md-tooltip>
-										<md-icon class="material-icons">
-											edit
-										</md-icon>
+										<ng-md-icon size="20" icon="edit"></ng-md-icon>
 									</md-button>
 									<md-button ng-show="vm.isExportActionEnabled && !vm.isWidgetExpanded"
 											   ng-disabled="vm.loading()"
-											   class="md-icon-button md-primary"
+											   class="md-icon-button"
 											   ng-click="vm.exportWidget($event, widget)"
 											   aria-label="{{ 'widget.export' | translate }}">
 										<md-tooltip md-direction="top">
 											{{ 'widget.export' | translate }}
 										</md-tooltip>
-										<md-icon class="material-icons">
-											file_download
-										</md-icon>
+										<ng-md-icon size="20" icon="file_download"></ng-md-icon>
 									</md-button>
 									<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
 										ng-disabled="vm.loading()"
-										class="md-icon-button md-primary"
+										class="md-icon-button"
 										ng-click="vm.removeWidget($event, widget)"
 										aria-label="{{ 'widget.remove' | translate }}">
 										<md-tooltip md-direction="top">
 											{{ 'widget.remove' | translate }}
 										</md-tooltip>
-										<md-icon class="material-icons">
-										  close
-										</md-icon>
+										<ng-md-icon size="20" icon="close"></ng-md-icon>
 									</md-button>
 								</div>
 								<div flex layout="column" class="tb-widget-content">
 									<div flex tb-widget
-										 locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isEdit: vm.isEdit, stDiff: vm.stDiff }">
+										 locals="{ visibleRect: vm.visibleRect,
+										 widget: widget,
+										 deviceAliasList: vm.deviceAliasList,
+										 isEdit: vm.isEdit,
+										 stDiff: vm.stDiff,
+										 dashboardTimewindow: vm.dashboardTimewindow,
+										 dashboardTimewindowApi: vm.dashboardTimewindowApi }">
 									</div>
 								</div>
 							</div>
diff --git a/ui/src/app/components/expand-fullscreen.directive.js b/ui/src/app/components/expand-fullscreen.directive.js
index 16bd713..f74aa91 100644
--- a/ui/src/app/components/expand-fullscreen.directive.js
+++ b/ui/src/app/components/expand-fullscreen.directive.js
@@ -101,11 +101,15 @@ function ExpandFullscreen($compile, $document) {
         if (attrs.expandButtonId) {
             expandButton = $('#' + attrs.expandButtonId, element)[0];
         }
+        var buttonSize;
+        if (attrs.expandButtonSize) {
+            buttonSize = attrs.expandButtonSize;
+        }
 
         var html = '<md-tooltip md-direction="{{expanded ? \'bottom\' : \'top\'}}">' +
             '{{(expanded ? \'fullscreen.exit\' : \'fullscreen.expand\') | translate}}' +
             '</md-tooltip>' +
-            '<ng-md-icon icon="{{expanded ? \'fullscreen_exit\' : \'fullscreen\'}}" ' +
+            '<ng-md-icon ' + (buttonSize ? 'size="'+ buttonSize +'" ' : '') + 'icon="{{expanded ? \'fullscreen_exit\' : \'fullscreen\'}}" ' +
             'options=\'{"easing": "circ-in-out", "duration": 375, "rotation": "none"}\'>' +
             '</ng-md-icon>';
 
diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js
index 14fff62..580b21b 100644
--- a/ui/src/app/components/timewindow.directive.js
+++ b/ui/src/app/components/timewindow.directive.js
@@ -79,26 +79,38 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
         if (scope.asButton) {
             template = $templateCache.get(timewindowButtonTemplate);
         } else {
+            scope.direction = scope.direction || 'left';
             template = $templateCache.get(timewindowTemplate);
         }
         element.html(template);
 
         scope.openEditMode = function (event) {
+            if (scope.disabled) {
+                return;
+            }
             var position;
             var isGtSm = $mdMedia('gt-sm');
             if (isGtSm) {
                 var panelHeight = 375;
+                var panelWidth = 417;
                 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($mdPanel.xPosition.ALIGN_START, yPosition);
+                    .addPanelPosition(xPosition, yPosition);
             } else {
                 position = $mdPanel.newPanelPosition()
                     .absolute()
@@ -223,7 +235,8 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
         require: "^ngModel",
         scope: {
             asButton: '=asButton',
-            buttonColor: '=?'
+            direction: '=?',
+            disabled:'=ngDisabled'
         },
         link: linker
     };
diff --git a/ui/src/app/components/timewindow.scss b/ui/src/app/components/timewindow.scss
index 0c85d52..9ea7c34 100644
--- a/ui/src/app/components/timewindow.scss
+++ b/ui/src/app/components/timewindow.scss
@@ -57,3 +57,10 @@
     }
   }
 }
+
+tb-timewindow {
+  span {
+    pointer-events: all;
+    cursor: pointer;
+  }
+}
diff --git a/ui/src/app/components/timewindow.tpl.html b/ui/src/app/components/timewindow.tpl.html
index 0de81e2..2778975 100644
--- a/ui/src/app/components/timewindow.tpl.html
+++ b/ui/src/app/components/timewindow.tpl.html
@@ -15,9 +15,23 @@
     limitations under the License.
 
 -->
-<section layout='row' layout-align="start center" style="min-height: 32px;">
-	<span ng-click="openEditMode($event)">{{model.displayValue}}</span>
-    <md-button class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
-     	  <md-icon ng-style="{ color: buttonColor }" aria-label="{{ 'timewindow.date-range' | translate }}" class="material-icons">date_range</md-icon>
+<section layout='row' layout-align="start center" ng-style="{minHeight: '32px', padding: '0 6px'}">
+    <md-button ng-if="direction === 'left'" ng-disabled="disabled" class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
+        <md-tooltip md-direction="top">
+            {{ 'timewindow.edit' | translate }}
+        </md-tooltip>
+        <ng-md-icon aria-label="{{ 'timewindow.date-range' | translate }}" icon="query_builder"></ng-md-icon>
+    </md-button>
+    <span ng-click="openEditMode($event)">
+        <md-tooltip md-direction="top">
+            {{ 'timewindow.edit' | translate }}
+        </md-tooltip>
+        {{model.displayValue}}
+    </span>
+    <md-button ng-if="direction === 'right'" ng-disabled="disabled" class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
+        <md-tooltip md-direction="top">
+            {{ 'timewindow.edit' | translate }}
+        </md-tooltip>
+        <ng-md-icon aria-label="{{ 'timewindow.date-range' | translate }}" icon="query_builder"></ng-md-icon>
     </md-button>	
 </section>
\ No newline at end of file
diff --git a/ui/src/app/components/timewindow-button.tpl.html b/ui/src/app/components/timewindow-button.tpl.html
index 11d8382..cea776c 100644
--- a/ui/src/app/components/timewindow-button.tpl.html
+++ b/ui/src/app/components/timewindow-button.tpl.html
@@ -15,7 +15,7 @@
     limitations under the License.
 
 -->
-<md-button class="md-raised md-primary" ng-click="openEditMode($event)">
-    <md-icon class="material-icons">date_range</md-icon>
+<md-button ng-disabled="disabled" class="md-raised md-primary" ng-click="openEditMode($event)">
+    <ng-md-icon icon="query_builder"></ng-md-icon>
     <span>{{model.displayValue}}</span>
 </md-button>
\ No newline at end of file
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index a2056e0..c0617bc 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -20,7 +20,8 @@ import 'javascript-detect-element-resize/detect-element-resize';
 
 /*@ngInject*/
 export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils, timeService,
-                                         datasourceService, deviceService, visibleRect, isEdit, stDiff, widget, deviceAliasList, widgetType) {
+                                         datasourceService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
+                                         dashboardTimewindowApi, widget, deviceAliasList, widgetType) {
 
     var vm = this;
 
@@ -136,6 +137,24 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         $scope.widgetErrorData = utils.processWidgetException(e);
     }
 
+    function notifyDataLoaded(apply) {
+        if ($scope.loadingData === true) {
+            $scope.loadingData = false;
+            if (apply) {
+                $scope.$digest();
+            }
+        }
+    }
+
+    function notifyDataLoading(apply) {
+        if ($scope.loadingData === false) {
+            $scope.loadingData = true;
+            if (apply) {
+                $scope.$digest();
+            }
+        }
+    }
+
     function onInit() {
         if (!widgetContext.inited) {
             widgetContext.inited = true;
@@ -274,7 +293,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
     }
 
     function initialize() {
-        if (widget.type !== types.widgetType.rpc.value) {
+        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]);
                 for (var a in datasource.dataKeys) {
@@ -287,7 +306,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
                     widgetContext.data.push(datasourceData);
                 }
             }
-        } else {
+        } else if (widget.type === types.widgetType.rpc.value) {
             if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
                 targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
                 if (deviceAliasList[targetDeviceAliasId]) {
@@ -354,14 +373,26 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         });
 
         if (widget.type === types.widgetType.timeseries.value) {
-            $scope.$watch(function () {
-                return widget.config.timewindow;
-            }, function (newTimewindow, prevTimewindow) {
-                if (!angular.equals(newTimewindow, prevTimewindow)) {
-                    unsubscribe();
-                    subscribe();
-                }
-            });
+            widgetContext.useDashboardTimewindow = angular.isDefined(widget.config.useDashboardTimewindow)
+                    ? widget.config.useDashboardTimewindow : true;
+            if (widgetContext.useDashboardTimewindow) {
+                $scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) {
+                    if (!angular.equals(dashboardTimewindow, newDashboardTimewindow)) {
+                        dashboardTimewindow = newDashboardTimewindow;
+                        unsubscribe();
+                        subscribe();
+                    }
+                });
+            } else {
+                $scope.$watch(function () {
+                    return widgetContext.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow;
+                }, function (newTimewindow, prevTimewindow) {
+                    if (!angular.equals(newTimewindow, prevTimewindow)) {
+                        unsubscribe();
+                        subscribe();
+                    }
+                });
+            }
         }
         subscribe();
     }
@@ -474,20 +505,29 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
     }*/
 
     function onResetTimewindow() {
-        if (originalTimewindow) {
-            widget.config.timewindow = angular.copy(originalTimewindow);
-            originalTimewindow = null;
+        if (widgetContext.useDashboardTimewindow) {
+            dashboardTimewindowApi.onResetTimewindow();
+        } else {
+            if (originalTimewindow) {
+                widget.config.timewindow = angular.copy(originalTimewindow);
+                originalTimewindow = null;
+            }
         }
     }
 
     function onUpdateTimewindow(startTimeMs, endTimeMs) {
-        if (!originalTimewindow) {
-            originalTimewindow = angular.copy(widget.config.timewindow);
+        if (widgetContext.useDashboardTimewindow) {
+            dashboardTimewindowApi.onUpdateTimewindow(startTimeMs, endTimeMs);
+        } else {
+            if (!originalTimewindow) {
+                originalTimewindow = angular.copy(widget.config.timewindow);
+            }
+            widget.config.timewindow = timeService.toHistoryTimewindow(widget.config.timewindow, startTimeMs, endTimeMs);
         }
-        widget.config.timewindow = timeService.toHistoryTimewindow(widget.config.timewindow, startTimeMs, endTimeMs);
     }
 
-    function dataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
+    function dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
+        notifyDataLoaded(apply);
         var update = true;
         if (widget.type === types.widgetType.latest.value) {
             var prevData = widgetContext.data[datasourceIndex + dataKeyIndex].data;
@@ -547,16 +587,28 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         if (_subscriptionTimewindow) {
             subscriptionTimewindow = _subscriptionTimewindow;
         } else {
-            subscriptionTimewindow = timeService.createSubscriptionTimewindow(widget.config.timewindow, widgetContext.timeWindow.stDiff);
+            subscriptionTimewindow =
+                timeService.createSubscriptionTimewindow(
+                    widgetContext.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow,
+                    widgetContext.timeWindow.stDiff);
         }
         updateTimewindow();
         return subscriptionTimewindow;
     }
 
+    function hasTimewindow() {
+        if (widgetContext.useDashboardTimewindow) {
+            return angular.isDefined(dashboardTimewindow);
+        } else {
+            return angular.isDefined(widget.config.timewindow);
+        }
+    }
+
     function subscribe() {
-        if (widget.type !== types.widgetType.rpc.value) {
+        if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
+            notifyDataLoading();
             if (widget.type === types.widgetType.timeseries.value &&
-                angular.isDefined(widget.config.timewindow)) {
+                hasTimewindow()) {
                 updateRealtimeSubscription();
                 if (subscriptionTimewindow.fixedWindow) {
                     onDataUpdated();
@@ -579,8 +631,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
                     subscriptionTimewindow: subscriptionTimewindow,
                     datasource: datasource,
                     deviceId: deviceId,
-                    dataUpdated: function (data, datasourceIndex, dataKeyIndex) {
-                        dataUpdated(data, datasourceIndex, dataKeyIndex);
+                    dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
+                        dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
                     },
                     updateRealtimeSubscription: function() {
                         this.subscriptionTimewindow = updateRealtimeSubscription();
@@ -601,6 +653,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
                 datasourceListeners.push(listener);
                 datasourceService.subscribeToDatasource(listener);
             }
+        } else {
+            notifyDataLoaded();
         }
     }
 
diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget.directive.js
index cd1d16b..6d3a330 100644
--- a/ui/src/app/components/widget.directive.js
+++ b/ui/src/app/components/widget.directive.js
@@ -66,6 +66,8 @@ function Widget($controller, $compile, widgetService) {
 
             function loadFromWidgetInfo(widgetInfo) {
 
+                scope.loadingData = true;
+
                 elem.addClass("tb-widget");
 
                 var widgetNamespace = "widget-type-" + (widget.isSystemType ? 'sys-' : '')
@@ -73,9 +75,12 @@ function Widget($controller, $compile, widgetService) {
                     + widget.typeAlias;
 
                 elem.addClass(widgetNamespace);
-                elem.html('<div class="tb-absolute-fill tb-widget-error"" ng-if="widgetErrorData">' +
+                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>');
 
                 $compile(elem.contents())(scope);
diff --git a/ui/src/app/components/widget.scss b/ui/src/app/components/widget.scss
index bad71b9..ddeddcc 100644
--- a/ui/src/app/components/widget.scss
+++ b/ui/src/app/components/widget.scss
@@ -23,4 +23,8 @@
        color: red;
     }
   }
+  .tb-widget-loading {
+    background: rgba(255,255,255,0.15);
+    z-index: 3;
+  }
 }
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index c2c5743..639395a 100644
--- a/ui/src/app/components/widget-config.directive.js
+++ b/ui/src/app/components/widget-config.directive.js
@@ -98,6 +98,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
                     }, true);
                 scope.mobileOrder = ngModelCtrl.$viewValue.mobileOrder;
                 scope.mobileHeight = ngModelCtrl.$viewValue.mobileHeight;
+                scope.useDashboardTimewindow = angular.isDefined(ngModelCtrl.$viewValue.useDashboardTimewindow) ?
+                    ngModelCtrl.$viewValue.useDashboardTimewindow : true;
                 scope.timewindow = ngModelCtrl.$viewValue.timewindow;
                 if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value) {
                     if (scope.datasources) {
@@ -174,7 +176,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
             }
         };
 
-        scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + padding + titleStyle + mobileOrder + mobileHeight + intervalSec', function () {
+        scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' +
+            'padding + titleStyle + mobileOrder + mobileHeight + useDashboardTimewindow', function () {
             if (ngModelCtrl.$viewValue) {
                 var value = ngModelCtrl.$viewValue;
                 value.title = scope.title;
@@ -191,7 +194,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
                 }
                 value.mobileOrder = angular.isNumber(scope.mobileOrder) ? scope.mobileOrder : undefined;
                 value.mobileHeight = scope.mobileHeight;
-                value.intervalSec = scope.intervalSec;
+                value.useDashboardTimewindow = scope.useDashboardTimewindow;
                 ngModelCtrl.$setViewValue(value);
                 scope.updateValidity();
             }
diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html
index 23c64fc..88bfc2d 100644
--- a/ui/src/app/components/widget-config.tpl.html
+++ b/ui/src/app/components/widget-config.tpl.html
@@ -88,10 +88,15 @@
                         <input ng-model="mobileHeight" type="number">
                     </md-input-container>
                 </div>
-                <div ng-show="widgetType === types.widgetType.timeseries.value" layout="row"
-                     layout-align="center center">
-                    <span translate style="padding-right: 8px;">widget-config.timewindow</span>
-                    <tb-timewindow as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow>
+                <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">
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index c2a9cf3..167b5ae 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html';
 
 /*@ngInject*/
 export default function DashboardController(types, widgetService, userService,
-                                            dashboardService, itembuffer, importExport, hotkeys, $window, $rootScope,
+                                            dashboardService, timeService, itembuffer, importExport, hotkeys, $window, $rootScope,
                                             $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
 
     var user = userService.getCurrentUser();
@@ -47,6 +47,25 @@ export default function DashboardController(types, widgetService, userService,
     vm.widgets = [];
     vm.dashboardInitComplete = false;
 
+    vm.isToolbarOpened = false;
+
+    Object.defineProperty(vm, 'toolbarOpened', {
+        get: function() { return vm.isToolbarOpened || vm.isEdit; },
+        set: function() { }
+    });
+
+    vm.openToolbar = function() {
+        $timeout(function() {
+            vm.isToolbarOpened = true;
+        });
+    }
+
+    vm.closeToolbar = function() {
+        $timeout(function() {
+            vm.isToolbarOpened = false;
+        });
+    }
+
     vm.addWidget = addWidget;
     vm.addWidgetFromType = addWidgetFromType;
     vm.dashboardInited = dashboardInited;
@@ -154,6 +173,9 @@ export default function DashboardController(types, widgetService, userService,
 
         if (vm.widgetEditMode) {
             $timeout(function () {
+                vm.dashboardConfiguration = {
+                    timewindow: timeService.defaultTimewindow()
+                };
                 vm.widgets = [{
                     isSystemType: true,
                     bundleAlias: 'customWidgetBundle',
@@ -186,9 +208,12 @@ export default function DashboardController(types, widgetService, userService,
                     if (angular.isUndefined(vm.dashboard.configuration.deviceAliases)) {
                         vm.dashboard.configuration.deviceAliases = {};
                     }
-                    //$timeout(function () {
-                        vm.widgets = vm.dashboard.configuration.widgets;
-                    //});
+
+                    if (angular.isUndefined(vm.dashboard.configuration.timewindow)) {
+                        vm.dashboard.configuration.timewindow = timeService.defaultTimewindow();
+                    }
+                    vm.dashboardConfiguration = vm.dashboard.configuration;
+                    vm.widgets = vm.dashboard.configuration.widgets;
                     deferred.resolve();
                 }, function fail(e) {
                     deferred.reject(e);
@@ -607,6 +632,7 @@ export default function DashboardController(types, widgetService, userService,
                 if (revert) {
                     vm.dashboard = vm.prevDashboard;
                     vm.widgets = vm.dashboard.configuration.widgets;
+                    vm.dashboardConfiguration = vm.dashboard.configuration;
                 }
             }
         }
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index 64aa4ec..2d3b2fa 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+@import "~compass-sass-mixins/lib/compass";
 @import '../../scss/constants';
 
 section.tb-dashboard-title {
@@ -53,3 +55,81 @@ tb-details-sidenav.tb-widget-details-sidenav {
     }
   }
 }
+
+/***********************
+ * dashboard toolbar
+ ***********************/
+
+section.tb-dashboard-toolbar {
+  position: absolute;
+  top: 0px;
+  left: -100%;
+  z-index: 3;
+  pointer-events: none;
+  &.tb-dashboard-toolbar-opened {
+    right: 0px;
+    @include transition(right .3s cubic-bezier(.55,0,.55,.2));
+  }
+  &.tb-dashboard-toolbar-closed {
+    right: 18px;
+    @include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
+  }
+  md-fab-toolbar {
+    &.md-is-open {
+      md-fab-trigger {
+        .md-button {
+          &.md-fab {
+            opacity: 1;
+            @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
+          }
+        }
+      }
+    }
+    md-fab-trigger {
+      .md-button {
+        &.md-fab {
+          line-height: 36px;
+          width: 36px;
+          height: 36px;
+          margin: 4px 0 0 4px;
+          opacity: 0.5;
+          @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
+          md-icon {
+            margin: 0;
+            line-height: 18px;
+            height: 18px;
+            width: 18px;
+            min-height: 18px;
+            min-width: 18px;
+          }
+        }
+      }
+    }
+    .md-fab-toolbar-wrapper {
+      height: 40px;
+      md-toolbar {
+        min-height: 36px;
+        height: 36px;
+        md-fab-actions {
+          .close-action {
+            margin-right: -18px;
+          }
+          tb-timewindow {
+            font-size: 16px;
+          }
+        }
+      }
+    }
+  }
+}
+
+.tb-dashboard-container {
+   &.tb-dashboard-toolbar-opened {
+     margin-top: 40px;
+     @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+   }
+   &.tb-dashboard-toolbar-closed {
+     margin-top: 0px;
+     @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
+  }
+}
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 298d365..bf0b3ac 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -15,235 +15,263 @@
     limitations under the License.
 
 -->
-<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode" hide-expand-button="vm.widgetEditMode || vm.iframeMode">
-    <!--section ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode" layout="row" layout-wrap
-             class="tb-header-buttons tb-top-header-buttons md-fab" ng-style="{'right': '50px'}">
-        <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit" ng-disabled="loading"
-                   class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
-                   aria-label="{{ 'action.apply' | translate }}"
-                   ng-click="vm.saveDashboard()">
-            <md-tooltip md-direction="top">
-                {{ 'action.apply-changes' | translate }}
-            </md-tooltip>
-            <ng-md-icon icon="done"></ng-md-icon>
-        </md-button>
-        <md-button ng-if="vm.isTenantAdmin()" ng-disabled="loading"
-                   class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
-                   aria-label="{{ 'action.edit-mode' | translate }}"
-                   ng-click="vm.toggleDashboardEditMode()">
-            <md-tooltip md-direction="top">
-                {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
-            </md-tooltip>
-            <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
-                        options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
-        </md-button>
-    </section-->
-    <section ng-show="!loading && vm.noData()" layout-align="center center"
-             ng-class="{'tb-padded' : !vm.widgetEditMode}"
-             style="text-transform: uppercase; display: flex; z-index: 1;"
-             class="md-headline tb-absolute-fill">
-	 <span translate ng-if="!vm.isEdit">
-		 dashboard.no-widgets
-	 </span>
-        <md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget($event)">
-            <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
-            {{ 'dashboard.add-widget' | translate }}
-        </md-button>
-    </section>
-    <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center">
-        <h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3>
-        <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
-            <label translate>dashboard.title</label>
-            <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title">
-        </md-input-container>
-        <md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDeviceAliases($event)">
-            {{ 'device.aliases' | translate }}
-        </md-button>
-        <md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDashboardSettings($event)">
-            {{ 'dashboard.settings' | translate }}
-        </md-button>
-    </section>
-    <div class="tb-absolute-fill"
-         ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }">
-        <tb-dashboard
-                dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
-                                  'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
-                                  'background-repeat': 'no-repeat',
-                                  'background-attachment': 'scroll',
-                                  'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
-                                  'background-position': '0% 0%'}"
-                widgets="vm.widgets"
-                columns="vm.dashboard.configuration.gridSettings.columns"
-                margins="vm.dashboard.configuration.gridSettings.margins"
-                device-alias-list="vm.dashboard.configuration.deviceAliases"
-                is-edit="vm.isEdit"
-                is-mobile="vm.forceDashboardMobileMode"
-                is-mobile-disabled="vm.widgetEditMode"
-                is-edit-action-enabled="vm.isEdit"
-                is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
-                is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
-                on-edit-widget="vm.editWidget(event, widget)"
-                on-export-widget="vm.exportWidget(event, widget)"
-                on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
-                on-widget-clicked="vm.widgetClicked(event, widget)"
-                on-widget-context-menu="vm.widgetContextMenu(event, widget)"
-                prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
-                prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
-                on-remove-widget="vm.removeWidget(event, widget)"
-                load-widgets="vm.loadDashboard()"
-                get-st-diff="vm.getServerTimeDiff()"
-                on-init="vm.dashboardInited(dashboard)"
-                on-init-failed="vm.dashboardInitFailed(e)">
-        </tb-dashboard>
-    </div>
-    <tb-details-sidenav class="tb-widget-details-sidenav"
-                        header-title="vm.editingWidget.config.title"
-                        header-subtitle="{{vm.editingWidgetSubtitle}}"
-                        is-read-only="false"
-                        is-open="vm.isEditingWidget"
-                        is-always-edit="true"
-                        on-close-details="vm.onEditWidgetClosed()"
-                        on-toggle-details-edit-mode="vm.onRevertWidgetEdit(vm.widgetForm)"
-                        on-apply-details="vm.saveWidget(vm.widgetForm)"
-                        the-form="vm.widgetForm">
-        <details-buttons tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
-            <div id="help-container"></div>
-        </details-buttons>
-        <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
-            <tb-edit-widget
-                    dashboard="vm.dashboard"
-                    widget="vm.editingWidget"
-                    the-form="vm.widgetForm">
-            </tb-edit-widget>
-        </form>
-    </tb-details-sidenav>
-    <tb-details-sidenav ng-if="!vm.widgetEditMode" class="tb-select-widget-sidenav"
-                        header-title="'dashboard.select-widget-title' | translate"
-                        header-height-px="120"
-                        is-read-only="true"
-                        is-open="vm.isAddingWidget"
-                        is-edit="false"
-                        on-close-details="vm.onAddWidgetClosed()">
-        <header-pane ng-if="vm.isAddingWidget">
-            <div layout="row">
-                <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
-                <tb-widgets-bundle-select flex-offset="5"
-                                          flex
-                        ng-model="vm.widgetsBundle"
-                        tb-required="true"
-                        select-first-bundle="false">
-                </tb-widgets-bundle-select>
-            </div>
-        </header-pane>
-        <div ng-if="vm.isAddingWidget">
-            <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
-                            vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
-                     flex
-                     class="tb-absolute-fill" md-border-bottom>
-                <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
-                    <tb-dashboard
-                            widgets="vm.timeseriesWidgetTypes"
-                            is-edit="false"
-                            is-mobile="true"
-                            is-edit-action-enabled="false"
-                            is-remove-action-enabled="false"
-                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
-                    </tb-dashboard>
-                </md-tab>
-                <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
-                    <tb-dashboard
-                            widgets="vm.latestWidgetTypes"
-                            is-edit="false"
-                            is-mobile="true"
-                            is-edit-action-enabled="false"
-                            is-remove-action-enabled="false"
-                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
-                    </tb-dashboard>
-                </md-tab>
-                <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
-                    <tb-dashboard
-                            widgets="vm.rpcWidgetTypes"
-                            is-edit="false"
-                            is-mobile="true"
-                            is-edit-action-enabled="false"
-                            is-remove-action-enabled="false"
-                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
-                    </tb-dashboard>
-                </md-tab>
-                <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
-                    <tb-dashboard
-                            widgets="vm.staticWidgetTypes"
-                            is-edit="false"
-                            is-mobile="true"
-                            is-edit-action-enabled="false"
-                            is-remove-action-enabled="false"
-                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
-                    </tb-dashboard>
-                </md-tab>
-            </md-tabs>
-            <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
-                                   vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
-                  layout-align="center center"
-                  style="text-transform: uppercase; display: flex;"
-                  class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
-            <span translate ng-if="!vm.widgetsBundle"
-                  layout-align="center center"
-                  style="text-transform: uppercase; display: flex;"
-                  class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
-        </div>
-    </tb-details-sidenav>
-    <!-- </section> -->
-    <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
-        <md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
-                           md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
-            <md-fab-trigger>
-                <md-button ng-disabled="loading"
-                           class="tb-btn-footer md-accent md-hue-2 md-fab"
-                           aria-label="{{ 'dashboard.add-widget' | translate }}">
-                    <md-tooltip md-direction="top">
-                        {{ 'dashboard.add-widget' | translate }}
+<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode" expand-button-id="dashboard-expand-button"
+            hide-expand-button="vm.widgetEditMode || vm.iframeMode"
+        ng-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
+                    'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
+                    'background-repeat': 'no-repeat',
+                    'background-attachment': 'scroll',
+                    'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
+                    'background-position': '0% 0%'}">
+    <section class="tb-dashboard-toolbar"
+             ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
+        <md-fab-toolbar md-open="vm.toolbarOpened"
+                        md-direction="left">
+            <md-fab-trigger class="align-with-text">
+                <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.openToolbar()">
+                    <md-tooltip ng-show="!vm.toolbarOpened" md-direction="top">
+                        {{ 'dashboard.open-toolbar' | translate }}
                     </md-tooltip>
-                    <ng-md-icon icon="add"></ng-md-icon>
+                    <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
                 </md-button>
             </md-fab-trigger>
-            <md-fab-actions>
-                <md-button ng-disabled="loading"
-                           class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
-                           aria-label="{{ 'action.create' | translate }}">
-                    <md-tooltip md-direction="top">
-                        {{ 'dashboard.create-new-widget' | translate }}
-                    </md-tooltip>
-                    <ng-md-icon icon="insert_drive_file"></ng-md-icon>
-                </md-button>
-                <md-button ng-disabled="loading"
-                           class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidget($event)"
-                           aria-label="{{ 'action.import' | translate }}">
-                    <md-tooltip md-direction="top">
-                        {{ 'dashboard.import-widget' | translate }}
-                    </md-tooltip>
-                    <ng-md-icon icon="file_upload"></ng-md-icon>
-                </md-button>
-            </md-fab-actions>
-        </md-fab-speed-dial>
-        <md-button ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
-                   class="tb-btn-footer md-accent md-hue-2 md-fab"
-                   aria-label="{{ 'action.apply' | translate }}"
-                   ng-click="vm.saveDashboard()">
-            <md-tooltip md-direction="top">
-                {{ 'action.apply-changes' | translate }}
-            </md-tooltip>
-            <ng-md-icon icon="done"></ng-md-icon>
-        </md-button>
-        <md-button ng-show="!vm.isAddingWidget && !loading"
-                   ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-disabled="loading"
-                   class="tb-btn-footer md-accent md-hue-2 md-fab"
-                   aria-label="{{ 'action.edit-mode' | translate }}"
-                   ng-click="vm.toggleDashboardEditMode()">
-            <md-tooltip md-direction="top">
-                {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
-            </md-tooltip>
-            <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
-                        options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
-        </md-button>
+            <md-toolbar>
+                <md-fab-actions class="md-toolbar-tools">
+                    <md-button ng-show="!vm.isEdit" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
+                        <md-tooltip md-direction="top">
+                            {{ 'dashboard.close-toolbar' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
+                    </md-button>
+                    <md-button id="dashboard-expand-button"
+                               aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                               class="md-icon-button">
+                    </md-button>
+                    <tb-timewindow direction="left" aggregation ng-model="vm.dashboardConfiguration.timewindow">
+                    </tb-timewindow>
+                    <md-button ng-show="vm.isEdit" aria-label="{{ 'device.aliases' | translate }}" class="md-icon-button"
+                               ng-click="vm.openDeviceAliases($event)">
+                        <md-tooltip md-direction="top">
+                            {{ 'device.aliases' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="{{ 'device.aliases' | translate }}" class="material-icons">devices_other</md-icon>
+                    </md-button>
+                    <md-button ng-show="vm.isEdit" aria-label="{{ 'dashboard.settings' | translate }}" class="md-icon-button"
+                               ng-click="vm.openDashboardSettings($event)">
+                        <md-tooltip md-direction="top">
+                            {{ 'dashboard.settings' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
+                    </md-button>
+                </md-fab-actions>
+            </md-toolbar>
+        </md-fab-toolbar>
+    </section>
+    <section class="tb-dashboard-container tb-absolute-fill"
+             ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
+        <section ng-show="!loading && vm.noData()" layout-align="center center"
+                 ng-class="{'tb-padded' : !vm.widgetEditMode}"
+                 style="text-transform: uppercase; display: flex; z-index: 1;"
+                 class="md-headline tb-absolute-fill">
+         <span translate ng-if="!vm.isEdit">
+             dashboard.no-widgets
+         </span>
+            <md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget($event)">
+                <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
+                {{ 'dashboard.add-widget' | translate }}
+            </md-button>
+        </section>
+        <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center"
+                 ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">
+            <h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3>
+            <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
+                <label translate ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">dashboard.title</label>
+                <input class="tb-dashboard-title" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}" required name="title" ng-model="vm.dashboard.title">
+            </md-input-container>
+        </section>
+        <div class="tb-absolute-fill"
+             ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }">
+            <tb-dashboard
+                    dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
+                        'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
+                        'background-repeat': 'no-repeat',
+                        'background-attachment': 'scroll',
+                        'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
+                        'background-position': '0% 0%'}"
+                    widgets="vm.widgets"
+                    columns="vm.dashboard.configuration.gridSettings.columns"
+                    margins="vm.dashboard.configuration.gridSettings.margins"
+                    device-alias-list="vm.dashboard.configuration.deviceAliases"
+                    dashboard-timewindow="vm.dashboardConfiguration.timewindow"
+                    is-edit="vm.isEdit"
+                    is-mobile="vm.forceDashboardMobileMode"
+                    is-mobile-disabled="vm.widgetEditMode"
+                    is-edit-action-enabled="vm.isEdit"
+                    is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
+                    is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
+                    on-edit-widget="vm.editWidget(event, widget)"
+                    on-export-widget="vm.exportWidget(event, widget)"
+                    on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
+                    on-widget-clicked="vm.widgetClicked(event, widget)"
+                    on-widget-context-menu="vm.widgetContextMenu(event, widget)"
+                    prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
+                    prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
+                    on-remove-widget="vm.removeWidget(event, widget)"
+                    load-widgets="vm.loadDashboard()"
+                    get-st-diff="vm.getServerTimeDiff()"
+                    on-init="vm.dashboardInited(dashboard)"
+                    on-init-failed="vm.dashboardInitFailed(e)">
+            </tb-dashboard>
+        </div>
+        <tb-details-sidenav class="tb-widget-details-sidenav"
+                            header-title="vm.editingWidget.config.title"
+                            header-subtitle="{{vm.editingWidgetSubtitle}}"
+                            is-read-only="false"
+                            is-open="vm.isEditingWidget"
+                            is-always-edit="true"
+                            on-close-details="vm.onEditWidgetClosed()"
+                            on-toggle-details-edit-mode="vm.onRevertWidgetEdit(vm.widgetForm)"
+                            on-apply-details="vm.saveWidget(vm.widgetForm)"
+                            the-form="vm.widgetForm">
+            <details-buttons tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
+                <div id="help-container"></div>
+            </details-buttons>
+            <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
+                <tb-edit-widget
+                        dashboard="vm.dashboard"
+                        widget="vm.editingWidget"
+                        the-form="vm.widgetForm">
+                </tb-edit-widget>
+            </form>
+        </tb-details-sidenav>
+        <tb-details-sidenav ng-if="!vm.widgetEditMode" class="tb-select-widget-sidenav"
+                            header-title="'dashboard.select-widget-title' | translate"
+                            header-height-px="120"
+                            is-read-only="true"
+                            is-open="vm.isAddingWidget"
+                            is-edit="false"
+                            on-close-details="vm.onAddWidgetClosed()">
+            <header-pane ng-if="vm.isAddingWidget">
+                <div layout="row">
+                    <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
+                    <tb-widgets-bundle-select flex-offset="5"
+                                              flex
+                            ng-model="vm.widgetsBundle"
+                            tb-required="true"
+                            select-first-bundle="false">
+                    </tb-widgets-bundle-select>
+                </div>
+            </header-pane>
+            <div ng-if="vm.isAddingWidget">
+                <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
+                                vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
+                         flex
+                         class="tb-absolute-fill" md-border-bottom>
+                    <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
+                        <tb-dashboard
+                                widgets="vm.timeseriesWidgetTypes"
+                                is-edit="false"
+                                is-mobile="true"
+                                is-edit-action-enabled="false"
+                                is-remove-action-enabled="false"
+                                on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                        </tb-dashboard>
+                    </md-tab>
+                    <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
+                        <tb-dashboard
+                                widgets="vm.latestWidgetTypes"
+                                is-edit="false"
+                                is-mobile="true"
+                                is-edit-action-enabled="false"
+                                is-remove-action-enabled="false"
+                                on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                        </tb-dashboard>
+                    </md-tab>
+                    <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
+                        <tb-dashboard
+                                widgets="vm.rpcWidgetTypes"
+                                is-edit="false"
+                                is-mobile="true"
+                                is-edit-action-enabled="false"
+                                is-remove-action-enabled="false"
+                                on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                        </tb-dashboard>
+                    </md-tab>
+                    <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
+                        <tb-dashboard
+                                widgets="vm.staticWidgetTypes"
+                                is-edit="false"
+                                is-mobile="true"
+                                is-edit-action-enabled="false"
+                                is-remove-action-enabled="false"
+                                on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                        </tb-dashboard>
+                    </md-tab>
+                </md-tabs>
+                <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
+                                       vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
+                      layout-align="center center"
+                      style="text-transform: uppercase; display: flex;"
+                      class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
+                <span translate ng-if="!vm.widgetsBundle"
+                      layout-align="center center"
+                      style="text-transform: uppercase; display: flex;"
+                      class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
+            </div>
+        </tb-details-sidenav>
+        <!-- </section> -->
+        <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
+            <md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
+                               md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
+                <md-fab-trigger>
+                    <md-button ng-disabled="loading"
+                               class="tb-btn-footer md-accent md-hue-2 md-fab"
+                               aria-label="{{ 'dashboard.add-widget' | translate }}">
+                        <md-tooltip md-direction="top">
+                            {{ 'dashboard.add-widget' | translate }}
+                        </md-tooltip>
+                        <ng-md-icon icon="add"></ng-md-icon>
+                    </md-button>
+                </md-fab-trigger>
+                <md-fab-actions>
+                    <md-button ng-disabled="loading"
+                               class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
+                               aria-label="{{ 'action.create' | translate }}">
+                        <md-tooltip md-direction="top">
+                            {{ 'dashboard.create-new-widget' | translate }}
+                        </md-tooltip>
+                        <ng-md-icon icon="insert_drive_file"></ng-md-icon>
+                    </md-button>
+                    <md-button ng-disabled="loading"
+                               class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidget($event)"
+                               aria-label="{{ 'action.import' | translate }}">
+                        <md-tooltip md-direction="top">
+                            {{ 'dashboard.import-widget' | translate }}
+                        </md-tooltip>
+                        <ng-md-icon icon="file_upload"></ng-md-icon>
+                    </md-button>
+                </md-fab-actions>
+            </md-fab-speed-dial>
+            <md-button ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
+                       class="tb-btn-footer md-accent md-hue-2 md-fab"
+                       aria-label="{{ 'action.apply' | translate }}"
+                       ng-click="vm.saveDashboard()">
+                <md-tooltip md-direction="top">
+                    {{ 'action.apply-changes' | translate }}
+                </md-tooltip>
+                <ng-md-icon icon="done"></ng-md-icon>
+            </md-button>
+            <md-button ng-show="!vm.isAddingWidget && !loading"
+                       ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-disabled="loading"
+                       class="tb-btn-footer md-accent md-hue-2 md-fab"
+                       aria-label="{{ 'action.edit-mode' | translate }}"
+                       ng-click="vm.toggleDashboardEditMode()">
+                <md-tooltip md-direction="top">
+                    {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
+                </md-tooltip>
+                <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
+                            options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+            </md-button>
+        </section>
     </section>
 </md-content>
diff --git a/ui/src/app/dashboard/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
index 91885d3..aac6da3 100644
--- a/ui/src/app/dashboard/dashboard-settings.controller.js
+++ b/ui/src/app/dashboard/dashboard-settings.controller.js
@@ -32,6 +32,7 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
     }
 
     vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
+    vm.gridSettings.titleColor = vm.gridSettings.titleColor || 'rgba(0,0,0,0.870588)';
     vm.gridSettings.columns = vm.gridSettings.columns || 24;
     vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
     vm.hMargin = vm.gridSettings.margins[0];
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
index 9a1f5d4..38cb530 100644
--- a/ui/src/app/dashboard/dashboard-settings.tpl.html
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -31,10 +31,22 @@
         <md-dialog-content>
             <div class="md-dialog-content">
                 <fieldset ng-disabled="loading">
-                    <div layout="row" layout-padding>
+                    <div layout="row" layout-align="start center">
                         <md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
                                      ng-model="vm.gridSettings.showTitle">{{ 'dashboard.display-title' | translate }}
                         </md-checkbox>
+                        <div flex
+                             ng-required="false"
+                             md-color-picker
+                             ng-model="vm.gridSettings.titleColor"
+                             label="{{ 'dashboard.title-color' | translate }}"
+                             icon="format_color_fill"
+                             default="rgba(0, 0, 0, 0.870588)"
+                             md-color-clear-button="false"
+                             open-on-input="true"
+                             md-color-generic-palette="false"
+                             md-color-history="false"
+                        ></div>
                     </div>
                     <md-input-container class="md-block">
                         <label translate>dashboard.columns-count</label>
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 05411d7..635ddec 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -247,6 +247,7 @@ export default angular.module('thingsboard.locale', [])
                     "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
                     "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
                     "display-title": "Display dashboard title",
+                    "title-color": "Title color",
                     "import": "Import dashboard",
                     "export": "Export dashboard",
                     "export-failed-error": "Unable to export dashboard: {error}",
@@ -258,7 +259,9 @@ export default angular.module('thingsboard.locale', [])
                     "import-widget": "Import widget",
                     "widget-file": "Widget file",
                     "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.",
-                    "widget-import-missing-aliases-title": "Select missing devices used by widget"
+                    "widget-import-missing-aliases-title": "Select missing devices used by widget",
+                    "open-toolbar": "Open dashboard toolbar",
+                    "close-toolbar": "Close toolbar"
                 },
                 "datakey": {
                     "settings": "Settings",
@@ -702,6 +705,7 @@ export default angular.module('thingsboard.locale', [])
                     "order": "Order",
                     "height": "Height",
                     "timewindow": "Timewindow",
+                    "use-dashboard-timewindow": "Use dashboard timewindow",
                     "datasources": "Datasources",
                     "datasource-type": "Type",
                     "datasource-parameters": "Parameters",
diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js
index 2888b0f..9c34405 100644
--- a/ui/src/app/widget/lib/flot-widget.js
+++ b/ui/src/app/widget/lib/flot-widget.js
@@ -360,9 +360,12 @@ export default class TbFlot {
     update() {
         if (!this.isMouseInteraction) {
             if (this.chartType === 'line' || this.chartType === 'bar') {
+                this.options.xaxis.min = this.ctx.timeWindow.minTime;
+                this.options.xaxis.max = this.ctx.timeWindow.maxTime;
                 this.ctx.plot.getOptions().xaxes[0].min = this.ctx.timeWindow.minTime;
                 this.ctx.plot.getOptions().xaxes[0].max = this.ctx.timeWindow.maxTime;
                 if (this.chartType === 'bar') {
+                    this.options.series.bars.barWidth = this.ctx.timeWindow.interval * 0.6;
                     this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.timeWindow.interval * 0.6;
                 }
                 this.ctx.plot.setData(this.ctx.data);
@@ -879,6 +882,7 @@ export default class TbFlot {
     destroy() {
         if (this.ctx.plot) {
             this.ctx.plot.destroy();
+            this.ctx.plot = null;
         }
     }
 
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 31c256d..8ffac23 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -193,6 +193,12 @@ md-sidenav {
   pointer-events: all;
 }
 
+.md-color-picker-input-container {
+  md-input-container {
+    margin-bottom: 0px;
+  }
+}
+
 /***********************
  * THINGSBOARD SPECIFIC
  ***********************/
@@ -201,6 +207,10 @@ md-sidenav {
   color: rgba(0,0,0,0.54);
 }
 
+.tb-disabled-label {
+  color: rgba(0,0,0,0.44);
+}
+
 label {
   &.tb-small {
     pointer-events: none;