瀏覽代碼

ux: merge branch navbarv2, new navbar with dashboard search available on all pages, closes #6475

Torkel Ödegaard 8 年之前
父節點
當前提交
e9d5e037e8
共有 73 個文件被更改,包括 923 次插入544 次删除
  1. 16 3
      public/app/core/components/grafana_app.ts
  2. 2 2
      public/app/core/components/help/help.html
  3. 30 5
      public/app/core/components/navbar/navbar.html
  4. 21 6
      public/app/core/components/navbar/navbar.ts
  5. 56 42
      public/app/core/components/search/search.html
  6. 5 0
      public/app/core/components/search/search.ts
  7. 3 1
      public/app/core/controllers/error_ctrl.js
  8. 3 0
      public/app/core/core.ts
  9. 209 0
      public/app/core/nav_model_srv.ts
  10. 4 0
      public/app/core/routes/routes.ts
  11. 1 5
      public/app/core/services/keybindingSrv.ts
  12. 11 3
      public/app/features/admin/admin.ts
  13. 3 1
      public/app/features/admin/adminEditOrgCtrl.js
  14. 2 1
      public/app/features/admin/adminEditUserCtrl.js
  15. 2 1
      public/app/features/admin/adminListOrgsCtrl.js
  16. 3 1
      public/app/features/admin/admin_list_users_ctrl.ts
  17. 1 2
      public/app/features/admin/partials/admin_home.html
  18. 1 6
      public/app/features/admin/partials/edit_org.html
  19. 1 6
      public/app/features/admin/partials/edit_user.html
  20. 1 6
      public/app/features/admin/partials/new_user.html
  21. 1 6
      public/app/features/admin/partials/orgs.html
  22. 1 2
      public/app/features/admin/partials/settings.html
  23. 1 2
      public/app/features/admin/partials/stats.html
  24. 6 9
      public/app/features/admin/partials/users.html
  25. 6 5
      public/app/features/alerting/alert_list_ctrl.ts
  26. 4 1
      public/app/features/alerting/notification_edit_ctrl.ts
  27. 6 3
      public/app/features/alerting/notifications_list_ctrl.ts
  28. 1 2
      public/app/features/alerting/partials/alert_list.html
  29. 1 6
      public/app/features/alerting/partials/notification_edit.html
  30. 1 2
      public/app/features/alerting/partials/notifications_list.html
  31. 22 53
      public/app/features/dashboard/dashnav/dashnav.html
  32. 82 96
      public/app/features/dashboard/dashnav/dashnav.ts
  33. 3 6
      public/app/features/dashboard/viewStateSrv.js
  34. 2 1
      public/app/features/org/change_password_ctrl.js
  35. 2 1
      public/app/features/org/newOrgCtrl.js
  36. 2 1
      public/app/features/org/orgApiKeysCtrl.js
  37. 2 1
      public/app/features/org/orgDetailsCtrl.js
  38. 3 1
      public/app/features/org/org_users_ctrl.ts
  39. 1 2
      public/app/features/org/partials/change_password.html
  40. 1 2
      public/app/features/org/partials/newOrg.html
  41. 1 2
      public/app/features/org/partials/orgApiKeys.html
  42. 1 2
      public/app/features/org/partials/orgDetails.html
  43. 1 2
      public/app/features/org/partials/orgUsers.html
  44. 2 3
      public/app/features/org/partials/profile.html
  45. 3 1
      public/app/features/org/profile_ctrl.ts
  46. 1 2
      public/app/features/playlist/partials/playlist.html
  47. 1 2
      public/app/features/playlist/partials/playlists.html
  48. 19 10
      public/app/features/playlist/playlist_edit_ctrl.ts
  49. 3 3
      public/app/features/playlist/playlist_srv.ts
  50. 8 6
      public/app/features/playlist/playlists_ctrl.ts
  51. 5 1
      public/app/features/playlist/specs/playlist_edit_ctrl_specs.ts
  52. 120 116
      public/app/features/plugins/ds_edit_ctrl.ts
  53. 26 16
      public/app/features/plugins/ds_list_ctrl.ts
  54. 1 2
      public/app/features/plugins/partials/ds_edit.html
  55. 1 5
      public/app/features/plugins/partials/ds_list.html
  56. 1 2
      public/app/features/plugins/partials/plugin_edit.html
  57. 1 2
      public/app/features/plugins/partials/plugin_list.html
  58. 1 2
      public/app/features/plugins/partials/plugin_page.html
  59. 12 7
      public/app/features/plugins/plugin_edit_ctrl.ts
  60. 3 1
      public/app/features/plugins/plugin_list_ctrl.ts
  61. 43 2
      public/app/features/plugins/plugin_page_ctrl.ts
  62. 1 2
      public/app/features/snapshot/partials/snapshots.html
  63. 10 1
      public/app/features/snapshot/snapshot_ctrl.ts
  64. 1 2
      public/app/features/styleguide/styleguide.html
  65. 3 1
      public/app/features/styleguide/styleguide.ts
  66. 1 2
      public/app/partials/dashboard.html
  67. 1 2
      public/app/partials/error.html
  68. 2 2
      public/sass/_old_responsive.scss
  69. 8 8
      public/sass/components/_dropdown.scss
  70. 39 7
      public/sass/components/_navbar.scss
  71. 74 31
      public/sass/components/_search.scss
  72. 5 3
      public/sass/components/_sidemenu.scss
  73. 1 1
      public/sass/components/_view_states.scss

+ 16 - 3
public/app/core/components/grafana_app.ts

@@ -105,10 +105,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
         if (pageClass) {
           body.removeClass(pageClass);
         }
-        pageClass = data.$$route.pageClass;
-        if (pageClass) {
-          body.addClass(pageClass);
+
+        if (data.$$route) {
+          pageClass = data.$$route.pageClass;
+          if (pageClass) {
+            body.addClass(pageClass);
+          }
         }
+
         $("#tooltip, .tooltip").remove();
 
         // check for kiosk url param
@@ -194,6 +198,15 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
             });
           }
         }
+
+        // hide menus
+        var openMenus = body.find('.navbar-page-btn--open');
+        if (openMenus.length > 0) {
+          if (target.parents('.navbar-page-btn--open').length === 0) {
+            openMenus.removeClass('navbar-page-btn--open');
+          }
+        }
+
         // hide sidemenu
         if (!ignoreSideMenuHide && !contextSrv.pinned && body.find('.sidemenu').length > 0) {
           if (target.parents('.sidemenu').length === 0) {

+ 2 - 2
public/app/core/components/help/help.html

@@ -1,7 +1,7 @@
 <div class="modal-body">
 	<div class="modal-header">
 		<h2 class="modal-header-title">
-			<i class="fa fa-keyboard"></i>
+			<i class="fa fa-keyboard-o"></i>
 			<span class="p-l-1">Shortcuts</span>
 		</h2>
 
@@ -20,7 +20,7 @@
 
 	<div class="modal-content help-modal">
 
-		<p class="small" style="position: absolute; top: 48px; right: 10px">
+		<p class="small" style="position: absolute; top: 13px; right: 44px">
 			<span class="shortcut-table-key">mod</span> =
 			<span class="muted">CTRL on windows or linux and CMD key on Mac</span>
 		</p>

+ 30 - 5
public/app/core/components/navbar/navbar.html

@@ -8,11 +8,36 @@
 		<i class="fa fa-chevron-left"></i>
 	</a>
 
-	<a href="{{ctrl.titleUrl}}" class="navbar-page-btn" ng-show="ctrl.title">
-		<i class="{{ctrl.icon}}" ng-show="ctrl.icon"></i>
-		<img ng-src="{{ctrl.iconUrl}}" ng-show="ctrl.iconUrl"></i>
-		{{ctrl.title}}
+  <a class="navbar-page-btn" ng-click="ctrl.showSearch()">
+		<i class="fa fa-search"></i>
 	</a>
 
-	<div ng-transclude></div>
+	<div ng-if="::!ctrl.hasMenu">
+		<a href="{{::ctrl.section.url}}" class="navbar-page-btn">
+      <i class="{{::ctrl.section.icon}}" ng-show="::ctrl.section.icon"></i>
+      <img ng-src="{{::ctrl.section.iconUrl}}" ng-show="::ctrl.section.iconUrl"></i>
+      {{::ctrl.section.title}}
+    </a>
+	</div>
+
+  <div class="dropdown navbar-section-wrapper"  ng-if="::ctrl.hasMenu">
+    <a href="{{::ctrl.section.url}}" class="navbar-page-btn" data-toggle="dropdown">
+      <i class="{{::ctrl.section.icon}}" ng-show="::ctrl.section.icon"></i>
+      <img ng-src="{{::ctrl.section.iconUrl}}" ng-show="::ctrl.section.iconUrl"></i>
+      {{::ctrl.section.title}}
+      <i class="fa fa-caret-down"></i>
+    </a>
+    <ul class="dropdown-menu dropdown-menu--navbar">
+      <li ng-repeat="navItem in ::ctrl.model.menu" ng-class="{active: navItem.active}">
+        <a class="pointer" ng-href="{{::navItem.url}}" ng-click="ctrl.navItemClicked(navItem, $event)">
+          <i class="{{::navItem.icon}}" ng-show="::navItem.icon"></i>
+          {{::navItem.title}}
+        </a>
+      </li>
+    </ul>
+  </div>
+
+  <div ng-transclude></div>
 </div>
+
+<dashboard-search></dashboard-search>

+ 21 - 6
public/app/core/components/navbar/navbar.ts

@@ -4,10 +4,28 @@ import config from 'app/core/config';
 import _ from 'lodash';
 import $ from 'jquery';
 import coreModule from '../../core_module';
+import {NavModel, NavModelItem}  from '../../nav_model_srv';
 
 export class NavbarCtrl {
+  model: NavModel;
+  section: NavModelItem;
+  hasMenu: boolean;
+
   /** @ngInject */
-  constructor(private $scope, private contextSrv) {
+  constructor(private $scope, private $rootScope, private contextSrv) {
+    this.section = this.model.section;
+    this.hasMenu = this.model.menu.length > 0;
+  }
+
+  showSearch() {
+    this.$rootScope.appEvent('show-dash-search');
+  }
+
+  navItemClicked(navItem, evt) {
+    if (navItem.clickHandler) {
+      navItem.clickHandler();
+      evt.preventDefault();
+    }
   }
 }
 
@@ -20,12 +38,9 @@ export function navbarDirective() {
     transclude: true,
     controllerAs: 'ctrl',
     scope: {
-      title: "@",
-      titleUrl: "@",
-      iconUrl: "@",
+      model: "=",
     },
-    link: function(scope, elem, attrs, ctrl) {
-      ctrl.icon = attrs.icon;
+    link: function(scope, elem) {
       elem.addClass('navbar');
     }
   };

+ 56 - 42
public/app/core/components/search/search.html

@@ -1,9 +1,22 @@
+
+<div class="search-backdrop" ng-if="ctrl.isOpen"></div>
+
 <div class="search-container" ng-if="ctrl.isOpen">
+
 	<div class="search-field-wrapper">
-		<span style="position: relative;">
-			<input  type="text" placeholder="Find dashboards by name" give-focus="ctrl.giveSearchFocus" tabindex="1"
-			ng-keydown="ctrl.keyDown($event)" ng-model="ctrl.query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.search()" />
-		</span>
+		<div class="search-field-icon">
+			<i class="fa fa-search"></i>
+		</div>
+
+		<input type="text" placeholder="Find dashboards by name" give-focus="ctrl.giveSearchFocus" tabindex="1"
+						ng-keydown="ctrl.keyDown($event)"
+						ng-model="ctrl.query.query"
+						ng-model-options="{ debounce: 500 }"
+						spellcheck='false'
+						ng-change="ctrl.search()"
+						ng-blur="ctrl.searchInputBlur()"
+						/>
+
 		<div class="search-switches">
 			<i class="fa fa-filter"></i>
 			<a class="pointer" href="javascript:void 0;" ng-click="ctrl.showStarred()" tabindex="2">
@@ -24,54 +37,55 @@
 				</span>
 			</span>
 		</div>
+
+		<div class="search-field-spacer"></div>
 	</div>
 
-	<div class="search-results-container" ng-if="ctrl.tagsMode">
-		<div ng-repeat="tag in ctrl.results" class="pointer" style="width: 180px; float: left;"
-			ng-class="{'selected': $index === ctrl.selectedIndex }"
-			ng-click="ctrl.filterByTag(tag.term, $event)">
-			<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
-				<i class="fa fa-tag"></i>
-				<span>{{tag.term}} &nbsp;({{tag.count}})</span>
-			</a>
+	<div class="search-dropdown" ng-class="{'search-dropdown--fade-in': ctrl.openCompleted}">
+		<div class="search-results-container" ng-if="ctrl.tagsMode">
+			<div ng-repeat="tag in ctrl.results" class="pointer" style="width: 180px; float: left;"
+				ng-class="{'selected': $index === ctrl.selectedIndex }"
+				ng-click="ctrl.filterByTag(tag.term, $event)">
+				<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
+					<i class="fa fa-tag"></i>
+					<span>{{tag.term}} &nbsp;({{tag.count}})</span>
+				</a>
+			</div>
 		</div>
-	</div>
 
-	<div class="search-results-container" ng-if="!ctrl.tagsMode">
-		<h6 ng-hide="ctrl.results.length">No dashboards matching your query were found.</h6>
+		<div class="search-results-container" ng-if="!ctrl.tagsMode">
+			<h6 ng-hide="ctrl.results.length">No dashboards matching your query were found.</h6>
 
-		<a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in ctrl.results"
-			ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
+			<a class="search-item pointer search-item-{{row.type}}" bindonce ng-repeat="row in ctrl.results"
+				ng-class="{'selected': $index == ctrl.selectedIndex}" ng-href="{{row.url}}">
 
-			<span class="search-result-tags">
-				<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag"  class="label label-tag">
-					{{tag}}
+				<span class="search-result-tags">
+					<span ng-click="ctrl.filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name="tag"  class="label label-tag">
+						{{tag}}
+					</span>
+					<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
 				</span>
-				<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
-			</span>
 
-			<span class="search-result-link">
-				<i class="fa search-result-icon"></i>
-				<span bo-text="row.title"></span>
-			</span>
-		</a>
-	</div>
-
-	<div class="search-button-row">
-		<a class="btn btn-inverse pull-left" href="dashboard/new" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
-			<i class="fa fa-plus"></i>
-			Create New
-		</a>
+				<span class="search-result-link">
+					<i class="fa search-result-icon"></i>
+					<span bo-text="row.title"></span>
+				</span>
+			</a>
+		</div>
 
-		<a class="btn btn-inverse pull-left" href="dashboard/new/?editview=import" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
-			<i class="fa fa-upload"></i>
-			Import
-		</a>
+		<div class="search-button-row">
+			<a class="btn btn-secondary" href="dashboard/new" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
+				<i class="fa fa-plus"></i>&nbsp; New Dashboard
+			</a>
 
-		<a class="pull-right search-button-row-explore-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
-      		Find <img src="public/img/icn-dashboard-tiny.svg" width="14" /> dashboards on Grafana.com
-		</a>
+			<a class="btn btn-inverse" href="dashboard/new/?editview=import" ng-show="ctrl.contextSrv.isEditor" ng-click="ctrl.isOpen = false;">
+				<i class="fa fa-upload"></i>&nbsp; Import Dashboard
+			</a>
 
- 		<div class="clearfix"></div>
+			<a class="search-button-row-explore-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
+				Find <img src="public/img/icn-dashboard-tiny.svg" width="14" /> dashboards on Grafana.com
+			</a>
+		</div>
 	</div>
 </div>
+

+ 5 - 0
public/app/core/components/search/search.ts

@@ -18,6 +18,8 @@ export class SearchCtrl {
   showImport: boolean;
   dismiss: any;
   ignoreClose: any;
+  // triggers fade animation class
+  openCompleted: boolean;
 
   /** @ngInject */
   constructor(private $scope, private $location, private $timeout, private backendSrv, private contextSrv, private $rootScope) {
@@ -27,6 +29,7 @@ export class SearchCtrl {
 
   closeSearch() {
     this.isOpen = this.ignoreClose;
+    this.openCompleted = false;
   }
 
   openSearch(evt, payload) {
@@ -56,6 +59,7 @@ export class SearchCtrl {
     }
 
     this.$timeout(() => {
+      this.openCompleted = true;
       this.ignoreClose = false;
       this.giveSearchFocus = this.giveSearchFocus + 1;
       this.search();
@@ -169,6 +173,7 @@ export function searchDirective() {
     controller: SearchCtrl,
     bindToController: true,
     controllerAs: 'ctrl',
+    scope: {},
   };
 }
 

+ 3 - 1
public/app/core/controllers/error_ctrl.js

@@ -5,7 +5,9 @@ define([
 function (angular, coreModule) {
   'use strict';
 
-  coreModule.default.controller('ErrorCtrl', function($scope, contextSrv) {
+  coreModule.default.controller('ErrorCtrl', function($scope, contextSrv, navModelSrv) {
+
+    $scope.navModel = navModelSrv.getNotFoundNav();
 
     var showSideMenu = contextSrv.sidemenu;
     contextSrv.sidemenu = false;

+ 3 - 0
public/app/core/core.ts

@@ -44,6 +44,7 @@ import {assignModelProperties} from './utils/model_utils';
 import {contextSrv} from './services/context_srv';
 import {KeybindingSrv} from './services/keybindingSrv';
 import {helpModal} from './components/help/help';
+import {NavModelSrv, NavModel} from './nav_model_srv';
 
 
 export {
@@ -68,4 +69,6 @@ export {
   contextSrv,
   KeybindingSrv,
   helpModal,
+  NavModelSrv,
+  NavModel,
 };

+ 209 - 0
public/app/core/nav_model_srv.ts

@@ -0,0 +1,209 @@
+///<reference path="../headers/common.d.ts" />
+
+import coreModule from 'app/core/core_module';
+
+export interface NavModelItem {
+  title: string;
+  url: string;
+  icon?: string;
+  iconUrl?: string;
+}
+
+export interface NavModel {
+  section: NavModelItem;
+  menu: NavModelItem[];
+}
+
+export class NavModelSrv {
+
+
+  /** @ngInject */
+  constructor(private contextSrv) {
+  }
+
+  getAlertingNav(subPage) {
+    return {
+      section: {
+        title: 'Alerting',
+        url: 'plugins',
+        icon: 'icon-gf icon-gf-alert'
+      },
+      menu: [
+        {title: 'Alert List', active: subPage === 0, url: 'alerting/list', icon: 'fa fa-list-ul'},
+        {title: 'Notification channels', active: subPage === 1, url: 'alerting/notifications', icon: 'fa fa-bell-o'},
+      ]
+    };
+  }
+
+  getDatasourceNav(subPage) {
+    return {
+      section: {
+        title: 'Data Sources',
+        url: 'datasources',
+        icon: 'icon-gf icon-gf-datasources'
+      },
+      menu: [
+        {title: 'List view', active: subPage === 0, url: '/datasources', icon: 'fa fa-list-ul'},
+        {title: 'Add data source', active: subPage === 1, url: '/datasources/new', icon: 'fa fa-plus'},
+      ]
+    };
+  }
+
+  getPlaylistsNav(subPage) {
+    return {
+      section: {
+        title: 'Playlists',
+        url: 'playlists',
+        icon: 'fa fa-fw fa-film'
+      },
+      menu: [
+        {title: 'List view', active: subPage === 0, url: '/playlists', icon: 'fa fa-list-ul'},
+        {title: 'Add Playlist', active: subPage === 1, url: '/playlists/create', icon: 'fa fa-plus'},
+      ]
+    };
+  }
+
+  getProfileNav() {
+    return {
+      section: {
+        title: 'User Profile',
+        url: 'profile',
+        icon: 'fa fa-fw fa-user'
+      },
+      menu: []
+    };
+  }
+
+  getNotFoundNav() {
+    return {
+      section: {
+        title: 'Page',
+        url: '',
+        icon: 'fa fa-fw fa-warning'
+      },
+      menu: []
+    };
+  }
+
+  getOrgNav(subPage) {
+    return {
+      section: {
+        title: 'Organization',
+        url: 'org',
+        icon: 'icon-gf icon-gf-users'
+      },
+      menu: [
+        {title: 'Preferences', active: subPage === 0, url: '/org', icon: 'fa fa-fw fa-cog'},
+        {title: 'Org Users', active: subPage === 1, url: '/org/users', icon: 'fa fa-fw fa-users'},
+        {title: 'API Keys', active: subPage === 2, url: '/org/apikeys', icon: 'fa fa-fw fa-key'},
+      ]
+    };
+  }
+
+  getAdminNav(subPage) {
+    return {
+      section: {
+        title: 'Admin',
+        url: 'admin',
+        icon: 'fa fa-fw fa-cogs'
+      },
+      menu: [
+        {title: 'Users', active: subPage === 0, url: '/admin/users', icon: 'fa fa-fw fa-user'},
+        {title: 'Orgs', active: subPage === 1, url: '/admin/orgs', icon: 'fa fa-fw fa-users'},
+        {title: 'Server Settings', active: subPage === 2, url: '/admin/settings', icon: 'fa fa-fw fa-cogs'},
+        {title: 'Server Stats', active: subPage === 2, url: '/admin/stats', icon: 'fa fa-fw fa-line-chart'},
+        {title: 'Style Guide', active: subPage === 2, url: '/styleguide', icon: 'fa fa-fw fa-key'},
+      ]
+    };
+  }
+
+  getPluginsNav() {
+    return {
+      section: {
+        title: 'Plugins',
+        url: 'plugins',
+        icon: 'icon-gf icon-gf-apps'
+      },
+      menu: []
+    };
+  }
+
+  getDashboardNav(dashboard, dashNavCtrl) {
+    // special handling for snapshots
+    if (dashboard.meta.isSnapshot) {
+      return {
+        section: {
+          title: dashboard.title,
+          icon: 'icon-gf icon-gf-snapshot'
+        },
+        menu: [
+          {
+            title: 'Go to original dashboard',
+            icon: 'fa fa-fw fa-external-link',
+            url: dashboard.snapshot.originalUrl,
+          }
+        ]
+      };
+    }
+
+    var menu = [];
+
+    if (dashboard.meta.canEdit) {
+      menu.push({
+        title: 'Settings',
+        icon: 'fa fa-fw fa-cog',
+        clickHandler: () => dashNavCtrl.openEditView('settings')
+      });
+
+      menu.push({
+        title: 'Templating',
+        icon: 'fa fa-fw fa-code',
+        clickHandler: () => dashNavCtrl.openEditView('templating')
+      });
+
+      menu.push({
+        title: 'Annotations',
+        icon: 'fa fa-fw fa-bolt',
+        clickHandler: () => dashNavCtrl.openEditView('annotations')
+      });
+
+      menu.push({
+        title: 'View JSON',
+        icon: 'fa fa-fw fa-eye',
+        clickHandler: () => dashNavCtrl.viewJson()
+      });
+    }
+
+    if (this.contextSrv.isEditor && !dashboard.editable) {
+      menu.push({
+        title: 'Make Editable',
+        icon: 'fa fa-fw fa-edit',
+        clickHandler: () => dashNavCtrl.makeEditable()
+      });
+    }
+
+    menu.push({
+      title: 'Shortcuts',
+      icon: 'fa fa-fw fa-keyboard-o',
+      clickHandler: () => dashNavCtrl.showHelpModal()
+    });
+
+    if (this.contextSrv.isEditor) {
+      menu.push({
+        title: 'Save As ...',
+        icon: 'fa fa-fw fa-save',
+        clickHandler: () => dashNavCtrl.saveDashboardAs()
+      });
+    }
+
+    return {
+      section: {
+        title: dashboard.title,
+        icon: 'icon-gf icon-gf-dashboard'
+      },
+      menu: menu
+    };
+  }
+}
+
+coreModule.service('navModelSrv', NavModelSrv);

+ 4 - 0
public/app/core/routes/routes.ts

@@ -103,11 +103,13 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
   .when('/admin', {
     templateUrl: 'public/app/features/admin/partials/admin_home.html',
     controller : 'AdminHomeCtrl',
+    controllerAs: 'ctrl',
     resolve: loadAdminBundle,
   })
   .when('/admin/settings', {
     templateUrl: 'public/app/features/admin/partials/settings.html',
     controller : 'AdminSettingsCtrl',
+    controllerAs: 'ctrl',
     resolve: loadAdminBundle,
   })
   .when('/admin/users', {
@@ -129,11 +131,13 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
   .when('/admin/orgs', {
     templateUrl: 'public/app/features/admin/partials/orgs.html',
     controller : 'AdminListOrgsCtrl',
+    controllerAs: 'ctrl',
     resolve: loadAdminBundle,
   })
   .when('/admin/orgs/edit/:id', {
     templateUrl: 'public/app/features/admin/partials/edit_org.html',
     controller : 'AdminEditOrgCtrl',
+    controllerAs: 'ctrl',
     resolve: loadAdminBundle,
   })
   .when('/admin/stats', {

+ 1 - 5
public/app/core/services/keybindingSrv.ts

@@ -113,10 +113,6 @@ export class KeybindingSrv {
       scope.appEvent('shift-time-forward');
     });
 
-    this.bind('mod+i', () => {
-      scope.appEvent('quick-snapshot');
-    });
-
     // edit panel
     this.bind('e', () => {
       if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
@@ -225,7 +221,7 @@ export class KeybindingSrv {
       }
 
       scope.appEvent('hide-dash-editor');
-      scope.exitFullscreen();
+      scope.appEvent('panel-change-view', {fullscreen: false, edit: false});
     });
   }
 }

+ 11 - 3
public/app/features/admin/admin.ts

@@ -6,9 +6,11 @@ import  './adminEditUserCtrl';
 import coreModule from 'app/core/core_module';
 
 class AdminSettingsCtrl {
+  navModel: any;
 
   /** @ngInject **/
-  constructor($scope, backendSrv) {
+  constructor($scope, backendSrv, navModelSrv) {
+    this.navModel = navModelSrv.getAdminNav();
 
     backendSrv.get('/api/admin/settings').then(function(settings) {
       $scope.settings = settings;
@@ -18,16 +20,22 @@ class AdminSettingsCtrl {
 }
 
 class AdminHomeCtrl {
+  navModel: any;
+
   /** @ngInject **/
-  constructor() {
+  constructor(navModelSrv) {
+    this.navModel = navModelSrv.getAdminNav();
   }
 }
 
 export class AdminStatsCtrl {
   stats: any;
+  navModel: any;
 
   /** @ngInject */
-  constructor(backendSrv: any) {
+  constructor(backendSrv: any, navModelSrv) {
+    this.navModel = navModelSrv.getAdminNav();
+
     backendSrv.get('/api/admin/stats').then(stats => {
       this.stats = stats;
     });

+ 3 - 1
public/app/features/admin/adminEditOrgCtrl.js

@@ -6,9 +6,11 @@ function (angular) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('AdminEditOrgCtrl', function($scope, $routeParams, backendSrv, $location) {
+  module.controller('AdminEditOrgCtrl', function($scope, $routeParams, backendSrv, $location, navModelSrv) {
 
     $scope.init = function() {
+      $scope.navModel = navModelSrv.getAdminNav();
+
       if ($routeParams.id) {
         $scope.getOrg($routeParams.id);
         $scope.getOrgUsers($routeParams.id);

+ 2 - 1
public/app/features/admin/adminEditUserCtrl.js

@@ -7,10 +7,11 @@ function (angular, _) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location) {
+  module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location, navModelSrv) {
     $scope.user = {};
     $scope.newOrg = { name: '', role: 'Editor' };
     $scope.permissions = {};
+    $scope.navModel = navModelSrv.getAdminNav();
 
     $scope.init = function() {
       if ($routeParams.id) {

+ 2 - 1
public/app/features/admin/adminListOrgsCtrl.js

@@ -6,9 +6,10 @@ function (angular) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('AdminListOrgsCtrl', function($scope, backendSrv) {
+  module.controller('AdminListOrgsCtrl', function($scope, backendSrv, navModelSrv) {
 
     $scope.init = function() {
+      $scope.navModel = navModelSrv.getAdminNav();
       $scope.getOrgs();
     };
 

+ 3 - 1
public/app/features/admin/admin_list_users_ctrl.ts

@@ -8,9 +8,11 @@ export default class AdminListUsersCtrl {
   totalPages: number;
   showPaging = false;
   query: any;
+  navModel: any;
 
   /** @ngInject */
-  constructor(private $scope, private backendSrv) {
+  constructor(private $scope, private backendSrv, private navModelSrv) {
+    this.navModel = navModelSrv.getAdminNav();
     this.query = '';
     this.getUsers();
   }

+ 1 - 2
public/app/features/admin/partials/admin_home.html

@@ -1,5 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 6
public/app/features/admin/partials/edit_org.html

@@ -1,9 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<a href="admin/orgs" class="navbar-page-btn">
-		<i class="icon-gf icon-gf-users"></i>
-		Orgs
-	</a>
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 6
public/app/features/admin/partials/edit_user.html

@@ -1,9 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<a href="admin/users" class="navbar-page-btn">
-		<i class="icon-gf icon-gf-users"></i>
-		Users
-	</a>
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 6
public/app/features/admin/partials/new_user.html

@@ -1,9 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<a href="admin/users" class="navbar-page-btn">
-		<i class="icon-gf icon-gf-users"></i>
-		Users
-	</a>
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 6
public/app/features/admin/partials/orgs.html

@@ -1,9 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<a href="admin/orgs" class="navbar-page-btn">
-		<i class="icon-gf icon-gf-users"></i>
-		Orgs
-	</a>
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 2
public/app/features/admin/partials/settings.html

@@ -1,5 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 2
public/app/features/admin/partials/stats.html

@@ -1,5 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 6 - 9
public/app/features/admin/partials/users.html

@@ -1,9 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-  <a href="admin/users" class="navbar-page-btn">
-    <i class="icon-gf icon-gf-users"></i>
-    Users
-  </a>
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
   <div class="page-header">
@@ -14,12 +9,14 @@
       Add new user
     </a>
   </div>
-  <div class="search-field-wrapper pull-right width-18">
+
+  <div class="gf-form pull-right gf-form-group">
+		<label class="gf-form-label">Search</label>
     <span style="position: relative;">
-      <input  type="text" placeholder="Find user by name/login/email" tabindex="1" give-focus="true"
-      ng-model="ctrl.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.getUsers()" />
+      <input class="gf-form-input width-15" type="text" placeholder="Find user by name/login/email" tabindex="1" give-focus="true" ng-model="ctrl.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.getUsers()" />
     </span>
   </div>
+
   <div class="admin-list-table">
     <table class="filter-table form-inline">
       <thead>

+ 6 - 5
public/app/features/alerting/alert_list_ctrl.ts

@@ -2,13 +2,12 @@
 
 import angular from 'angular';
 import _ from 'lodash';
-import coreModule from '../../core/core_module';
-import appEvents from '../../core/app_events';
 import moment from 'moment';
+
+import {coreModule, appEvents} from  'app/core/core';
 import alertDef from './alert_def';
 
 export class AlertListCtrl {
-
   alerts: any;
   stateFilters = [
     {text: 'All', value: null},
@@ -17,13 +16,15 @@ export class AlertListCtrl {
     {text: 'No Data', value: 'no_data'},
     {text: 'Paused', value: 'paused'},
   ];
-
   filters = {
     state: 'ALL'
   };
+  navModel: any;
 
   /** @ngInject */
-  constructor(private backendSrv, private $location, private $scope) {
+  constructor(private backendSrv, private $location, private $scope, navModelSrv) {
+    this.navModel = navModelSrv.getAlertingNav(0);
+
     var params = $location.search();
     this.filters.state = params.state || null;
     this.loadAlerts();

+ 4 - 1
public/app/features/alerting/notification_edit_ctrl.ts

@@ -7,6 +7,7 @@ import {appEvents, coreModule} from 'app/core/core';
 
 export class AlertNotificationEditCtrl {
   theForm: any;
+  navModel: any;
   testSeverity = "critical";
   notifiers: any;
   notifierTemplateId: string;
@@ -23,7 +24,9 @@ export class AlertNotificationEditCtrl {
   };
 
   /** @ngInject */
-  constructor(private $routeParams, private backendSrv, private $location, private $templateCache) {
+  constructor(private $routeParams, private backendSrv, private $location, private $templateCache, navModelSrv) {
+    this.navModel = navModelSrv.getAlertingNav();
+
     this.backendSrv.get(`/api/alert-notifiers`).then(notifiers => {
       this.notifiers = notifiers;
 

+ 6 - 3
public/app/features/alerting/notifications_list_ctrl.ts

@@ -2,16 +2,19 @@
 
 import angular from 'angular';
 import _ from 'lodash';
-import coreModule from '../../core/core_module';
 import config from 'app/core/config';
 
-export class AlertNotificationsListCtrl {
+import {coreModule} from  'app/core/core';
+
 
+export class AlertNotificationsListCtrl {
   notifications: any;
+  navModel: any;
 
   /** @ngInject */
-  constructor(private backendSrv, private $scope) {
+  constructor(private backendSrv, private $scope, navModelSrv) {
     this.loadNotifications();
+    this.navModel = navModelSrv.getAlertingNav(1);
   }
 
   loadNotifications() {

+ 1 - 2
public/app/features/alerting/partials/alert_list.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container" >
 	<div class="page-header">

+ 1 - 6
public/app/features/alerting/partials/notification_edit.html

@@ -1,9 +1,4 @@
-<navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
-	<a href="alerting/notifications" class="navbar-page-btn">
-		<i class="fa fa-fw fa-rss"></i>
-		Notification channels
-	</a>
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
   <div class="page-header">

+ 1 - 2
public/app/features/alerting/partials/notifications_list.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-alert" title="Alerting" title-url="alerting">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container" >
 	<div class="page-header">

+ 22 - 53
public/app/features/dashboard/dashnav/dashnav.html

@@ -1,93 +1,62 @@
-<navbar>
+<navbar model="ctrl.navModel">
 
-<a class="pointer navbar-page-btn" ng-if="::!dashboardMeta.isSnapshot" ng-click="openSearch()">
-	<i class="icon-gf icon-gf-dashboard"></i>
-	<span>{{dashboard.title}}</span>
-	<i class="fa fa-caret-down"></i>
-</a>
-
-<a class="pointer navbar-page-btn" ng-if="::dashboardMeta.isSnapshot" bs-tooltip="titleTooltip" data-placement="bottom" ng-click="openSearch()">
-	<i class="icon-gf icon-gf-snapshot"></i>
-	<span>
-		{{dashboard.title}}
-		<em class="small">&nbsp;&nbsp;(snapshot)</em>
-	</span>
-</a>
-
-<ul class="nav dash-playlist-actions" ng-if="playlistSrv">
+<ul class="nav dash-playlist-actions" ng-if="ctrl.playlistSrv.isPlaying">
 	<li>
-		<a ng-click="playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
+		<a ng-click="ctrl.playlistSrv.prev()"><i class="fa fa-step-backward"></i></a>
 	</li>
 	<li>
-		<a ng-click="playlistSrv.stop()"><i class="fa fa-stop"></i></a>
+		<a ng-click="ctrl.playlistSrv.stop()"><i class="fa fa-stop"></i></a>
 	</li>
 	<li>
-		<a ng-click="playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
+		<a ng-click="ctrl.playlistSrv.next()"><i class="fa fa-step-forward"></i></a>
 	</li>
 </ul>
 
 <ul class="nav pull-left dashnav-action-icons">
-	<li ng-show="::dashboardMeta.canStar">
-		<a class="pointer" ng-click="starDashboard()">
-			<i class="fa" ng-class="{'fa-star-o': !dashboardMeta.isStarred, 'fa-star': dashboardMeta.isStarred}" style="color: orange;"></i>
+	<li ng-show="::ctrl.dashboard.meta.canStar">
+		<a class="pointer" ng-click="ctrl.starDashboard()">
+			<i class="fa" ng-class="{'fa-star-o': !ctrl.dashboard.meta.isStarred, 'fa-star': ctrl.dashboard.meta.isStarred}" style="color: orange;"></i>
 		</a>
 	</li>
-	<li ng-show="::dashboardMeta.canShare" class="dropdown">
-		<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
+	<li ng-show="::ctrl.dashboard.meta.canShare" class="dropdown">
+		<a class="pointer" ng-click="ctrl.hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
 		<ul class="dropdown-menu">
 			<li>
-				<a class="pointer" ng-click="shareDashboard(0)">
+				<a class="pointer" ng-click="ctrl.shareDashboard(0)">
 					<i class="fa fa-link"></i> Link to Dashboard
 					<div class="dropdown-desc">Share an internal link to the current dashboard. Some configuration options available.</div>
 				</a>
 			</li>
 			<li>
-				<a class="pointer" ng-click="shareDashboard(1)">
+				<a class="pointer" ng-click="ctrl.shareDashboard(1)">
 					<i class="icon-gf icon-gf-snapshot"></i>Snapshot
 					<div class="dropdown-desc">Interactive, publically accessible dashboard. Sensitive data is stripped out.</div>
 				</a>
 			</li>
-      <li>
-				<a class="pointer" ng-click="shareDashboard(2)">
+			<li>
+				<a class="pointer" ng-click="ctrl.shareDashboard(2)">
 					<i class="fa fa-cloud-upload"></i>Export
 					<div class="dropdown-desc">Export the dashboard to a JSON file for others and to share on Grafana.com</div>
 				</a>
 			</li>
 		</ul>
 	</li>
-	<li ng-show="::dashboardMeta.canSave">
-		<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
+	<li ng-show="::ctrl.dashboard.meta.canSave">
+		<a ng-click="ctrl.saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
 	</li>
-	<li ng-if="dashboard.snapshot.originalUrl">
-		<a ng-href="{{dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom"><i class="fa fa-link"></i></a>
-	</li>
-	<li ng-if="::showSettingsMenu" class="dropdown">
-		<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Manage dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
-		<ul class="dropdown-menu">
-			<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('settings');">Settings</a></li>
-			<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('annotations');">Annotations</a></li>
-			<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="openEditView('templating');">Templating</a></li>
-			<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="viewJson();">View JSON</a></li>
-			<li ng-if="contextSrv.isEditor && !dashboard.editable"><a class="pointer" ng-click="makeEditable();">Make Editable</a></li>
-			<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
-			<li class="dropdown-menu-item-with-shortcut">
-        <a class="pointer" ng-click="showHelpModal();">
-          Shortcuts <span class="dropdown-menu-item-shortcut">?</span>
-        </a>
-      </li>
-			<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
-		</ul>
+	<li ng-if="ctrl.dashboard.snapshot.originalUrl">
+		<a ng-href="{{ctrl.dashboard.snapshot.originalUrl}}" bs-tooltip="'Open original dashboard'" data-placement="bottom"><i class="fa fa-link"></i></a>
 	</li>
 </ul>
 
 <ul class="nav pull-right">
-	<li ng-show="dashboard.meta.fullscreen" class="dashnav-back-to-dashboard">
-		<a ng-click="exitFullscreen()">
+	<li ng-show="ctrl.dashboard.meta.fullscreen" class="dashnav-back-to-dashboard">
+		<a ng-click="ctrl.exitFullscreen()">
 			Back to dashboard
 		</a>
 	</li>
-	<li ng-if="dashboard">
-		<gf-time-picker dashboard="dashboard"></gf-time-picker>
+	<li>
+		<gf-time-picker dashboard="ctrl.dashboard"></gf-time-picker>
 	</li>
 </ul>
 

+ 82 - 96
public/app/features/dashboard/dashnav/dashnav.ts

@@ -3,92 +3,98 @@
 import _ from 'lodash';
 import moment from 'moment';
 import angular from 'angular';
-
+import {appEvents, NavModel} from 'app/core/core';
+import {DashboardModel} from '../model';
 import {DashboardExporter} from '../export/exporter';
 
 export class DashNavCtrl {
+  dashboard: DashboardModel;
+  navModel: NavModel;
+  titleTooltip: string;
 
   /** @ngInject */
-  constructor($scope, $rootScope, dashboardSrv, $location, playlistSrv, backendSrv, $timeout, datasourceSrv) {
-
-    $scope.init = function() {
-      $scope.onAppEvent('save-dashboard', $scope.saveDashboard);
-      $scope.onAppEvent('delete-dashboard', $scope.deleteDashboard);
-      $scope.onAppEvent('quick-snapshot', $scope.quickSnapshot);
-
-      $scope.showSettingsMenu = $scope.dashboardMeta.canEdit || $scope.contextSrv.isEditor;
-
-      if ($scope.dashboardMeta.isSnapshot) {
-        $scope.showSettingsMenu = false;
-        var meta = $scope.dashboardMeta;
-        $scope.titleTooltip = 'Created: &nbsp;' + moment(meta.created).calendar();
+  constructor(
+    private $scope,
+    private $rootScope,
+    private dashboardSrv,
+    private $location,
+    private playlistSrv,
+    private backendSrv,
+    private $timeout,
+    private datasourceSrv,
+    private navModelSrv) {
+
+      this.navModel = navModelSrv.getDashboardNav(this.dashboard, this);
+
+      appEvents.on('save-dashboard', this.saveDashboard.bind(this), $scope);
+      appEvents.on('delete-dashboard', this.deleteDashboard.bind(this), $scope);
+
+      if (this.dashboard.meta.isSnapshot) {
+        var meta = this.dashboard.meta;
+        this.titleTooltip = 'Created: &nbsp;' + moment(meta.created).calendar();
         if (meta.expires) {
-          $scope.titleTooltip += '<br>Expires: &nbsp;' + moment(meta.expires).fromNow() + '<br>';
+          this.titleTooltip += '<br>Expires: &nbsp;' + moment(meta.expires).fromNow() + '<br>';
         }
       }
-    };
+    }
 
-    $scope.openEditView = function(editview) {
-      var search = _.extend($location.search(), {editview: editview});
-      $location.search(search);
-    };
+    openEditView(editview) {
+      var search = _.extend(this.$location.search(), {editview: editview});
+      this.$location.search(search);
+    }
 
-    $scope.showHelpModal = function() {
-      $scope.appEvent('show-modal', {templateHtml: '<help-modal></help-modal>'});
-    };
+    showHelpModal() {
+      appEvents.emit('show-modal', {templateHtml: '<help-modal></help-modal>'});
+    }
 
-    $scope.starDashboard = function() {
-      if ($scope.dashboardMeta.isStarred) {
-        backendSrv.delete('/api/user/stars/dashboard/' + $scope.dashboard.id).then(function() {
-          $scope.dashboardMeta.isStarred = false;
-        });
-      } else {
-        backendSrv.post('/api/user/stars/dashboard/' + $scope.dashboard.id).then(function() {
-          $scope.dashboardMeta.isStarred = true;
+    starDashboard() {
+      if (this.dashboard.meta.isStarred) {
+        return this.backendSrv.delete('/api/user/stars/dashboard/' + this.dashboard.id).then(() =>  {
+          this.dashboard.meta.isStarred = false;
         });
       }
-    };
 
-    $scope.shareDashboard = function(tabIndex) {
-      var modalScope = $scope.$new();
+      this.backendSrv.post('/api/user/stars/dashboard/' + this.dashboard.id).then(() => {
+        this.dashboard.meta.isStarred = true;
+      });
+    }
+
+    shareDashboard(tabIndex) {
+      var modalScope = this.$scope.$new();
       modalScope.tabIndex = tabIndex;
+      modalScope.dashboard = this.dashboard;
 
-      $scope.appEvent('show-modal', {
+      appEvents.emit('show-modal', {
         src: 'public/app/features/dashboard/partials/shareModal.html',
         scope: modalScope
       });
-    };
-
-    $scope.quickSnapshot = function() {
-      $scope.shareDashboard(1);
-    };
+    }
 
-    $scope.openSearch = function() {
-      $scope.appEvent('show-dash-search');
-    };
-
-    $scope.hideTooltip = function(evt) {
+    hideTooltip(evt) {
       angular.element(evt.currentTarget).tooltip('hide');
-      $scope.appEvent('hide-dash-search');
-    };
+    }
 
-    $scope.makeEditable = function() {
-      $scope.dashboard.editable = true;
+    makeEditable() {
+      this.dashboard.editable = true;
 
-      return dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(function() {
+      return this.dashboardSrv.saveDashboard({makeEditable: true, overwrite: false}).then(() => {
         // force refresh whole page
         window.location.href = window.location.href;
       });
-    };
+    }
+
+    exitFullscreen() {
+      this.$rootScope.appEvent('panel-change-view', {fullscreen: false, edit: false});
+    }
 
-    $scope.saveDashboard = function(options) {
-      return dashboardSrv.saveDashboard(options);
-    };
+    saveDashboard(options) {
+      return this.dashboardSrv.saveDashboard(options);
+    }
 
-    $scope.deleteDashboard = function() {
+    deleteDashboard() {
       var confirmText = "";
-      var text2 = $scope.dashboard.title;
-      var alerts = $scope.dashboard.rows.reduce((memo, row) => {
+      var text2 = this.dashboard.title;
+      var alerts = this.dashboard.rows.reduce((memo, row) => {
         memo += row.panels.filter(panel => panel.alert).length;
         return memo;
       }, 0);
@@ -98,60 +104,37 @@ export class DashNavCtrl {
         text2 = `This dashboad contains ${alerts} alerts. Deleting this dashboad will also delete those alerts`;
       }
 
-      $scope.appEvent('confirm-modal', {
+      appEvents.emit('confirm-modal', {
         title: 'Delete',
         text: 'Do you want to delete this dashboard?',
         text2: text2,
         icon: 'fa-trash',
         confirmText: confirmText,
         yesText: 'Delete',
-        onConfirm: function() {
-          $scope.dashboardMeta.canSave = false;
-          $scope.deleteDashboardConfirmed();
+        onConfirm: () => {
+          this.dashboard.meta.canSave = false;
+          this.deleteDashboardConfirmed();
         }
       });
-    };
+    }
 
-    $scope.deleteDashboardConfirmed = function() {
-      backendSrv.delete('/api/dashboards/db/' + $scope.dashboardMeta.slug).then(function() {
-        $scope.appEvent('alert-success', ['Dashboard Deleted', $scope.dashboard.title + ' has been deleted']);
-        $location.url('/');
+    deleteDashboardConfirmed() {
+      this.backendSrv.delete('/api/dashboards/db/' + this.dashboard.meta.slug).then(() => {
+        appEvents.emit('alert-success', ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
+        this.$location.url('/');
       });
-    };
+    }
 
-    $scope.saveDashboardAs = function() {
-      return dashboardSrv.saveDashboardAs();
-    };
+    saveDashboardAs() {
+      return this.dashboardSrv.saveDashboardAs();
+    }
 
-    $scope.viewJson = function() {
-      var clone = $scope.dashboard.getSaveModelClone();
+    viewJson() {
+      var clone = this.dashboard.getSaveModelClone();
       var html = angular.toJson(clone, true);
       var uri = "data:application/json;charset=utf-8," + encodeURIComponent(html);
       var newWindow = window.open(uri);
-    };
-
-    $scope.snapshot = function() {
-      $scope.dashboard.snapshot = true;
-      $rootScope.$broadcast('refresh');
-
-      $timeout(function() {
-        $scope.dashboard.snapshot = false;
-        $scope.appEvent('dashboard-snapshot-cleanup');
-      }, 1000);
-
-    };
-
-    $scope.editJson = function() {
-      var clone = $scope.dashboard.getSaveModelClone();
-      $scope.appEvent('show-json-editor', { object: clone });
-    };
-
-    $scope.stopPlaylist = function() {
-      playlistSrv.stop(1);
-    };
-
-    $scope.init();
-  }
+    }
 }
 
 export function dashNavDirective() {
@@ -159,7 +142,10 @@ export function dashNavDirective() {
     restrict: 'E',
     templateUrl: 'public/app/features/dashboard/dashnav/dashnav.html',
     controller: DashNavCtrl,
+    bindToController: true,
+    controllerAs: 'ctrl',
     transclude: true,
+    scope: { dashboard: "=" }
   };
 }
 

+ 3 - 6
public/app/features/dashboard/viewStateSrv.js

@@ -20,12 +20,6 @@ function (angular, _, $, config) {
       self.$scope = $scope;
       self.dashboard = $scope.dashboard;
 
-      $scope.exitFullscreen = function() {
-        if (self.state.fullscreen) {
-          self.update({ fullscreen: false });
-        }
-      };
-
       $scope.onAppEvent('$routeUpdate', function() {
         var urlState = self.getQueryStringState();
         if (self.needsSync(urlState)) {
@@ -41,6 +35,9 @@ function (angular, _, $, config) {
         self.registerPanel(payload.scope);
       });
 
+      // this marks changes to location during this digest cycle as not to add history item
+      // dont want url changes like adding orgId to add browser history
+      $location.replace();
       this.update(this.getQueryStringState());
       this.expandRowForPanel();
     }

+ 2 - 1
public/app/features/org/change_password_ctrl.js

@@ -7,11 +7,12 @@ function (angular, config) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('ChangePasswordCtrl', function($scope, backendSrv, $location) {
+  module.controller('ChangePasswordCtrl', function($scope, backendSrv, $location, navModelSrv) {
 
     $scope.command = {};
     $scope.authProxyEnabled = config.authProxyEnabled;
     $scope.ldapEnabled = config.ldapEnabled;
+    $scope.navModel = navModelSrv.getProfileNav();
 
     $scope.changePassword = function() {
       if (!$scope.userForm.$valid) { return; }

+ 2 - 1
public/app/features/org/newOrgCtrl.js

@@ -7,8 +7,9 @@ function (angular, config) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('NewOrgCtrl', function($scope, $http, backendSrv) {
+  module.controller('NewOrgCtrl', function($scope, $http, backendSrv, navModelSrv) {
 
+    $scope.navModel = navModelSrv.getOrgNav(0);
     $scope.newOrg = {name: ''};
 
     $scope.createOrg = function() {

+ 2 - 1
public/app/features/org/orgApiKeysCtrl.js

@@ -6,8 +6,9 @@ function (angular) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('OrgApiKeysCtrl', function($scope, $http, backendSrv) {
+  module.controller('OrgApiKeysCtrl', function($scope, $http, backendSrv, navModelSrv) {
 
+    $scope.navModel = navModelSrv.getOrgNav(0);
     $scope.roleTypes = ['Viewer', 'Editor', 'Admin'];
     $scope.token = { role: 'Viewer' };
 

+ 2 - 1
public/app/features/org/orgDetailsCtrl.js

@@ -6,10 +6,11 @@ function (angular) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('OrgDetailsCtrl', function($scope, $http, backendSrv, contextSrv) {
+  module.controller('OrgDetailsCtrl', function($scope, $http, backendSrv, contextSrv, navModelSrv) {
 
     $scope.init = function() {
       $scope.getOrgInfo();
+      $scope.navModel = navModelSrv.getOrgNav(0);
     };
 
     $scope.getOrgInfo = function() {

+ 3 - 1
public/app/features/org/org_users_ctrl.ts

@@ -11,13 +11,15 @@ export class OrgUsersCtrl {
   pendingInvites: any;
   editor: any;
   showInviteUI: boolean;
+  navModel: any;
 
   /** @ngInject */
-  constructor(private $scope, private $http, private backendSrv) {
+  constructor(private $scope, private $http, private backendSrv, navModelSrv) {
     this.user = {
       loginOrEmail: '',
       role: 'Viewer',
     };
+    this.navModel = navModelSrv.getOrgNav(0);
 
     this.get();
     this.editor = { index: 0 };

+ 1 - 2
public/app/features/org/partials/change_password.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Profile" title-url="profile">
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 2
public/app/features/org/partials/newOrg.html

@@ -1,5 +1,4 @@
-<navbar title="Organization" icon="icon-gf icon-gf-users">
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container" ng-form="playlistEditForm">
 	<div class="page-header">

+ 1 - 2
public/app/features/org/partials/orgApiKeys.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Organization" title-url="org">
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 2
public/app/features/org/partials/orgDetails.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Organization" title-url="org">
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 2
public/app/features/org/partials/orgUsers.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Organization Users" title-url="org/users">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 2 - 3
public/app/features/org/partials/profile.html

@@ -1,9 +1,8 @@
-<navbar icon="icon-gf icon-gf-users" title="Profile" title-url="profile">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>Profile</h1>
+		<h1>User Profile</h1>
 	</div>
 
 	<form name="ctrl.userForm" class="gf-form-group">

+ 3 - 1
public/app/features/org/profile_ctrl.ts

@@ -11,11 +11,13 @@ export class ProfileCtrl {
   userForm: any;
   showOrgsList = false;
   readonlyLoginFields = config.disableLoginForm;
+  navModel: any;
 
   /** @ngInject **/
-  constructor(private backendSrv, private contextSrv, private $location) {
+  constructor(private backendSrv, private contextSrv, private $location, navModelSrv) {
     this.getUser();
     this.getUserOrgs();
+    this.navModel = navModelSrv.getProfileNav();
   }
 
   getUser() {

+ 1 - 2
public/app/features/playlist/partials/playlist.html

@@ -1,5 +1,4 @@
-<navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container" ng-form="playlistEditForm">
 	<div class="page-header">

+ 1 - 2
public/app/features/playlist/partials/playlists.html

@@ -1,5 +1,4 @@
-<navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
   <div class="page-header">

+ 19 - 10
public/app/features/playlist/playlist_edit_ctrl.ts

@@ -16,21 +16,30 @@ export class PlaylistEditCtrl {
   playlistItems: any = [];
   dashboardresult: any = [];
   tagresult: any = [];
+  navModel: any;
 
   /** @ngInject */
-  constructor(private $scope, private playlistSrv, private backendSrv, private $location, private $route) {
+  constructor(
+    private $scope,
+    private playlistSrv,
+    private backendSrv,
+    private $location,
+    private $route,
+    private navModelSrv
+  ) {
+
+    this.navModel = navModelSrv.getPlaylistsNav(0);
+
     if ($route.current.params.id) {
       var playlistId = $route.current.params.id;
 
-      backendSrv.get('/api/playlists/' + playlistId)
-        .then((result) => {
-          this.playlist = result;
-        });
+      backendSrv.get('/api/playlists/' + playlistId).then(result => {
+        this.playlist = result;
+      });
 
-      backendSrv.get('/api/playlists/' + playlistId + '/items')
-        .then((result) => {
-          this.playlistItems = result;
-        });
+      backendSrv.get('/api/playlists/' + playlistId + '/items').then(result => {
+        this.playlistItems = result;
+      });
     }
   }
 
@@ -85,7 +94,7 @@ export class PlaylistEditCtrl {
       ? this.backendSrv.put('/api/playlists/' + playlist.id, playlist)
       : this.backendSrv.post('/api/playlists', playlist);
 
-    savePromise
+      savePromise
       .then(() => {
         this.$scope.appEvent('alert-success', ['Playlist saved', '']);
         this.$location.path('/playlists');

+ 3 - 3
public/app/features/playlist/playlist_srv.ts

@@ -11,6 +11,7 @@ class PlaylistSrv {
   private interval: any;
   private playlistId: number;
   private startUrl: string;
+  public isPlaying: boolean;
 
   /** @ngInject */
   constructor(private $rootScope: any, private $location: any, private $timeout: any, private backendSrv: any) { }
@@ -42,7 +43,7 @@ class PlaylistSrv {
     this.startUrl = window.location.href;
     this.index = 0;
     this.playlistId = playlistId;
-    this.$rootScope.playlistSrv = this;
+    this.isPlaying = true;
 
     this.backendSrv.get(`/api/playlists/${playlistId}`).then(playlist => {
       this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then(dashboards => {
@@ -55,13 +56,12 @@ class PlaylistSrv {
 
   stop() {
     this.index = 0;
+    this.isPlaying = false;
     this.playlistId = 0;
 
     if (this.cancelPromise) {
       this.$timeout.cancel(this.cancelPromise);
     }
-
-    this.$rootScope.playlistSrv = null;
   }
 }
 

+ 8 - 6
public/app/features/playlist/playlists_ctrl.ts

@@ -6,20 +6,22 @@ import coreModule from '../../core/core_module';
 
 export class PlaylistsCtrl {
   playlists: any;
+  navModel: any;
 
   /** @ngInject */
-  constructor(private $scope, private $location, private backendSrv) {
-    backendSrv.get('/api/playlists')
-      .then((result) => {
-        this.playlists = result;
-      });
+  constructor(private $scope, private $location, private backendSrv, private navModelSrv) {
+    this.navModel = navModelSrv.getPlaylistsNav(0);
+
+    backendSrv.get('/api/playlists').then(result => {
+      this.playlists = result;
+    });
   }
 
   removePlaylistConfirmed(playlist) {
     _.remove(this.playlists, { id: playlist.id });
 
     this.backendSrv.delete('/api/playlists/' + playlist.id)
-      .then(() => {
+    .then(() => {
         this.$scope.appEvent('alert-success', ['Playlist deleted', '']);
       }, () => {
         this.$scope.appEvent('alert-error', ['Unable to delete playlist', '']);

+ 5 - 1
public/app/features/playlist/specs/playlist_edit_ctrl_specs.ts

@@ -5,7 +5,11 @@ import {PlaylistEditCtrl} from '../playlist_edit_ctrl';
 describe('PlaylistEditCtrl', () => {
   var ctx: any;
   beforeEach(() => {
-    ctx = new PlaylistEditCtrl(null, null, null, null, { current: { params: {} } });
+    let navModelSrv = {
+      getPlaylistsNav: page => {},
+    };
+
+    ctx = new PlaylistEditCtrl(null, null, null, null, { current: { params: {} } }, navModelSrv);
 
     ctx.dashboardresult = [
       { id: 2, title: 'dashboard: 2' },

+ 120 - 116
public/app/features/plugins/ds_edit_ctrl.ts

@@ -30,6 +30,7 @@ export class DataSourceEditCtrl {
   hasDashboards: boolean;
   editForm: any;
   gettingStarted: boolean;
+  navModel: any;
 
   /** @ngInject */
   constructor(
@@ -38,141 +39,144 @@ export class DataSourceEditCtrl {
     private backendSrv,
     private $routeParams,
     private $location,
-    private datasourceSrv) {
-
-      this.isNew = true;
-      this.datasources = [];
-      this.tabIndex = 0;
-
-      this.loadDatasourceTypes().then(() => {
-        if (this.$routeParams.id) {
-          this.getDatasourceById(this.$routeParams.id);
-        } else {
-          this.initNewDatasourceModel();
-        }
-      });
-    }
-
-    initNewDatasourceModel() {
-      this.current = angular.copy(defaults);
-
-      // We are coming from getting started
-      if (this.$location.search().gettingstarted) {
-        this.gettingStarted = true;
-        this.current.isDefault = true;
-      }
-
-      this.typeChanged();
-    }
-
-    loadDatasourceTypes() {
-      if (datasourceTypes.length > 0) {
-        this.types = datasourceTypes;
-        return this.$q.when(null);
+    private datasourceSrv,
+    private navModelSrv,
+  ) {
+
+    this.navModel = navModelSrv.getDatasourceNav(0);
+    this.isNew = true;
+    this.datasources = [];
+    this.tabIndex = 0;
+
+    this.loadDatasourceTypes().then(() => {
+      if (this.$routeParams.id) {
+        this.getDatasourceById(this.$routeParams.id);
+      } else {
+        this.initNewDatasourceModel();
       }
+    });
+  }
 
-      return this.backendSrv.get('/api/plugins', {enabled: 1, type: 'datasource'}).then(plugins => {
-        datasourceTypes = plugins;
-        this.types = plugins;
-      });
-    }
+  initNewDatasourceModel() {
+    this.current = angular.copy(defaults);
 
-    getDatasourceById(id) {
-      this.backendSrv.get('/api/datasources/' + id).then(ds => {
-        this.isNew = false;
-        this.current = ds;
-        if (datasourceCreated) {
-          datasourceCreated = false;
-          this.testDatasource();
-        }
-        return this.typeChanged();
-      });
+    // We are coming from getting started
+    if (this.$location.search().gettingstarted) {
+      this.gettingStarted = true;
+      this.current.isDefault = true;
     }
 
-    typeChanged() {
-      this.hasDashboards = false;
-      return this.backendSrv.get('/api/plugins/' + this.current.type + '/settings').then(pluginInfo => {
-        this.datasourceMeta = pluginInfo;
-        console.log(this.datasourceMeta) ;
-        this.hasDashboards = _.find(pluginInfo.includes, {type: 'dashboard'});
-      });
-    }
+    this.typeChanged();
+  }
 
-    updateFrontendSettings() {
-      return this.backendSrv.get('/api/frontend/settings').then(settings => {
-        config.datasources = settings.datasources;
-        config.defaultDatasource = settings.defaultDatasource;
-        this.datasourceSrv.init();
-      });
+  loadDatasourceTypes() {
+    if (datasourceTypes.length > 0) {
+      this.types = datasourceTypes;
+      return this.$q.when(null);
     }
 
-    testDatasource() {
-      this.testing = { done: false };
-
-      this.datasourceSrv.get(this.current.name).then(datasource => {
-        if (!datasource.testDatasource) {
-          delete this.testing;
-          return;
-        }
+    return this.backendSrv.get('/api/plugins', {enabled: 1, type: 'datasource'}).then(plugins => {
+      datasourceTypes = plugins;
+      this.types = plugins;
+    });
+  }
+
+  getDatasourceById(id) {
+    this.backendSrv.get('/api/datasources/' + id).then(ds => {
+      this.isNew = false;
+      this.current = ds;
+      if (datasourceCreated) {
+        datasourceCreated = false;
+        this.testDatasource();
+      }
+      return this.typeChanged();
+    });
+  }
+
+  typeChanged() {
+    this.hasDashboards = false;
+    return this.backendSrv.get('/api/plugins/' + this.current.type + '/settings').then(pluginInfo => {
+      this.datasourceMeta = pluginInfo;
+      console.log(this.datasourceMeta) ;
+      this.hasDashboards = _.find(pluginInfo.includes, {type: 'dashboard'});
+    });
+  }
+
+  updateFrontendSettings() {
+    return this.backendSrv.get('/api/frontend/settings').then(settings => {
+      config.datasources = settings.datasources;
+      config.defaultDatasource = settings.defaultDatasource;
+      this.datasourceSrv.init();
+    });
+  }
+
+  testDatasource() {
+    this.testing = { done: false };
+
+    this.datasourceSrv.get(this.current.name).then(datasource => {
+      if (!datasource.testDatasource) {
+        delete this.testing;
+        return;
+      }
 
-        return datasource.testDatasource().then(result => {
-          this.testing.message = result.message;
-          this.testing.status = result.status;
-          this.testing.title = result.title;
-        }).catch(err => {
-          if (err.statusText) {
-            this.testing.message = err.statusText;
-            this.testing.title = "HTTP Error";
-          } else {
-            this.testing.message = err.message;
-            this.testing.title = "Unknown error";
-          }
-        });
-      }).finally(() => {
-        if (this.testing) {
-          this.testing.done = true;
+      return datasource.testDatasource().then(result => {
+        this.testing.message = result.message;
+        this.testing.status = result.status;
+        this.testing.title = result.title;
+      }).catch(err => {
+        if (err.statusText) {
+          this.testing.message = err.statusText;
+          this.testing.title = "HTTP Error";
+        } else {
+          this.testing.message = err.message;
+          this.testing.title = "Unknown error";
         }
       });
-    }
-
-    saveChanges() {
-      if (!this.editForm.$valid) {
-        return;
+    }).finally(() => {
+      if (this.testing) {
+        this.testing.done = true;
       }
+    });
+  }
 
-      if (this.current.id) {
-        return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
-          this.updateFrontendSettings().then(() => {
-            this.testDatasource();
-          });
-        });
-      } else {
-        return this.backendSrv.post('/api/datasources', this.current).then(result => {
-          this.updateFrontendSettings();
-
-          datasourceCreated = true;
-          this.$location.path('datasources/edit/' + result.id);
-        });
-      }
+  saveChanges() {
+    if (!this.editForm.$valid) {
+      return;
     }
 
-    confirmDelete() {
-      this.backendSrv.delete('/api/datasources/' + this.current.id).then(() => {
-        this.$location.path('datasources');
+    if (this.current.id) {
+      return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
+        this.updateFrontendSettings().then(() => {
+          this.testDatasource();
+        });
       });
-    }
+    } else {
+      return this.backendSrv.post('/api/datasources', this.current).then(result => {
+        this.updateFrontendSettings();
 
-    delete(s) {
-      appEvents.emit('confirm-modal', {
-        title: 'Delete',
-        text: 'Are you sure you want to delete this datasource?',
-        yesText: "Delete",
-        icon: "fa-trash",
-        onConfirm: () => {
-          this.confirmDelete();
-        }
+        datasourceCreated = true;
+        this.$location.path('datasources/edit/' + result.id);
       });
     }
+  }
+
+  confirmDelete() {
+    this.backendSrv.delete('/api/datasources/' + this.current.id).then(() => {
+      this.$location.path('datasources');
+    });
+  }
+
+  delete(s) {
+    appEvents.emit('confirm-modal', {
+      title: 'Delete',
+      text: 'Are you sure you want to delete this datasource?',
+      yesText: "Delete",
+      icon: "fa-trash",
+      onConfirm: () => {
+        this.confirmDelete();
+      }
+    });
+  }
 }
 
 coreModule.controller('DataSourceEditCtrl', DataSourceEditCtrl);

+ 26 - 16
public/app/features/plugins/ds_list_ctrl.ts

@@ -6,31 +6,41 @@ import coreModule from '../../core/core_module';
 
 export class DataSourcesCtrl {
   datasources: any;
+  navModel: any;
 
   /** @ngInject */
-  constructor(private $scope, private $location, private $http, private backendSrv, private datasourceSrv) {
-    backendSrv.get('/api/datasources')
-      .then((result) => {
-        this.datasources = result;
-      });
+  constructor(
+    private $scope,
+    private $location,
+    private $http,
+    private backendSrv,
+    private datasourceSrv,
+    private navModelSrv
+  ) {
+
+    this.navModel = this.navModelSrv.getDatasourceNav(0);
+
+    backendSrv.get('/api/datasources').then(result => {
+      this.datasources = result;
+    });
   }
 
   removeDataSourceConfirmed(ds) {
 
     this.backendSrv.delete('/api/datasources/' + ds.id)
-      .then(() => {
-        this.$scope.appEvent('alert-success', ['Datasource deleted', '']);
-      }, () => {
-        this.$scope.appEvent('alert-error', ['Unable to delete datasource', '']);
-      }).then(() => {
+    .then(() => {
+      this.$scope.appEvent('alert-success', ['Datasource deleted', '']);
+    }, () => {
+      this.$scope.appEvent('alert-error', ['Unable to delete datasource', '']);
+    }).then(() => {
       this.backendSrv.get('/api/datasources')
-        .then((result) => {
-          this.datasources = result;
-        });
+      .then((result) => {
+        this.datasources = result;
+      });
       this.backendSrv.get('/api/frontend/settings')
-        .then((settings) => {
-          this.datasourceSrv.init(settings.datasources);
-        });
+      .then((settings) => {
+        this.datasourceSrv.init(settings.datasources);
+      });
     });
   }
 

+ 1 - 2
public/app/features/plugins/partials/ds_edit.html

@@ -1,5 +1,4 @@
-<navbar title="Data Sources" title-url="datasources" icon="icon-gf icon-gf-datasources">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 

+ 1 - 5
public/app/features/plugins/partials/ds_list.html

@@ -1,8 +1,4 @@
-<navbar
-	title="Data Sources"
-	title-url="datasources"
-	icon="icon-gf icon-gf-datasources">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 1 - 2
public/app/features/plugins/partials/plugin_edit.html

@@ -1,5 +1,4 @@
-<navbar title="Plugins" title-url="plugins" icon="icon-gf icon-gf-apps">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container" ng-init="ctrl.init()">
   <div class="page-header">

+ 1 - 2
public/app/features/plugins/partials/plugin_list.html

@@ -1,5 +1,4 @@
-<navbar title="Plugins" icon="icon-gf icon-gf-apps" title-url="plugins">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
   <div class="page-header">

+ 1 - 2
public/app/features/plugins/partials/plugin_page.html

@@ -1,5 +1,4 @@
-<navbar icon-url="{{ctrl.appLogoUrl}}" title="{{ctrl.appModel.name}}" title-url="{{ctrl.appModel.defaultNavUrl}}">
-</navbar>
+<navbar model="ctrl.navModel" ng-if="ctrl.navModel"></navbar>
 
 <div class="page-container" >
 	<div ng-if="ctrl.page">

+ 12 - 7
public/app/features/plugins/plugin_edit_ctrl.ts

@@ -13,17 +13,22 @@ export class PluginEditCtrl {
   includedDatasources: any;
   tabIndex: number;
   tabs: any;
+  navModel: any;
   hasDashboards: any;
   preUpdateHook: () => any;
   postUpdateHook: () => any;
 
   /** @ngInject */
-  constructor(private $scope,
-              private $rootScope,
-              private backendSrv,
-              private $routeParams,
-              private $sce,
-              private $http) {
+  constructor(
+    private $scope,
+    private $rootScope,
+    private backendSrv,
+    private $routeParams,
+    private $sce,
+    private $http,
+    private navModelSrv,
+  ) {
+    this.navModel = navModelSrv.getPluginsNav();
     this.model = {};
     this.pluginId = $routeParams.pluginId;
     this.tabIndex = 0;
@@ -31,7 +36,7 @@ export class PluginEditCtrl {
 
     this.preUpdateHook = () => Promise.resolve();
     this.postUpdateHook = () => Promise.resolve();
-   }
+  }
 
   init() {
     return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => {

+ 3 - 1
public/app/features/plugins/plugin_list_ctrl.ts

@@ -5,10 +5,12 @@ import angular from 'angular';
 export class PluginListCtrl {
   plugins: any[];
   tabIndex: number;
+  navModel: any;
 
   /** @ngInject */
-  constructor(private backendSrv: any, $location) {
+  constructor(private backendSrv: any, $location, navModelSrv) {
     this.tabIndex = 0;
+    this.navModel = navModelSrv.getPluginsNav();
 
     var pluginType = $location.search().type || 'panel';
     switch (pluginType) {

+ 43 - 2
public/app/features/plugins/plugin_page_ctrl.ts

@@ -2,6 +2,7 @@
 
 import angular from 'angular';
 import _ from 'lodash';
+import {NavModel} from 'app/core/core';
 
 var pluginInfoCache = {};
 
@@ -9,7 +10,7 @@ export class AppPageCtrl {
   page: any;
   pluginId: any;
   appModel: any;
-  appLogoUrl: any;
+  navModel: NavModel;
 
   /** @ngInject */
   constructor(private backendSrv, private $routeParams: any, private $rootScope) {
@@ -25,13 +26,53 @@ export class AppPageCtrl {
   initPage(app) {
     this.appModel = app;
     this.page = _.find(app.includes, {slug: this.$routeParams.slug});
-    this.appLogoUrl = app.info.logos.small;
 
     pluginInfoCache[this.pluginId] = app;
 
     if (!this.page) {
       this.$rootScope.appEvent('alert-error', ['App Page Not Found', '']);
+
+      this.navModel = {
+        section: {
+          title: "Page not found",
+          url: app.defaultNavUrl,
+          icon: 'icon-gf icon-gf-sadface',
+        },
+        menu: [],
+      };
+
+      return;
+    }
+
+    let menu = [];
+
+    for (let item of app.includes) {
+      if (item.addToNav) {
+        if (item.type === 'dashboard') {
+          menu.push({
+            title: item.name,
+            url: 'dashboard/db/' + item.slug,
+            icon: 'fa fa-fw fa-dot-circle-o',
+          });
+        }
+        if (item.type === 'page') {
+          menu.push({
+            title: item.name,
+            url: `plugins/${app.id}/page/${item.slug}`,
+            icon: 'fa fa-fw fa-dot-circle-o',
+          });
+        }
+      }
     }
+
+    this.navModel = {
+      section: {
+        title: app.name,
+        url: app.defaultNavUrl,
+        iconUrl: app.info.logos.small,
+      },
+      menu: menu,
+    };
   }
 
   loadPluginInfo() {

+ 1 - 2
public/app/features/snapshot/partials/snapshots.html

@@ -1,5 +1,4 @@
-<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots" title-url="dashboard/snapshots">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
   <div class="page-header">

+ 10 - 1
public/app/features/snapshot/snapshot_ctrl.ts

@@ -4,11 +4,20 @@ import angular from 'angular';
 import _ from 'lodash';
 
 export class SnapshotsCtrl {
-
+  navModel: any;
   snapshots: any;
 
   /** @ngInject */
   constructor(private $rootScope, private backendSrv) {
+    this.navModel = {
+      section: {
+        title: 'Snapshots',
+        icon:  'icon-gf icon-gf-snapshot',
+        url: 'dashboard/snapshots',
+      },
+      menu: [],
+    };
+
     this.backendSrv.get('/api/dashboard/snapshots').then(result => {
       this.snapshots = result;
     });

+ 1 - 2
public/app/features/styleguide/styleguide.html

@@ -1,5 +1,4 @@
-<navbar icon="fa fa-fw fa-adjust" title="Style Guide" title-url="styleguide">
-</navbar>
+<navbar model="ctrl.navModel"></navbar>
 
 <div class="page-container">
 	<div class="page-header">

+ 3 - 1
public/app/features/styleguide/styleguide.ts

@@ -12,9 +12,11 @@ class StyleGuideCtrl {
   icons: any = [];
   page: any;
   pages = ['colors', 'buttons', 'icons', 'plugins'];
+  navModel: any;
 
   /** @ngInject **/
-  constructor(private $http, private $routeParams, private $location, private backendSrv) {
+  constructor(private $http, private $routeParams, private $location, private backendSrv, navModelSrv) {
+    this.navModel = navModelSrv.getAdminNav();
     this.theme = config.bootData.user.lightTheme ? 'light': 'dark';
     this.page = {};
 

+ 1 - 2
public/app/partials/dashboard.html

@@ -1,9 +1,8 @@
 <div dash-class ng-if="dashboard">
-	<dashnav></dashnav>
+	<dashnav dashboard="dashboard"></dashnav>
 
 	<div class="dashboard-container">
 		<div dash-editor-view></div>
-		<dashboard-search></dashboard-search>
 		<div class="clearfix"></div>
 
 		<dashboard-submenu ng-if="dashboard.meta.submenuEnabled" dashboard="dashboard"></dashboard-submenu>

+ 1 - 2
public/app/partials/error.html

@@ -1,5 +1,4 @@
-<navbar title="404" icon="fa fa-fw fa-question" title-url="/">
-</navbar>
+<navbar model="navModel"></navbar>
 
 <div class="page-container">
 

+ 2 - 2
public/sass/_old_responsive.scss

@@ -40,14 +40,14 @@
 
 @include media-breakpoint-down(xs) {
   .page-dashboard .navbar-page-btn {
-    max-width: 150px;
+    max-width: 250px;
   }
 }
 
 // form styles
 @include media-breakpoint-up(md) {
   .page-dashboard .navbar-page-btn {
-    max-width: 180px;
+    max-width: 280px;
   }
   .gf-timepicker-nav-btn {
     max-width: 120px;

+ 8 - 8
public/sass/components/_dropdown.scss

@@ -15,13 +15,13 @@
 }
 
 .dropdown-desc {
-    position: relative;
-    top: -3px;
-    width: 250px;
-    font-size: 80%;
-    margin-left: 22px;
-    color: $gray-2;
-    white-space: normal;
+  position: relative;
+  top: -3px;
+  width: 250px;
+  font-size: 80%;
+  margin-left: 22px;
+  color: $gray-2;
+  white-space: normal;
 }
 
 // Dropdown arrow/caret
@@ -75,7 +75,7 @@
 
   // Links within the dropdown menu
   > li {
-      > a {
+    > a {
       display: block;
       padding: 3px 20px 3px 15px;
       clear: both;

+ 39 - 7
public/sass/components/_navbar.scss

@@ -42,7 +42,6 @@
 // Hover/focus
 .navbar .nav > li > a:focus,
 .navbar .nav > li > a:hover {
-  background-color: $navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover/:focus from .active
   color: $navbarLinkColorHover;
   text-decoration: none;
 }
@@ -64,10 +63,12 @@
   border-right: 1px solid $tight-form-border;
   background-color: $navbarButtonBackground;
   padding: 0.4rem 1.0rem 0.4rem 1rem;
+  min-height:: $navbarHeight;
 
   .fa-caret-down {
     font-size: 70%;
   }
+
   .fa-chevron-left{
     display: none;
   }
@@ -95,7 +96,7 @@
   .icon-gf-grafana_wordmark {
     font-size: 21px;
     position: relative;
-    top: 4px;
+    top: 5px;
     padding-left: 5px;
     display: none;
   }
@@ -108,16 +109,16 @@
   float: left;
   display: block;
   margin: 0;
-  font-size: 1.4em;
+  font-size: 1.4rem;
   border-right: 1px solid $tight-form-border;
   color: darken($link-color, 5%);
   background-color: $navbarButtonBackground;
   font-size: $font-size-lg;
-  padding: 1rem 1rem 0.7rem 1rem;
+  padding: 1rem 1rem 0.75rem 1rem;
+  min-height:: $navbarHeight;
 
-  &:hover {
+  &:hover, &.active {
     background: $navbarButtonBackgroundHighlight;
-    color: $link-color;
   }
 
   .fa-caret-down {
@@ -138,9 +139,36 @@
   }
 }
 
+.navbar-page-btn-wrapper {
+  display: inline-block;
+  position: relative;
+}
+
+.dropdown-menu.dropdown-menu--navbar {
+  top: 100%;
+  min-width: 100%;
+  margin-top: 0px;
+
+  li a {
+    padding: $spacer/2 $spacer;
+    border-left: 2px solid $side-menu-bg;
+    background: $side-menu-bg;
+
+    i {
+      display: inline-block;
+      padding-right: 21px;
+    }
+
+    &:hover {
+      @include left-brand-border-gradient();
+      color: $link-hover-color;
+      background: $input-label-bg;
+    }
+  }
+}
+
 .sidemenu-pinned {
   .navbar-brand-btn {
-    background-color: $page-bg;
     width: $side-menu-width;
 
     .icon-gf-grafana_wordmark  {
@@ -160,3 +188,7 @@
   }
 }
 
+.navbar-section-wrapper {
+  position: relative;
+  float: left;
+}

+ 74 - 31
public/sass/components/_search.scss

@@ -1,14 +1,20 @@
+.search-backdrop {
+  position: fixed;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  top: $navbarHeight;
+  z-index: $zindex-modal-backdrop;
+  background-color: $black;
+  @include opacity(70);
+}
+
 .search-container {
-  left: 72px;
-  top: 39px;
-  margin: 16px 0 0 2px;
-  z-index: 1000;
+  left: 78px;
+  top: 0;
+  right: 0;
+  z-index: ($zindex-modal-backdrop + 10);
   position: absolute;
-  width: 700px;
-  box-shadow: $search-shadow;
-  padding: 10px;
-  background-color: $panel-bg;
-  border: $panel-border;
 
   .label-tag {
     margin-left: 6px;
@@ -19,19 +25,48 @@
 
 // Search
 .search-field-wrapper {
-  padding-bottom: 10px;
+  width: 100%;
+  display: flex;
+  background-color: $navbarButtonBackground;
+
   input {
-    width: 100%;
-    padding: 8px 8px;
-    height: 100%;
+    max-width: 653px;
+    //padding: 0.5rem 1.5rem 0.5rem 0;
+    padding: 1rem 1rem 0.75rem 1rem;
+    height: 51px;
+    line-height: 51px;
     box-sizing: border-box;
+    outline: none;
+    background: $side-menu-bg;
+    background-color: $navbarButtonBackground;
+    flex-grow: 10;
   }
-  button {
-    margin: 0 4px 0 0;
-  }
-  > span {
-    display: block;
-    overflow: hidden;
+}
+
+.search-field-spacer {
+  flex-grow: 1;
+}
+.search-switches {
+  flex-grow: 1;
+  padding: 1rem 1rem 0.75rem 1rem;
+  white-space: nowrap;
+}
+
+.search-field-icon {
+  font-size: $font-size-lg;
+  padding: 1rem 1rem 0.75rem 1rem;
+}
+
+.search-dropdown {
+  display: flex;
+  max-width: 1100px;
+  visibility: none;
+  opacity: 0;
+
+  &--fade-in {
+    visibility: visible;
+    opacity: 1;
+    transition: opacity 0.3s;
   }
 }
 
@@ -40,10 +75,9 @@
   overflow: auto;
   display: block;
   line-height: 28px;
-
-  .search-item:hover, .search-item.selected {
-    background-color: $grafanaListHighlight;
-  }
+  padding: $spacer;
+  background: $panel-bg;
+  flex-grow: 10;
 
   .selected {
     .search-result-tag {
@@ -71,8 +105,10 @@
     word-wrap: break-word;
     display: block;
     padding: 3px 10px;
-    background-color: $grafanaListBackground;
+    white-space: nowrap;
+    background-color: $tight-form-bg;
     margin-bottom: 4px;
+    @include left-brand-border();
 
     .search-result-icon::before {
       content: "\f009";
@@ -81,6 +117,15 @@
     &.search-item-dash-home .search-result-icon::before {
       content: "\f015";
     }
+
+    &:hover {
+      background-color: $tight-form-func-bg;
+      @include left-brand-border-gradient();
+    }
+
+    &.selected {
+      background-color: $grafanaListBackground;
+    }
   }
 
   .search-result-tags {
@@ -93,17 +138,15 @@
   }
 }
 
-.search-switches {
-  position: absolute;
-  top: 19px;
-  right: 21px;
-}
 
 .search-button-row {
-  padding-top: 20px;
+  padding: $spacer*2;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
 
   button, a {
-    margin-right: 10px;
+    margin-bottom: $spacer;
   }
 
   .search-button-row-explore-link {

+ 5 - 3
public/sass/components/_sidemenu.scss

@@ -9,7 +9,8 @@
   width: $side-menu-width;
   background-color: rgba($side-menu-bg,$side-menu-opacity);
   z-index: 101;
-  transform: translate3d(0, -100%, 0);
+  //transform: translate3d(0, -100%, 0);
+  opacity: 0;
   visibility: hidden;
 
   a:focus {
@@ -20,8 +21,9 @@
 .sidemenu-open {
   .sidemenu-wrapper {
     visibility: visible;
-    transform: translate3d(0, 0, 0);
-    transition: all 0.2s;
+    //transform: translate3d(0, 0, 0);
+    opacity: 1;
+    transition: opacity 0.3s;
   }
 }
 

+ 1 - 1
public/sass/components/_view_states.scss

@@ -51,7 +51,7 @@
   .navbar-page-btn {
     border-color: transparent;
     background: transparent;
-    transform: translate3d(-50px, 0, 0);
+    transform: translate3d(-95px, 0, 0);
     transition: all 1.5s ease-in-out 1s;
     .icon-gf {
       opacity: 0;