Explorar el Código

Merge remote-tracking branch 'origin/pro' into pro

Conflicts:
	src/test/test-main.js
Torkel Ödegaard hace 11 años
padre
commit
b39fbff8fa

+ 2 - 3
src/app/app.js

@@ -48,9 +48,8 @@ function (angular, $, _, appLevelRequire, config) {
     return module;
   };
 
-  app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
-
-    $routeProvider.otherwise({ redirectTo: config.default_route });
+  app.config(function ($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
+    $locationProvider.html5Mode(true);
     // this is how the internet told me to dynamically add modules :/
     register_fns.controller = $controllerProvider.register;
     register_fns.directive  = $compileProvider.directive;

+ 2 - 1
src/app/components/require.config.js

@@ -2,9 +2,10 @@
  * Bootstrap require with the needed config, then load the app.js module.
  */
 require.config({
-  baseUrl: 'public/app',
+  baseUrl: '/public/app',
 
   paths: {
+    app: 'p_app',
     config:                   ['../config', '../config.sample'],
     settings:                 'components/settings',
     kbn:                      'components/kbn',

+ 1 - 1
src/app/controllers/all.js

@@ -1,5 +1,5 @@
 define([
-  './grafanaCtrl',
+  './p_grafanaCtrl',
   './dashboardCtrl',
   './dashboardNavCtrl',
   './row',

+ 126 - 0
src/app/controllers/p_grafanaCtrl.js

@@ -0,0 +1,126 @@
+define([
+  'angular',
+  'config',
+  'lodash',
+  'jquery',
+  'store',
+],
+function (angular, config, _, $, store) {
+  "use strict";
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope) {
+
+    $scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
+    $scope.consoleEnabled = store.getBool('grafanaConsole');
+    $scope.showProSideMenu = store.getBool('grafanaProSideMenu');
+
+    $rootScope.profilingEnabled = store.getBool('profilingEnabled');
+    $rootScope.performance = { loadStart: new Date().getTime() };
+
+    $scope.init = function() {
+      $scope._ = _;
+
+      if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
+
+      $scope.dashAlerts = alertSrv;
+      $scope.grafana = { style: 'dark' };
+
+      $scope.onAppEvent('logged-out', function() {
+        $scope.showProSideMenu = false;
+      });
+
+      $scope.onAppEvent('logged-in', function() {
+        $scope.showProSideMenu = store.getBool('grafanaProSideMenu');
+      });
+    };
+
+    $scope.toggleProSideMenu = function() {
+      $scope.showProSideMenu = !$scope.showProSideMenu;
+      store.set('grafanaProSideMenu', $scope.showProSideMenu);
+    };
+
+    $scope.toggleConsole = function() {
+      $scope.consoleEnabled = !$scope.consoleEnabled;
+      store.set('grafanaConsole', $scope.consoleEnabled);
+    };
+
+    $rootScope.onAppEvent = function(name, callback) {
+      var unbind = $rootScope.$on(name, callback);
+      this.$on('$destroy', unbind);
+    };
+
+    $rootScope.emitAppEvent = function(name, payload) {
+      $rootScope.$emit(name, payload);
+    };
+
+    $rootScope.colors = [
+      "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
+      "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
+      "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
+      "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
+      "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
+      "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
+      "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"  //7
+    ];
+
+    $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() {
+      });
+
+      $scope.onAppEvent('setup-dashboard', 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();
+
+  });
+});

+ 53 - 0
src/app/controllers/p_loginCtrl.js

@@ -0,0 +1,53 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('LoginCtrl', function($scope, $http, $location, $routeParams, alertSrv) {
+    $scope.loginModel = {};
+
+    $scope.init = function() {
+      if ($routeParams.logout) {
+        $scope.logout();
+      }
+    };
+
+    $scope.logout = function() {
+      $http.post('/logout').then(function() {
+
+        alertSrv.set('Logged out!', '', 'success', 3000);
+        $scope.emitAppEvent('logged-out');
+
+      }, function() {
+        alertSrv.set('Logout failed:', 'Unexpected error', 'error', 3000);
+      });
+    };
+
+    $scope.login = function() {
+      delete $scope.loginError;
+
+      if (!$scope.loginForm.$valid) {
+        return;
+      }
+
+      $http.post('/login', $scope.loginModel).then(function() {
+        $scope.emitAppEvent('logged-in');
+        $location.path('/');
+      }, function(err) {
+        if (err.status === 401) {
+          $scope.loginError = "Username or password is incorrect";
+        }
+        else {
+          $scope.loginError = "Unexpected error";
+        }
+      });
+    };
+
+    $scope.init();
+
+  });
+
+});

+ 127 - 0
src/app/p_app.js

@@ -0,0 +1,127 @@
+/**
+ * main app level module
+ */
+define([
+  'angular',
+  'jquery',
+  'lodash',
+  'require',
+  'config',
+  'bootstrap',
+  'angular-route',
+  'angular-strap',
+  'angular-dragdrop',
+  'extend-jquery',
+  'bindonce',
+],
+function (angular, $, _, appLevelRequire, config) {
+
+  "use strict";
+
+  var app = angular.module('grafana', []),
+    // we will keep a reference to each module defined before boot, so that we can
+    // go back and allow it to define new features later. Once we boot, this will be false
+    pre_boot_modules = [],
+    // these are the functions that we need to call to register different
+    // features if we define them after boot time
+    register_fns = {};
+
+  // This stores the grafana version number
+  app.constant('grafanaVersion',"@grafanaVersion@");
+
+  // Use this for cache busting partials
+  app.constant('cacheBust',"cache-bust="+Date.now());
+
+  /**
+   * Tells the application to watch the module, once bootstraping has completed
+   * the modules controller, service, etc. functions will be overwritten to register directly
+   * with this application.
+   * @param  {[type]} module [description]
+   * @return {[type]}        [description]
+   */
+  app.useModule = function (module) {
+    if (pre_boot_modules) {
+      pre_boot_modules.push(module);
+    } else {
+      _.extend(module, register_fns);
+    }
+    return module;
+  };
+
+  app.config(function ($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
+    $locationProvider.html5Mode(true);
+    // this is how the internet told me to dynamically add modules :/
+    register_fns.controller = $controllerProvider.register;
+    register_fns.directive  = $compileProvider.directive;
+    register_fns.factory    = $provide.factory;
+    register_fns.service    = $provide.service;
+    register_fns.filter     = $filterProvider.register;
+  });
+
+  var apps_deps = [
+    'ngRoute',
+    '$strap.directives',
+    'ngDragDrop',
+    'grafana',
+    'pasvaz.bindonce'
+  ];
+
+  var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
+
+  _.each(module_types, function (type) {
+    var module_name = 'grafana.'+type;
+    // create the module
+    app.useModule(angular.module(module_name, []));
+    // push it into the apps dependencies
+    apps_deps.push(module_name);
+  });
+
+  var preBootRequires = [
+    'controllers/all',
+    'directives/all',
+    'filters/all',
+    'components/partials',
+    'routes/p_all',
+  ];
+
+  _.each(config.plugins.dependencies, function(dep) {
+    preBootRequires.push('../plugins/' + dep);
+  });
+
+  app.boot = function() {
+    require(preBootRequires, function () {
+
+      // disable tool tip animation
+      $.fn.tooltip.defaults.animation = false;
+
+      // bootstrap the app
+      angular
+        .element(document)
+        .ready(function() {
+          angular.bootstrap(document, apps_deps)
+            .invoke(['$rootScope', function ($rootScope) {
+              _.each(pre_boot_modules, function (module) {
+                _.extend(module, register_fns);
+              });
+              pre_boot_modules = false;
+
+              $rootScope.requireContext = appLevelRequire;
+              $rootScope.require = function (deps, fn) {
+                var $scope = this;
+                $scope.requireContext(deps, function () {
+                  var deps = _.toArray(arguments);
+                  // Check that this is a valid scope.
+                  if($scope.$id) {
+                    $scope.$apply(function () {
+                      fn.apply($scope, deps);
+                    });
+                  }
+                });
+              };
+            }]);
+        });
+    });
+  };
+
+  return app;
+});

+ 1 - 11
src/app/panels/overview/module.js

@@ -36,19 +36,9 @@ function (angular, app, _, timeSeries) {
     _.defaults($scope.panel, _d);
 
     $scope.init = function() {
-      panelSrv.init(this);
-
-      if (!$scope.skipDataOnInit) {
-        $scope.get_data();
-      }
-      //$scope.$on('refresh', $scope.render);
-      //$scope.render();
     };
 
     $scope.get_data = function() {
-      delete $scope.panel.error;
-      $scope.panelMeta.loading = true;
-
       $scope.rangeUnparsed = $scope.filter.timeRange(false);
 
       var metricsQuery = {
@@ -101,7 +91,7 @@ function (angular, app, _, timeSeries) {
     $scope.openEditor = function() {
     };
 
-    $scope.init();
+    panelSrv.init($scope);
 
   });
 });

+ 1 - 1
src/app/partials/dashboard.html

@@ -1,6 +1,6 @@
 <div ng-controller="DashboardCtrl" body-class ng-class="{'dashboard-fullscreen': dashboardViewState.fullscreen}">
 
-	<div ng-include="'app/partials/dashboard_topnav.html'">
+	<div ng-include="'app/partials/pro/dashboard_topnav.html'">
 	</div>
 
 	<div class="submenu-controls">

+ 14 - 0
src/app/partials/pro/admin_datasources.html

@@ -0,0 +1,14 @@
+<div class="navbar navbar-static-top">
+	<div class="navbar-inner">
+		<div class="container-fluid">
+			<span class="brand">
+				<a ng-click="toggleProSideMenu()">
+					<img src="img/small.png">
+				</a>
+				Admin / Data sources
+			</span>
+		</div>
+	</div>
+</div>
+
+

+ 82 - 0
src/app/partials/pro/dashboard_topnav.html

@@ -0,0 +1,82 @@
+<div class="navbar navbar-static-top">
+	<div class="navbar-inner">
+		<div class="container-fluid">
+			<span class="brand">
+				<a ng-click="toggleProSideMenu()">
+					<img src="img/small.png">
+				</a>
+				{{dashboard.title}}
+			</span>
+			<ul class="nav pull-right" ng-controller='DashboardNavCtrl' ng-init="init()">
+
+				<li ng-show="dashboardViewState.fullscreen">
+					<a ng-click="exitFullscreen()">
+						Back to dashboard
+					</a>
+				</li>
+
+				<li class="grafana-menu-zoom-out">
+					<a class='small' ng-click='zoom(2)'>
+						Zoom Out
+					</a>
+				</li>
+
+				<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
+					<grafana-simple-panel type="pulldown.type" ng-cloak>
+					</grafana-simple-panel>
+				</li>
+
+				<li class="dropdown grafana-menu-save">
+					<a href="#"  bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
+						<i class='icon-save'></i>
+					</a>
+
+					<ul class="save-dashboard-dropdown dropdown-menu" ng-if="saveDropdownOpened">
+						<li>
+							<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
+								<input class='input-medium' ng-model="dashboard.title" type="text" />
+								<button class="btn" ng-click="saveDashboard()"><i class="icon-save"></i></button>
+							</form>
+						</li>
+
+						<li>
+							<a class="link" ng-click="set_default()">Save as Home</a>
+						</li>
+						<li>
+							<a class="link" ng-click="purge_default()">Reset Home</a>
+						</li>
+						<li ng-show="!isFavorite">
+							<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
+						</li>
+						<li ng-show="isFavorite">
+							<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
+						</li>
+						<li>
+							<a class="link" ng-click="exportDashboard()">Export dashboard</a>
+						</li>
+						<li ng-show="db.saveTemp">
+							<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
+								Share temp copy
+							</a>
+						</li>
+					</ul>
+				</li>
+
+				<li class="dropdown grafana-menu-load" ng-controller="SearchCtrl" ng-init="init()" ng-include="'app/partials/search.html'">
+				</li>
+
+				<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='icon-home'></i></a></li>
+
+				<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" config-modal="app/partials/dasheditor.html"><i class='icon-cog pointer'></i></a></li>
+
+				<li class="grafana-menu-stop-playlist hide">
+					<a class='small' ng-click='stopPlaylist(2)'>
+						Stop playlist
+					</a>
+				</li>
+
+			</ul>
+		</div>
+	</div>
+</div>
+

+ 48 - 0
src/app/partials/pro/login.html

@@ -0,0 +1,48 @@
+
+<div class="container">
+
+	<div class="login-box">
+
+		<div class="login-box-logo">
+			<img src="/img/logo_transparent_200x75.png">
+		</div>
+
+		<form name="loginForm" class="form-horizontal">
+			<div class="row-fluid">
+				<div class="span8">
+					<div class="control-group">
+						<label class="control-label" for="inputEmail">Email</label>
+						<div class="controls">
+							<input type="text" required ng-model="loginModel.email" id="inputEmail" placeholder="Email">
+						</div>
+					</div>
+					<div class="control-group">
+						<label class="control-label" for="inputPassword">Password</label>
+						<div class="controls">
+							<input type="password" required ng-model="loginModel.password" id="inputPassword" placeholder="Password">
+						</div>
+					</div>
+					<div class="control-group">
+						<div class="controls">
+							<label class="checkbox">
+								<input type="checkbox" ng-model="loginModel.remember" ng-checked="login.remember"> Remember me
+							</label>
+						</div>
+					</div>
+				</div>
+				<div class="span4">
+					<button type="submit" ng-click="login()" class="btn btn-success btn-large">
+						<i class="icon-lock"></i>
+						Sign in
+					</button>
+				</div>
+			</div>
+			<div class="alert alert-error" ng-show="loginError">
+				<button type="button" class="close" data-dismiss="alert">&times;</button>
+				<strong>Login failed:</strong> {{loginError}}
+			</div>
+		</form>
+	</div>
+
+
+</div>

+ 45 - 0
src/app/partials/pro/sidemenu.html

@@ -0,0 +1,45 @@
+<div class="navbar navbar-static-top">
+	<div class="navbar-inner">
+		<div class="container-fluid">
+			<span class="brand">
+				<i class="icon-gears" style=""></i>
+				<span style="color: white; padding-left: 4px;">Grafana</span>
+				<ul class="nav" ng-controller='DashboardNavCtrl' ng-init="init()">
+			</span>
+			</ul>
+		</div>
+	</div>
+</div>
+
+<section class="pro-sidemenu-items">
+	<a class="pro-sidemenu-link pro-side-menu-user" href="/login?logout">
+		<img  src="https://secure.gravatar.com/avatar/c8656e8972626f01e1703681d5e55f92?s=90&default=blank">
+		logout
+	</a>
+	<a class="pro-sidemenu-link" href="/dashboard/db/home">
+		<i class="icon-th-large"></i>
+		Dashboards
+	</a>
+	<a class="pro-sidemenu-link" href="/charts">
+		<i class="icon-signal"></i>
+		Graphs
+	</a>
+	<a class="pro-sidemenu-link" href="/charts">
+		<i class="icon-bolt" style="padding-right: 23px"></i>
+		 Alerts
+	</a>
+	<a class="pro-sidemenu-link" href="/admin/datasources">
+		<i class="icon-sitemap"></i>
+		Data sources
+	</a>
+	<a class="pro-sidemenu-link" href="/admin">
+		<i class="icon-tasks"></i>
+		Global options
+	</a>
+	<a class="pro-sidemenu-link" href="/admin">
+		<i class="icon-user"></i>
+		User accounts
+	</a>
+</section>
+
+</div>

+ 0 - 0
src/app/partials/solo-panel.html → src/app/partials/pro/solo-panel.html


+ 21 - 0
src/app/routes/p_admin.js

@@ -0,0 +1,21 @@
+define([
+  'angular',
+],
+function (angular) {
+  "use strict";
+
+  var module = angular.module('grafana.routes');
+
+  module.config(function($routeProvider) {
+    $routeProvider
+      .when('/admin/datasources', {
+        templateUrl: 'app/partials/pro/admin_datasources.html',
+        controller : 'AdminCtrl',
+      });
+  });
+
+  module.controller('AdminCtrl', function() {
+
+  });
+
+});

+ 7 - 0
src/app/routes/p_all.js

@@ -0,0 +1,7 @@
+define([
+  './p_dashboard',
+  './p_solo-panel',
+  './p_admin',
+  './p_login',
+],
+function () {});

+ 45 - 0
src/app/routes/p_dashboard.js

@@ -0,0 +1,45 @@
+define([
+  'angular',
+],
+function (angular) {
+  "use strict";
+
+  var module = angular.module('grafana.routes');
+
+  module.config(function($routeProvider) {
+    $routeProvider
+      .when('/', {
+        templateUrl: '/app/partials/dashboard.html',
+        controller : 'DashFromDBProvider',
+        reloadOnSearch: false,
+      })
+      .when('/dashboard/db/:id', {
+        templateUrl: '/app/partials/dashboard.html',
+        controller : 'DashFromDBProvider',
+        reloadOnSearch: false,
+      })
+      .when('/dashboard/temp/:id', {
+        templateUrl: '/app/partials/dashboard.html',
+        controller : 'DashFromDBProvider',
+        reloadOnSearch: false,
+      });
+  });
+
+  module.controller('DashFromDBProvider', function($scope, $rootScope, datasourceSrv, $routeParams, alertSrv) {
+
+    var db = datasourceSrv.getGrafanaDB();
+    var isTemp = window.location.href.indexOf('dashboard/temp') !== -1;
+    if (!$routeParams.id) {
+      $routeParams.id = 'default';
+    }
+
+    db.getDashboard($routeParams.id, isTemp)
+      .then(function(dashboard) {
+        $scope.emitAppEvent('setup-dashboard', dashboard);
+      }).then(null, function(error) {
+        alertSrv.set('Error', error, 'error');
+      });
+
+  });
+
+});

+ 59 - 0
src/app/routes/p_login.js

@@ -0,0 +1,59 @@
+define([
+  'angular',
+],
+function (angular) {
+  "use strict";
+
+  var module = angular.module('grafana.routes');
+
+  module.config(function($routeProvider) {
+    $routeProvider
+      .when('/login', {
+        templateUrl: 'app/partials/pro/login.html',
+        controller : 'LoginCtrl',
+      });
+  });
+
+  module.controller('LoginCtrl', function($scope, $http, $location, $routeParams, alertSrv) {
+    $scope.loginModel = {};
+
+    $scope.init = function() {
+      if ($routeParams.logout) {
+        $scope.logout();
+      }
+    };
+
+    $scope.logout = function() {
+      $http.post('/logout').then(function() {
+        alertSrv.set('Logged out!', '', 'success', 3000);
+        $scope.emitAppEvent('logged-out');
+      }, function() {
+        alertSrv.set('Logout failed:', 'Unexpected error', 'error', 3000);
+      });
+    };
+
+    $scope.login = function() {
+      delete $scope.loginError;
+
+      if (!$scope.loginForm.$valid) {
+        return;
+      }
+
+      $http.post('/login', $scope.loginModel).then(function() {
+        $scope.emitAppEvent('logged-in');
+        $location.path('/');
+      }, function(err) {
+        if (err.status === 401) {
+          $scope.loginError = "Username or password is incorrect";
+        }
+        else {
+          $scope.loginError = "Unexpected error";
+        }
+      });
+    };
+
+    $scope.init();
+
+  });
+
+});

+ 25 - 4
src/app/routes/solo-panel-route.js → src/app/routes/p_solo-panel.js

@@ -1,15 +1,16 @@
 define([
   'angular',
+  'lodash',
 ],
-function (angular) {
+function (angular, _) {
   "use strict";
 
   var module = angular.module('grafana.routes');
 
   module.config(function($routeProvider) {
     $routeProvider
-      .when('/solo-panel/db/:id', {
-        templateUrl: 'app/partials/solo-panel.html',
+      .when('/dashboard/:id/panel/:panelId', {
+        templateUrl: 'app/partials/pro/solo-panel.html',
         controller : 'SoloPanelCtrl',
       });
   });
@@ -17,6 +18,7 @@ function (angular) {
   module.controller('SoloPanelCtrl', function($scope, $rootScope, datasourceSrv, $routeParams, alertSrv, dashboardSrv, filterSrv) {
 
     var db = datasourceSrv.getGrafanaDB();
+    var panelId = parseInt($routeParams.panelId);
 
     db.getDashboard($routeParams.id, false)
       .then(function(dashboardData) {
@@ -33,13 +35,32 @@ function (angular) {
       };
       $scope.test = "Hej";
       $scope.$index = 0;
-      $scope.panel = $scope.dashboard.rows[0].panels[0];
+      $scope.panel = $scope.getPanelById(panelId);
+
       $scope.panel.span = 12;
+      $scope.dashboardViewState = {
+        registerPanel: function() {
+        }
+      };
 
       $scope.filter = filterSrv;
       $scope.filter.init($scope.dashboard);
     };
 
+    $scope.getPanelById = function(id) {
+      var rows = $scope.dashboard.rows;
+      for (var i = 0; i < rows.length; i++) {
+        var row = rows[i];
+        for (var j = 0; j < row.panels.length; j++) {
+          var panel = row.panels[j];
+          if (panel.id === id) {
+            return panel;
+          }
+        }
+      }
+      return null;
+    };
+
   });
 
 });

+ 1 - 1
src/css/less/bootswatch.dark.less

@@ -479,7 +479,7 @@ legend, label {
 .alert-error .alert-heading,
 .alert-info,
 .alert-info .alert-heading {
-  color: @grayLighter;
+  color: @white;
   text-shadow: none;
   border: none;
 }

+ 1 - 0
src/css/less/grafana.less

@@ -2,6 +2,7 @@
 @import "graph.less";
 @import "console.less";
 @import "bootstrap-tagsinput.less";
+@import "p_pro.less";
 
 .hide-controls {
   padding: 0;

+ 72 - 0
src/css/less/p_pro.less

@@ -0,0 +1,72 @@
+.pro-sidemenu {
+  display: none;
+}
+
+.pro-sidemenu-open {
+  .pro-sidemenu {
+    display: block;
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 200px;
+    background: @bodyBackground;
+    border-right: 2px solid black;
+    min-height: 100%;
+    z-index: 101;
+  }
+
+  .dashboard-notice {
+    margin-left: 200px;
+    width: auto;
+  }
+
+  .pro-main-view {
+    padding-left: 200px;
+  }
+
+  .panel-fullscreen {
+    left: 200px;
+  }
+}
+
+.pro-sidemenu-items {
+}
+
+.pro-sidemenu-link {
+  font-size: 1.0rem;
+  padding: 14px 10px 14px 20px;
+  display: block;
+  background: @grafanaPanelBackground;
+  color: @grayLight;
+  i {
+    padding-right: 15px;
+  }
+  border-bottom: 1px solid black;
+}
+
+.pro-sidemenu-link:first-child {
+ // border-top: 1px solid black;
+}
+
+.pro-side-menu-user {
+  padding-left: 5px;
+  img {
+    width: 49px;
+    padding-right: 10px;
+  }
+}
+
+.login-box {
+  width: 700px;
+  margin: 100px auto 0 auto;
+
+  button.btn {
+    margin-top: 19px;
+    padding: 12px 30px;
+  }
+}
+.login-box-logo {
+  text-align: center;
+  padding-bottom: 50px;
+}
+

BIN
src/img/logo_transparent_200x75.png


BIN
src/img/logo_transparent_400x.png