thingsboard-aplcache

Details

diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index 43c537c..a70308b 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -36,7 +36,8 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
         saveRelatedEntity: saveRelatedEntity,
         getRelatedEntity: getRelatedEntity,
         deleteRelatedEntity: deleteRelatedEntity,
-        moveEntity: moveEntity
+        moveEntity: moveEntity,
+        copyEntity: copyEntity
     };
 
     return service;
@@ -626,6 +627,32 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
         return deferred.promise;
     }
 
+    function copyEntity(entity, targetParentId, keys) {
+        var deferred = $q.defer();
+        if (!entity.id && !entity.id.id) {
+            deferred.reject();
+        } else {
+            getRelatedEntity(entity.id, keys).then(
+                function success(relatedEntity) {
+                    delete relatedEntity.id.id;
+                    relatedEntity.name = entity.name;
+                    saveRelatedEntity(relatedEntity, targetParentId, keys).then(
+                        function success(savedEntity) {
+                            deferred.resolve(savedEntity);
+                        },
+                        function fail() {
+                            deferred.reject();
+                        }
+                    );
+                },
+                function fail() {
+                    deferred.reject();
+                }
+            );
+        }
+        return deferred.promise;
+    }
+
     function saveEntityPromise(entity) {
         var entityType = entity.id.entityType;
         if (!entity.id.id) {
diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js
index 57317b5..78136df 100644
--- a/ui/src/app/common/dashboard-utils.service.js
+++ b/ui/src/app/common/dashboard-utils.service.js
@@ -179,6 +179,7 @@ function DashboardUtils(types, utils, timeService) {
             dashboard.configuration.settings.showEntitiesSelect = true;
             dashboard.configuration.settings.showDashboardTimewindow = true;
             dashboard.configuration.settings.showDashboardExport = true;
+            dashboard.configuration.settings.toolbarAlwaysOpen = false;
         } else {
             if (angular.isUndefined(dashboard.configuration.settings.stateControllerId)) {
                 dashboard.configuration.settings.stateControllerId = 'default';
diff --git a/ui/src/app/components/expand-fullscreen.directive.js b/ui/src/app/components/expand-fullscreen.directive.js
index 37491e7..b70fd94 100644
--- a/ui/src/app/components/expand-fullscreen.directive.js
+++ b/ui/src/app/components/expand-fullscreen.directive.js
@@ -24,7 +24,7 @@ export default angular.module('thingsboard.directives.expandFullscreen', [])
 /* eslint-disable angular/angularelement */
 
 /*@ngInject*/
-function ExpandFullscreen($compile, $document) {
+function ExpandFullscreen($compile, $document, $timeout) {
 
     var uniqueId = 1;
     var linker = function (scope, element, attrs) {
@@ -97,10 +97,6 @@ function ExpandFullscreen($compile, $document) {
             scope.expanded = !scope.expanded;
         }
 
-        var expandButton = null;
-        if (attrs.expandButtonId) {
-            expandButton = $('#' + attrs.expandButtonId, element)[0];
-        }
         var buttonSize;
         if (attrs.expandButtonSize) {
             buttonSize = attrs.expandButtonSize;
@@ -115,27 +111,38 @@ function ExpandFullscreen($compile, $document) {
             'options=\'{"easing": "circ-in-out", "duration": 375, "rotation": "none"}\'>' +
             '</ng-md-icon>';
 
-        if (expandButton) {
-            expandButton = angular.element(expandButton);
-            if (scope.hideExpandButton()) {
-                expandButton.remove();
-            } else {
-                expandButton.attr('md-ink-ripple', 'false');
-                expandButton.append(html);
+        if (attrs.expandButtonId) {
+            $timeout(function() {
+               var expandButton = $('#' + attrs.expandButtonId, element)[0];
+                renderExpandButton(expandButton);
+            });
+        } else {
+            renderExpandButton();
+        }
+
+        function renderExpandButton(expandButton) {
+            if (expandButton) {
+                expandButton = angular.element(expandButton);
+                if (scope.hideExpandButton()) {
+                    expandButton.remove();
+                } else {
+                    expandButton.attr('md-ink-ripple', 'false');
+                    expandButton.append(html);
 
-                $compile(expandButton.contents())(scope);
+                    $compile(expandButton.contents())(scope);
 
-                expandButton.on("click", scope.toggleExpand);
-            }
-        } else if (!scope.hideExpandButton()) {
-            var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
-                'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
-                html +
-                '</md-button>');
+                    expandButton.on("click", scope.toggleExpand);
+                }
+            } else if (!scope.hideExpandButton()) {
+                var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
+                    'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
+                    html +
+                    '</md-button>');
 
-            $compile(button)(scope);
+                $compile(button)(scope);
 
-            element.prepend(button);
+                element.prepend(button);
+            }
         }
     }
 
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index c446be4..ecf072d 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -63,7 +63,9 @@ export default function DashboardController(types, dashboardUtils, widgetService
     }
 
     Object.defineProperty(vm, 'toolbarOpened', {
-        get: function() { return !vm.widgetEditMode && ($scope.forceFullscreen || vm.isToolbarOpened || vm.isEdit || vm.showRightLayoutSwitch()); },
+        get: function() {
+            return !vm.widgetEditMode &&
+                (toolbarAlwaysOpen() || $scope.forceFullscreen || vm.isToolbarOpened || vm.isEdit || vm.showRightLayoutSwitch()); },
         set: function() { }
     });
 
@@ -103,9 +105,11 @@ export default function DashboardController(types, dashboardUtils, widgetService
     }
 
     vm.showCloseToolbar = function() {
-        return !$scope.forceFullscreen && !vm.isEdit && !vm.showRightLayoutSwitch();
+        return !vm.toolbarAlwaysOpen() && !$scope.forceFullscreen && !vm.isEdit && !vm.showRightLayoutSwitch();
     }
 
+    vm.toolbarAlwaysOpen = toolbarAlwaysOpen;
+
     vm.showRightLayoutSwitch = function() {
         return vm.isMobile && vm.layouts.right.show;
     }
@@ -738,6 +742,15 @@ export default function DashboardController(types, dashboardUtils, widgetService
         return link;
     }
 
+    function toolbarAlwaysOpen() {
+        if (vm.dashboard && vm.dashboard.configuration.settings &&
+            angular.isDefined(vm.dashboard.configuration.settings.toolbarAlwaysOpen)) {
+            return vm.dashboard.configuration.settings.toolbarAlwaysOpen;
+        } else {
+            return false;
+        }
+    }
+
     function displayTitle() {
         if (vm.dashboard && vm.dashboard.configuration.settings &&
             angular.isDefined(vm.dashboard.configuration.settings.showTitle)) {
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index bc5ec56..ca2bbab 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -68,87 +68,23 @@ section.tb-dashboard-toolbar {
   pointer-events: none;
   &.tb-dashboard-toolbar-opened {
     right: 0px;
-    @include transition(right .3s cubic-bezier(.55,0,.55,.2));
+   // @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 {
-            position: absolute;
-            top: 25%;
-            margin: 0;
-            line-height: 18px;
-            height: 18px;
-            width: 18px;
-            min-height: 18px;
-            min-width: 18px;
-          }
-        }
-      }
-    }
-    .md-fab-toolbar-wrapper {
-      height: 50px;
-      md-toolbar {
-        min-height: 46px;
-        height: 46px;
-        md-fab-actions {
-          font-size: 16px;
-          margin-top: 0px;
-          .close-action {
-            margin-right: -18px;
-          }
-          .md-fab-action-item {
-            width: 100%;
-            height: 46px;
-            .tb-dashboard-action-panels {
-              height: 46px;
-              flex-direction: row-reverse;
-              .tb-dashboard-action-panel {
-                height: 46px;
-                flex-direction: row-reverse;
-                div {
-                  height: 46px;
-                }
-                md-select {
-                  pointer-events: all;
-                }
-                tb-states-component {
-                  pointer-events: all;
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-  }
 }
 
 .tb-dashboard-container {
    &.tb-dashboard-toolbar-opened {
-     margin-top: 50px;
-     @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+     &.is-fullscreen {
+       margin-top: 64px;
+     }
+     &:not(.is-fullscreen) {
+       margin-top: 50px;
+       @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+     }
    }
    &.tb-dashboard-toolbar-closed {
      margin-top: 0px;
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 94e016e..8338612 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -19,110 +19,98 @@
             hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom">
     <section class="tb-dashboard-toolbar" ng-show="vm.showDashboardToolbar()"
              ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
-        <md-fab-toolbar ng-show="!vm.widgetEditMode" 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="bottom">
-                        {{ 'dashboard.open-toolbar' | translate }}
-                    </md-tooltip>
-                    <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
-                </md-button>
-            </md-fab-trigger>
-            <md-toolbar>
-                <md-fab-actions class="md-toolbar-tools">
-                    <div class="tb-dashboard-action-panels" flex layout="row" layout-align="start center">
-                        <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="start center">
-                            <md-button ng-show="vm.showCloseToolbar()" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
-                                <md-tooltip md-direction="bottom">
-                                    {{ 'dashboard.close-toolbar' | translate }}
-                                </md-tooltip>
-                                <md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
-                            </md-button>
-                            <md-button ng-show="vm.showRightLayoutSwitch()" aria-label="switch-layouts" class="md-icon-button" ng-click="vm.toggleLayouts()">
-                                <ng-md-icon icon="{{vm.isRightLayoutOpened ? 'arrow_back' : 'menu'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
-                                <md-tooltip md-direction="bottom">
-                                    {{ (vm.isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}
-                                </md-tooltip>
-                            </md-button>
-                            <md-button id="dashboard-expand-button"
-                                       aria-label="{{ 'fullscreen.fullscreen' | translate }}"
-                                       class="md-icon-button">
-                            </md-button>
-                            <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true">
-                            </tb-user-menu>
-                            <md-button ng-show="vm.isEdit || vm.displayExport()"
-                                       aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
-                                       ng-click="vm.exportDashboard($event)">
-                                <md-tooltip md-direction="bottom">
-                                    {{ 'dashboard.export' | translate }}
-                                </md-tooltip>
-                                <md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
-                            </md-button>
-                            <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
-                                           is-toolbar
-                                           direction="left"
-                                           tooltip-direction="bottom" aggregation
-                                           ng-model="vm.dashboardCtx.dashboardTimewindow">
-                            </tb-timewindow>
-                            <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
-                                                      tooltip-direction="bottom"
-                                                      ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
-                                                      entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
-                            </tb-aliases-entity-select>
-                            <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
-                                       ng-click="vm.openEntityAliases($event)">
-                                <md-tooltip md-direction="bottom">
-                                    {{ 'entity.aliases' | translate }}
-                                </md-tooltip>
-                                <md-icon aria-label="{{ 'entity.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="bottom">
-                                    {{ 'dashboard.settings' | translate }}
-                                </md-tooltip>
-                                <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
-                            </md-button>
-                            <tb-dashboard-select   ng-show="!vm.isEdit && !vm.widgetEditMode && vm.displayDashboardsSelect()"
-                                                   ng-model="vm.currentDashboardId"
-                                                   dashboards-scope="{{vm.currentDashboardScope}}"
-                                                   customer-id="vm.currentCustomerId">
-                            </tb-dashboard-select>
-                        </div>
-                        <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="end center">
-                            <div layout="row" layout-align="start center" ng-show="vm.isEdit">
-                                <md-button aria-label="{{ 'dashboard.manage-states' | translate }}" class="md-icon-button"
-                                           ng-click="vm.manageDashboardStates($event)">
-                                    <md-tooltip md-direction="bottom">
-                                        {{ 'dashboard.manage-states' | translate }}
-                                    </md-tooltip>
-                                    <md-icon aria-label="{{ 'dashboard.manage-states' | translate }}" class="material-icons">layers</md-icon>
-                                </md-button>
-                                <md-button aria-label="{{ 'layout.manage' | translate }}" class="md-icon-button"
-                                           ng-click="vm.manageDashboardLayouts($event)">
-                                    <md-tooltip md-direction="bottom">
-                                        {{ 'layout.manage' | translate }}
-                                    </md-tooltip>
-                                    <md-icon aria-label="{{ 'layout.manage' | translate }}" class="material-icons">view_compact</md-icon>
-                                </md-button>
-                            </div>
-                            <div layout="row" layout-align="start center">
-                                <tb-states-component ng-if="vm.isEdit" states-controller-id="'default'"
-                                                     dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
-                                </tb-states-component>
-                                <tb-states-component ng-if="!vm.isEdit" states-controller-id="vm.dashboardConfiguration.settings.stateControllerId"
-                                                     dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
-                                </tb-states-component>
-                            </div>
-                         </div>
+        <tb-dashboard-toolbar ng-show="!vm.widgetEditMode" force-fullscreen="forceFullscreen"
+                              toolbar-opened="vm.toolbarOpened" on-trigger-click="vm.openToolbar()">
+            <div class="tb-dashboard-action-panels" flex layout="row" layout-align="start center">
+                <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="start center">
+                    <md-button ng-show="vm.showCloseToolbar()" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
+                        <md-tooltip md-direction="bottom">
+                            {{ 'dashboard.close-toolbar' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
+                    </md-button>
+                    <md-button ng-show="vm.showRightLayoutSwitch()" aria-label="switch-layouts" class="md-icon-button" ng-click="vm.toggleLayouts()">
+                        <ng-md-icon icon="{{vm.isRightLayoutOpened ? 'arrow_back' : 'menu'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+                        <md-tooltip md-direction="bottom">
+                            {{ (vm.isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}
+                        </md-tooltip>
+                    </md-button>
+                    <md-button id="dashboard-expand-button"
+                               aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                               class="md-icon-button">
+                    </md-button>
+                    <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true">
+                    </tb-user-menu>
+                    <md-button ng-show="vm.isEdit || vm.displayExport()"
+                               aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
+                               ng-click="vm.exportDashboard($event)">
+                        <md-tooltip md-direction="bottom">
+                            {{ 'dashboard.export' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
+                    </md-button>
+                    <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
+                                   is-toolbar
+                                   direction="left"
+                                   tooltip-direction="bottom" aggregation
+                                   ng-model="vm.dashboardCtx.dashboardTimewindow">
+                    </tb-timewindow>
+                    <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
+                                              tooltip-direction="bottom"
+                                              ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
+                                              entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
+                    </tb-aliases-entity-select>
+                    <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
+                               ng-click="vm.openEntityAliases($event)">
+                        <md-tooltip md-direction="bottom">
+                            {{ 'entity.aliases' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="{{ 'entity.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="bottom">
+                            {{ 'dashboard.settings' | translate }}
+                        </md-tooltip>
+                        <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
+                    </md-button>
+                    <tb-dashboard-select   ng-show="!vm.isEdit && !vm.widgetEditMode && vm.displayDashboardsSelect()"
+                                           ng-model="vm.currentDashboardId"
+                                           dashboards-scope="{{vm.currentDashboardScope}}"
+                                           customer-id="vm.currentCustomerId">
+                    </tb-dashboard-select>
+                </div>
+                <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="end center">
+                    <div layout="row" layout-align="start center" ng-show="vm.isEdit">
+                        <md-button aria-label="{{ 'dashboard.manage-states' | translate }}" class="md-icon-button"
+                                   ng-click="vm.manageDashboardStates($event)">
+                            <md-tooltip md-direction="bottom">
+                                {{ 'dashboard.manage-states' | translate }}
+                            </md-tooltip>
+                            <md-icon aria-label="{{ 'dashboard.manage-states' | translate }}" class="material-icons">layers</md-icon>
+                        </md-button>
+                        <md-button aria-label="{{ 'layout.manage' | translate }}" class="md-icon-button"
+                                   ng-click="vm.manageDashboardLayouts($event)">
+                            <md-tooltip md-direction="bottom">
+                                {{ 'layout.manage' | translate }}
+                            </md-tooltip>
+                            <md-icon aria-label="{{ 'layout.manage' | translate }}" class="material-icons">view_compact</md-icon>
+                        </md-button>
                     </div>
-                </md-fab-actions>
-            </md-toolbar>
-        </md-fab-toolbar>
+                    <div layout="row" layout-align="start center">
+                        <tb-states-component ng-if="vm.isEdit" states-controller-id="'default'"
+                                             dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
+                        </tb-states-component>
+                        <tb-states-component ng-if="!vm.isEdit" states-controller-id="vm.dashboardConfiguration.settings.stateControllerId"
+                                             dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
+                        </tb-states-component>
+                    </div>
+                 </div>
+            </div>
+        </tb-dashboard-toolbar>
     </section>
     <section class="tb-dashboard-container tb-absolute-fill"
-             ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
+             ng-class="{ 'is-fullscreen': forceFullscreen, 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
         <section ng-show="!loading && vm.dashboardConfigurationError()" layout-align="center center"
                  ng-style="{'color': vm.dashboard.configuration.settings.titleColor}"
                  ng-class="{'tb-padded' : !vm.widgetEditMode}"
diff --git a/ui/src/app/dashboard/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
index dbe4cbe..ac98709 100644
--- a/ui/src/app/dashboard/dashboard-settings.controller.js
+++ b/ui/src/app/dashboard/dashboard-settings.controller.js
@@ -57,6 +57,10 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
         if (angular.isUndefined(vm.settings.showDashboardExport)) {
             vm.settings.showDashboardExport = true;
         }
+
+        if (angular.isUndefined(vm.settings.toolbarAlwaysOpen)) {
+            vm.settings.toolbarAlwaysOpen = false;
+        }
     }
 
     if (vm.gridSettings) {
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
index 88fc66d..f3ff704 100644
--- a/ui/src/app/dashboard/dashboard-settings.tpl.html
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -41,6 +41,9 @@
                             </md-select>
                         </md-input-container>
                         <div layout="row" layout-align="start center">
+                            <md-checkbox flex aria-label="{{ 'dashboard.toolbar-always-open' | translate }}"
+                                         ng-model="vm.settings.toolbarAlwaysOpen">{{ 'dashboard.toolbar-always-open' | translate }}
+                            </md-checkbox>
                             <md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
                                          ng-model="vm.settings.showTitle">{{ 'dashboard.display-title' | translate }}
                             </md-checkbox>
diff --git a/ui/src/app/dashboard/dashboard-toolbar.directive.js b/ui/src/app/dashboard/dashboard-toolbar.directive.js
new file mode 100644
index 0000000..63a34f8
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.directive.js
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './dashboard-toolbar.scss';
+
+import 'javascript-detect-element-resize/detect-element-resize';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import dashboardToolbarTemplate from './dashboard-toolbar.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardToolbar() {
+    return {
+        restrict: "E",
+        scope: true,
+        transclude: true,
+        bindToController: {
+            toolbarOpened: '=',
+            forceFullscreen: '=',
+            onTriggerClick: '&'
+        },
+        controller: DashboardToolbarController,
+        controllerAs: 'vm',
+        templateUrl: dashboardToolbarTemplate
+    };
+}
+
+/* eslint-disable angular/angularelement */
+
+
+/*@ngInject*/
+function DashboardToolbarController($scope, $element, $timeout, mdFabToolbarAnimation) {
+
+    let vm = this;
+
+    vm.mdFabToolbarElement = angular.element($element[0].querySelector('md-fab-toolbar'));
+
+    $timeout(function() {
+        vm.mdFabBackgroundElement = angular.element(vm.mdFabToolbarElement[0].querySelector('.md-fab-toolbar-background'));
+        vm.mdFabTriggerElement = angular.element(vm.mdFabToolbarElement[0].querySelector('md-fab-trigger button'));
+    });
+
+    addResizeListener(vm.mdFabToolbarElement[0], triggerFabResize); // eslint-disable-line no-undef
+
+    $scope.$on("$destroy", function () {
+        removeResizeListener(vm.mdFabToolbarElement[0], triggerFabResize); // eslint-disable-line no-undef
+    });
+
+    function triggerFabResize() {
+        var ctrl = vm.mdFabToolbarElement.controller('mdFabToolbar');
+        if (ctrl.isOpen) {
+            if (!vm.mdFabBackgroundElement[0].offsetWidth) {
+                mdFabToolbarAnimation.addClass(vm.mdFabToolbarElement, 'md-is-open', function () {
+                });
+            } else {
+                var color = window.getComputedStyle(vm.mdFabTriggerElement[0]).getPropertyValue('background-color'); //eslint-disable-line
+
+                var width = vm.mdFabToolbarElement[0].offsetWidth;
+                var scale = 2 * (width / vm.mdFabTriggerElement[0].offsetWidth);
+                vm.mdFabBackgroundElement[0].style.backgroundColor = color;
+                vm.mdFabBackgroundElement[0].style.borderRadius = width + 'px';
+
+                var transform = vm.mdFabBackgroundElement[0].style.transform;
+                var targetTransform = 'scale(' + scale + ')';
+                if (!transform || !angular.equals(transform, targetTransform)) {
+                    vm.mdFabBackgroundElement[0].style.transform = targetTransform;
+                }
+            }
+        }
+    }
+}
+
diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss
new file mode 100644
index 0000000..af4889e
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.scss
@@ -0,0 +1,114 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import "~compass-sass-mixins/lib/compass";
+@import '../../scss/constants';
+
+tb-dashboard-toolbar {
+  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-toolbar-background {
+                background-color: $primary-default !important;
+            }
+          }
+        }
+      }
+    }
+    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 {
+            position: absolute;
+            top: 25%;
+            margin: 0;
+            line-height: 18px;
+            height: 18px;
+            width: 18px;
+            min-height: 18px;
+            min-width: 18px;
+          }
+        }
+      }
+    }
+    &.is-fullscreen {
+      &.md-is-open {
+        md-fab-trigger {
+          .md-button {
+            &.md-fab {
+              .md-fab-toolbar-background {
+                  transition-delay: 0ms !important;
+                  transition-duration: 0ms !important;
+              }
+            }
+          }
+        }
+      }
+      .md-fab-toolbar-wrapper {
+        height: 64px;
+        md-toolbar {
+          min-height: 64px;
+          height: 64px;
+        }
+      }
+    }
+    .md-fab-toolbar-wrapper {
+      height: 50px;
+      md-toolbar {
+        min-height: 50px;
+        height: 50px;
+        md-fab-actions {
+          font-size: 16px;
+          margin-top: 0px;
+          .close-action {
+            margin-right: -18px;
+          }
+          .md-fab-action-item {
+            width: 100%;
+            height: 46px;
+            .tb-dashboard-action-panels {
+              height: 46px;
+              flex-direction: row-reverse;
+              .tb-dashboard-action-panel {
+                height: 46px;
+                flex-direction: row-reverse;
+                div {
+                  height: 46px;
+                }
+                md-select {
+                  pointer-events: all;
+                }
+                tb-states-component {
+                  pointer-events: all;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/dashboard-toolbar.tpl.html b/ui/src/app/dashboard/dashboard-toolbar.tpl.html
new file mode 100644
index 0000000..46192ff
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.tpl.html
@@ -0,0 +1,35 @@
+<!--
+
+    Copyright © 2016-2017 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+
+<md-fab-toolbar md-open="vm.toolbarOpened"
+                md-direction="left"
+                ng-class="{'is-fullscreen': vm.forceFullscreen, 'md-whiteframe-z1': vm.forceFullscreen}">
+    <md-fab-trigger class="align-with-text">
+        <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.onTriggerClick()">
+            <md-tooltip ng-show="!vm.toolbarOpened" md-direction="bottom">
+                {{ 'dashboard.open-toolbar' | translate }}
+            </md-tooltip>
+            <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
+        </md-button>
+    </md-fab-trigger>
+    <md-toolbar>
+        <md-fab-actions class="md-toolbar-tools">
+            <div ng-transclude></div>
+        </md-fab-actions>
+    </md-toolbar>
+</md-fab-toolbar>
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
index 73a97a1..d940f09 100644
--- a/ui/src/app/dashboard/index.js
+++ b/ui/src/app/dashboard/index.js
@@ -45,6 +45,7 @@ import AddDashboardsToCustomerController from './add-dashboards-to-customer.cont
 import AddWidgetController from './add-widget.controller';
 import DashboardDirective from './dashboard.directive';
 import EditWidgetDirective from './edit-widget.directive';
+import DashboardToolbar from './dashboard-toolbar.directive';
 
 export default angular.module('thingsboard.dashboard', [
     uiRouter,
@@ -78,4 +79,5 @@ export default angular.module('thingsboard.dashboard', [
     .controller('AddWidgetController', AddWidgetController)
     .directive('tbDashboardDetails', DashboardDirective)
     .directive('tbEditWidget', EditWidgetDirective)
+    .directive('tbDashboardToolbar', DashboardToolbar)
     .name;
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 6e6308d..622754c 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -328,6 +328,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",
+                    "toolbar-always-open": "Keep toolbar opened",
                     "title-color": "Title color",
                     "display-dashboards-selection": "Display dashboards selection",
                     "display-entities-selection": "Display entities selection",
diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss
index 73697fc..c56d40b 100644
--- a/ui/src/scss/constants.scss
+++ b/ui/src/scss/constants.scss
@@ -20,10 +20,12 @@
 $gray: #eee;
 
 $primary-palette-color: 'indigo';
+$default: '500';
 $hue-1: '300';
 $hue-2: '800';
 $hue-3: 'a100';
 
+$primary-default: #305680; //material-color($primary-palette-color, $default);
 $primary-hue-1: material-color($primary-palette-color, $hue-1);
 $primary-hue-2: material-color($primary-palette-color, $hue-2);
 $primary-hue-3: rgb(207, 216, 220);