diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts
new file mode 100644
index 00000000000..068c9bec777
--- /dev/null
+++ b/public/app/core/components/grafana_app.ts
@@ -0,0 +1,184 @@
+///
+
+import config from 'app/core/config';
+import store from 'app/core/store';
+import _ from 'lodash';
+import angular from 'angular';
+import $ from 'jquery';
+import coreModule from '../core_module';
+
+export class GrafanaCtrl {
+
+ /** @ngInject */
+ constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv) {
+
+ $scope.init = function() {
+ $scope.contextSrv = contextSrv;
+
+ $scope._ = _;
+
+ $rootScope.profilingEnabled = store.getBool('profilingEnabled');
+ $rootScope.performance = { loadStart: new Date().getTime() };
+ $rootScope.appSubUrl = config.appSubUrl;
+
+ if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
+
+ alertSrv.init();
+ utilSrv.init();
+
+ $scope.dashAlerts = alertSrv;
+ };
+
+ $scope.initDashboard = function(dashboardData, viewScope) {
+ $controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
+ };
+
+ $rootScope.onAppEvent = function(name, callback, localScope) {
+ var unbind = $rootScope.$on(name, callback);
+ var callerScope = this;
+ if (callerScope.$id === 1 && !localScope) {
+ console.log('warning rootScope onAppEvent called without localscope');
+ }
+ if (localScope) {
+ callerScope = localScope;
+ }
+ callerScope.$on('$destroy', unbind);
+ };
+
+ $rootScope.appEvent = function(name, payload) {
+ $rootScope.$emit(name, payload);
+ };
+
+ $rootScope.colors = [
+ "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0",
+ "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477",
+ "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0",
+ "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93",
+ "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7",
+ "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B",
+ "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"
+ ];
+
+ $scope.getTotalWatcherCount = function() {
+ var count = 0;
+ var scopes = 0;
+ var root = $(document.getElementsByTagName('body'));
+
+ var f = function (element) {
+ if (element.data().hasOwnProperty('$scope')) {
+ scopes++;
+ angular.forEach(element.data().$scope.$$watchers, function () {
+ count++;
+ });
+ }
+
+ angular.forEach(element.children(), function (childElement) {
+ f($(childElement));
+ });
+ };
+
+ f(root);
+ $rootScope.performance.scopeCount = scopes;
+ return count;
+ };
+
+ $scope.initProfiling = function() {
+ var count = 0;
+
+ $scope.$watch(function digestCounter() {
+ count++;
+ }, function() {
+ // something
+ });
+
+ $rootScope.performance.panels = [];
+
+ $scope.$on('refresh', function() {
+ if ($rootScope.performance.panels.length > 0) {
+ var totalRender = 0;
+ var totalQuery = 0;
+
+ _.each($rootScope.performance.panels, function(panelTiming: any) {
+ totalRender += panelTiming.render;
+ totalQuery += panelTiming.query;
+ });
+
+ console.log('total query: ' + totalQuery);
+ console.log('total render: ' + totalRender);
+ console.log('avg render: ' + totalRender / $rootScope.performance.panels.length);
+ }
+
+ $rootScope.performance.panels = [];
+ });
+
+ $scope.onAppEvent('dashboard-loaded', function() {
+ count = 0;
+
+ setTimeout(function() {
+ console.log("Dashboard::Performance Total Digests: " + count);
+ console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount());
+ console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount);
+
+ var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart;
+ console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms");
+
+ // measure digest performance
+ var rootDigestStart = window.performance.now();
+ for (var i = 0; i < 30; i++) {
+ $rootScope.$apply();
+ }
+ console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30));
+
+ }, 3000);
+
+ });
+
+ };
+
+ $scope.init();
+ }
+}
+
+export function grafanaAppDirective() {
+ return {
+ restrict: 'E',
+ controller: GrafanaCtrl,
+ link: (scope, elem) => {
+ var ignoreSideMenuHide;
+ // handle sidemenu open state
+ scope.$watch('contextSrv.sidemenu', newVal => {
+ if (newVal !== undefined) {
+ elem.toggleClass('sidemenu-open', scope.contextSrv.sidemenu);
+ }
+ if (scope.contextSrv.sidemenu) {
+ ignoreSideMenuHide = true;
+ setTimeout(() => {
+ ignoreSideMenuHide = false;
+ }, 300);
+ }
+ });
+
+ // handle document clicks that should hide things
+ elem.click(function(evt) {
+ if ($(evt.target).parents().length === 0) {
+ return;
+ }
+
+ // hide search
+ if (elem.find('.search-container').length > 0) {
+ if ($(evt.target).parents('.search-container').length === 0) {
+ scope.appEvent('hide-dash-search');
+ }
+ }
+ // hide sidemenu
+ if (!ignoreSideMenuHide && elem.find('.sidemenu').length > 0) {
+ if ($(evt.target).parents('.sidemenu').length === 0) {
+ scope.$apply(() => scope.contextSrv.toggleSideMenu());
+ }
+ }
+ });
+ }
+ };
+}
+
+coreModule.directive('grafanaApp', grafanaAppDirective);
diff --git a/public/app/core/controllers/all.js b/public/app/core/controllers/all.js
index d22010cffdc..5c4184e609a 100644
--- a/public/app/core/controllers/all.js
+++ b/public/app/core/controllers/all.js
@@ -1,5 +1,4 @@
define([
- './grafana_ctrl',
'./search_ctrl',
'./inspect_ctrl',
'./json_editor_ctrl',
@@ -7,6 +6,5 @@ define([
'./invited_ctrl',
'./signup_ctrl',
'./reset_password_ctrl',
- './sidemenu_ctrl',
'./error_ctrl',
], function () {});
diff --git a/public/app/core/controllers/grafana_ctrl.ts b/public/app/core/controllers/grafana_ctrl.ts
deleted file mode 100644
index 2363ffb94b1..00000000000
--- a/public/app/core/controllers/grafana_ctrl.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-///
-
-import config from 'app/core/config';
-import store from 'app/core/store';
-import _ from 'lodash';
-import angular from 'angular';
-import $ from 'jquery';
-import coreModule from '../core_module';
-
-coreModule.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv) {
-
- $scope.init = function() {
- $scope.contextSrv = contextSrv;
-
- $scope._ = _;
-
- $rootScope.profilingEnabled = store.getBool('profilingEnabled');
- $rootScope.performance = { loadStart: new Date().getTime() };
- $rootScope.appSubUrl = config.appSubUrl;
-
- if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
-
- alertSrv.init();
- utilSrv.init();
-
- $scope.dashAlerts = alertSrv;
- };
-
- $scope.initDashboard = function(dashboardData, viewScope) {
- $controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
- };
-
- $rootScope.onAppEvent = function(name, callback, localScope) {
- var unbind = $rootScope.$on(name, callback);
- var callerScope = this;
- if (callerScope.$id === 1 && !localScope) {
- console.log('warning rootScope onAppEvent called without localscope');
- }
- if (localScope) {
- callerScope = localScope;
- }
- callerScope.$on('$destroy', unbind);
- };
-
- $rootScope.appEvent = function(name, payload) {
- $rootScope.$emit(name, payload);
- };
-
- $rootScope.colors = [
- "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0",
- "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477",
- "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0",
- "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93",
- "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7",
- "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B",
- "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"
- ];
-
- $scope.getTotalWatcherCount = function() {
- var count = 0;
- var scopes = 0;
- var root = $(document.getElementsByTagName('body'));
-
- var f = function (element) {
- if (element.data().hasOwnProperty('$scope')) {
- scopes++;
- angular.forEach(element.data().$scope.$$watchers, function () {
- count++;
- });
- }
-
- angular.forEach(element.children(), function (childElement) {
- f($(childElement));
- });
- };
-
- f(root);
- $rootScope.performance.scopeCount = scopes;
- return count;
- };
-
- $scope.initProfiling = function() {
- var count = 0;
-
- $scope.$watch(function digestCounter() {
- count++;
- }, function() {
- // something
- });
-
- $rootScope.performance.panels = [];
-
- $scope.$on('refresh', function() {
- if ($rootScope.performance.panels.length > 0) {
- var totalRender = 0;
- var totalQuery = 0;
-
- _.each($rootScope.performance.panels, function(panelTiming: any) {
- totalRender += panelTiming.render;
- totalQuery += panelTiming.query;
- });
-
- console.log('total query: ' + totalQuery);
- console.log('total render: ' + totalRender);
- console.log('avg render: ' + totalRender / $rootScope.performance.panels.length);
- }
-
- $rootScope.performance.panels = [];
- });
-
- $scope.onAppEvent('dashboard-loaded', function() {
- count = 0;
-
- setTimeout(function() {
- console.log("Dashboard::Performance Total Digests: " + count);
- console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount());
- console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount);
-
- var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart;
- console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms");
-
- // measure digest performance
- var rootDigestStart = window.performance.now();
- for (var i = 0; i < 30; i++) {
- $rootScope.$apply();
- }
- console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30));
-
- }, 3000);
-
- });
-
- };
-
- $scope.init();
-
-});
-
-var grafanaCtrl = {};
-export default grafanaCtrl;
diff --git a/public/app/core/controllers/sidemenu_ctrl.js b/public/app/core/controllers/sidemenu_ctrl.js
deleted file mode 100644
index 5bca63cb1cd..00000000000
--- a/public/app/core/controllers/sidemenu_ctrl.js
+++ /dev/null
@@ -1,130 +0,0 @@
-define([
- 'angular',
- 'lodash',
- 'jquery',
- '../core_module',
- 'app/core/config',
-],
-function (angular, _, $, coreModule, config) {
- 'use strict';
-
- coreModule.default.controller('SideMenuCtrl', function($scope, $location, contextSrv, backendSrv) {
-
- $scope.getUrl = function(url) {
- return config.appSubUrl + url;
- };
-
- $scope.setupMainNav = function() {
- _.each(config.bootData.mainNavLinks, function(item) {
- $scope.mainLinks.push({
- text: item.text,
- icon: item.icon,
- img: item.img,
- url: $scope.getUrl(item.url)
- });
- });
- };
-
- $scope.openUserDropdown = function() {
- $scope.orgMenu = [
- {section: 'You', cssClass: 'dropdown-menu-title'},
- {text: 'Profile', url: $scope.getUrl('/profile')},
- ];
-
- if (contextSrv.hasRole('Admin')) {
- $scope.orgMenu.push({section: contextSrv.user.orgName, cssClass: 'dropdown-menu-title'});
- $scope.orgMenu.push({
- text: "Settings",
- url: $scope.getUrl("/org"),
- });
- $scope.orgMenu.push({
- text: "Users",
- url: $scope.getUrl("/org/users"),
- });
- $scope.orgMenu.push({
- text: "API Keys",
- url: $scope.getUrl("/org/apikeys"),
- });
- }
-
- $scope.orgMenu.push({cssClass: "divider"});
-
- if (config.allowOrgCreate) {
- $scope.orgMenu.push({text: "New organization", icon: "fa fa-fw fa-plus", url: $scope.getUrl('/org/new')});
- }
-
- backendSrv.get('/api/user/orgs').then(function(orgs) {
- _.each(orgs, function(org) {
- if (org.orgId === contextSrv.user.orgId) {
- return;
- }
-
- $scope.orgMenu.push({
- text: "Switch to " + org.name,
- icon: "fa fa-fw fa-random",
- click: function() {
- $scope.switchOrg(org.orgId);
- }
- });
- });
-
- $scope.orgMenu.push({cssClass: "divider"});
- if (contextSrv.isGrafanaAdmin) {
- $scope.orgMenu.push({text: "Server admin", url: $scope.getUrl("/admin/settings")});
- }
- if (contextSrv.isSignedIn) {
- $scope.orgMenu.push({text: "Sign out", url: $scope.getUrl("/logout"), target: "_self"});
- }
- });
- };
-
- $scope.switchOrg = function(orgId) {
- backendSrv.post('/api/user/using/' + orgId).then(function() {
- window.location.href = $scope.getUrl('/');
- });
- };
-
- $scope.setupAdminNav = function() {
- $scope.systemSection = true;
- $scope.grafanaVersion = config.buildInfo.version;
-
- $scope.mainLinks.push({
- text: "System info",
- icon: "fa fa-fw fa-info",
- href: $scope.getUrl("/admin/settings"),
- });
-
- $scope.mainLinks.push({
- text: "Global Users",
- icon: "fa fa-fw fa-user",
- href: $scope.getUrl("/admin/users"),
- });
-
- $scope.mainLinks.push({
- text: "Global Orgs",
- icon: "fa fa-fw fa-users",
- href: $scope.getUrl("/admin/orgs"),
- });
- };
-
- $scope.updateMenu = function() {
- $scope.systemSection = false;
- $scope.mainLinks = [];
- $scope.orgMenu = [];
-
- var currentPath = $location.path();
- if (currentPath.indexOf('/admin') === 0) {
- $scope.setupAdminNav();
- } else {
- $scope.setupMainNav();
- }
- };
-
- $scope.init = function() {
- $scope.showSignout = contextSrv.isSignedIn && !config['authProxyEnabled'];
- $scope.updateMenu();
- $scope.$on('$routeChangeSuccess', $scope.updateMenu);
- };
- });
-
-});
diff --git a/public/app/core/core.ts b/public/app/core/core.ts
index 6882a87da69..7728ddb7a93 100644
--- a/public/app/core/core.ts
+++ b/public/app/core/core.ts
@@ -21,6 +21,7 @@ import "./directives/give_focus";
import './jquery_extended';
import './partials';
+import {grafanaAppDirective} from './components/grafana_app';
import {arrayJoin} from './directives/array_join';
import 'app/core/controllers/all';
import 'app/core/services/all';
@@ -28,4 +29,4 @@ import 'app/core/routes/all';
import './filters/filters';
import coreModule from './core_module';
-export {arrayJoin, coreModule};
+export {arrayJoin, coreModule, grafanaAppDirective};
diff --git a/public/app/core/directives/topnav.js b/public/app/core/directives/topnav.js
index e8874d38027..60a09583789 100644
--- a/public/app/core/directives/topnav.js
+++ b/public/app/core/directives/topnav.js
@@ -18,7 +18,7 @@ function (coreModule) {
'