Kaynağa Gözat

Merge remote-tracking branch 'origin/pro'

Torkel Ödegaard 11 yıl önce
ebeveyn
işleme
861e45aedf
100 değiştirilmiş dosya ile 2574 ekleme ve 668 silme
  1. 2 1
      .gitignore
  2. 17 2
      Gruntfile.js
  3. 4 3
      package.json
  4. 13 4
      src/app/app.js
  5. 12 0
      src/app/components/config.js
  6. 2 0
      src/app/components/panelmeta.js
  7. 100 0
      src/app/components/require.backend.js
  8. 1 5
      src/app/components/require.standalone.js
  9. 13 15
      src/app/components/settings.js
  10. 7 1
      src/app/components/store.js
  11. 3 0
      src/app/controllers/all.js
  12. 22 0
      src/app/controllers/errorCtrl.js
  13. 32 13
      src/app/controllers/grafanaCtrl.js
  14. 103 0
      src/app/controllers/loginCtrl.js
  15. 9 24
      src/app/controllers/search.js
  16. 107 0
      src/app/controllers/sidemenuCtrl.js
  17. 1 1
      src/app/directives/all.js
  18. 34 6
      src/app/directives/dashEditLink.js
  19. 0 89
      src/app/directives/grafanaPanel.js
  20. 15 0
      src/app/directives/tip.js
  21. 48 0
      src/app/directives/topnav.js
  22. 29 0
      src/app/features/account/accountCtrl.js
  23. 38 0
      src/app/features/account/accountUsersCtrl.js
  24. 35 0
      src/app/features/account/apiKeysCtrl.js
  25. 88 0
      src/app/features/account/datasourcesCtrl.js
  26. 78 0
      src/app/features/account/importCtrl.js
  27. 35 0
      src/app/features/account/partials/account.html
  28. 55 0
      src/app/features/account/partials/apikeys.html
  29. 119 0
      src/app/features/account/partials/datasources.html
  30. 63 0
      src/app/features/account/partials/import.html
  31. 54 0
      src/app/features/account/partials/users.html
  32. 18 0
      src/app/features/admin/adminCtrl.js
  33. 18 0
      src/app/features/admin/adminSettingsCtrl.js
  34. 25 0
      src/app/features/admin/adminUsersCtrl.js
  35. 9 0
      src/app/features/admin/partials/admin.html
  36. 11 0
      src/app/features/admin/partials/settings.html
  37. 43 0
      src/app/features/admin/partials/users.html
  38. 10 0
      src/app/features/all.js
  39. 12 10
      src/app/features/annotations/partials/editor.html
  40. 0 1
      src/app/features/dashboard/all.js
  41. 11 14
      src/app/features/dashboard/dashboardCtrl.js
  42. 25 18
      src/app/features/dashboard/dashboardNavCtrl.js
  43. 35 0
      src/app/features/dashboard/partials/shareModal.html
  44. 7 6
      src/app/features/dashboard/rowCtrl.js
  45. 10 5
      src/app/features/dashboard/sharePanelCtrl.js
  46. 1 0
      src/app/features/dashboard/unsavedChangesSrv.js
  47. 4 3
      src/app/features/dashboard/viewStateSrv.js
  48. 1 1
      src/app/features/elasticsearch/datasource.js
  49. 78 0
      src/app/features/grafanaDatasource/datasource.js
  50. 17 0
      src/app/features/grafanaDatasource/partials/query.editor.html
  51. 1 1
      src/app/features/influxdb/datasource.js
  52. 6 0
      src/app/features/panel/all.js
  53. 40 0
      src/app/features/panel/panelDirective.js
  54. 0 0
      src/app/features/panel/panelMenu.js
  55. 7 2
      src/app/features/panel/panelSrv.js
  56. 42 0
      src/app/features/panel/partials/panel.html
  57. 0 0
      src/app/features/panel/partials/panelTime.html
  58. 8 0
      src/app/features/panel/partials/soloPanel.html
  59. 70 0
      src/app/features/panel/soloPanelCtrl.js
  60. 127 0
      src/app/features/profile/partials/profile.html
  61. 46 0
      src/app/features/profile/profileCtrl.js
  62. 41 0
      src/app/panels/dashlist/editor.html
  63. 13 0
      src/app/panels/dashlist/module.html
  64. 69 0
      src/app/panels/dashlist/module.js
  65. 2 19
      src/app/panels/graph/module.html
  66. 10 2
      src/app/panels/graph/module.js
  67. 2 24
      src/app/panels/singlestat/module.html
  68. 10 2
      src/app/panels/singlestat/module.js
  69. 3 4
      src/app/panels/text/module.html
  70. 12 3
      src/app/panels/text/module.js
  71. 6 6
      src/app/panels/timepicker/custom.html
  72. 2 0
      src/app/panels/timepicker/editor.html
  73. 1 0
      src/app/panels/timepicker/module.html
  74. 4 4
      src/app/partials/confirm_modal.html
  75. 0 11
      src/app/partials/dashLoaderShare.html
  76. 5 6
      src/app/partials/dashboard.html
  77. 47 55
      src/app/partials/dashboard_topnav.html
  78. 10 8
      src/app/partials/dasheditor.html
  79. 11 8
      src/app/partials/edit_json.html
  80. 11 0
      src/app/partials/error.html
  81. 8 7
      src/app/partials/help_modal.html
  82. 4 8
      src/app/partials/inspector.html
  83. 0 36
      src/app/partials/loadmetrics.html
  84. 103 0
      src/app/partials/login.html
  85. 14 0
      src/app/partials/navbar.html
  86. 0 22
      src/app/partials/paneleditor.html
  87. 12 7
      src/app/partials/playlist.html
  88. 37 39
      src/app/partials/roweditor.html
  89. 7 8
      src/app/partials/search.html
  90. 0 33
      src/app/partials/share-panel.html
  91. 18 0
      src/app/partials/shareDashboard.html
  92. 39 0
      src/app/partials/sidemenu.html
  93. 0 14
      src/app/partials/submenu.html
  94. 77 78
      src/app/partials/templating_editor.html
  95. 17 15
      src/app/partials/unsaved-changes.html
  96. 79 0
      src/app/routes/backend/all.js
  97. 54 0
      src/app/routes/backend/dashboard.js
  98. 24 0
      src/app/routes/standalone/all.js
  99. 0 23
      src/app/routes/standalone/default.js
  100. 1 1
      src/app/routes/standalone/fromDB.js

+ 2 - 1
.gitignore

@@ -1,7 +1,8 @@
 node_modules
 coverage/
 .aws-config.json
-dist
+/dist
+/tmp
 
 # locally required config files
 web.config

+ 17 - 2
Gruntfile.js

@@ -11,6 +11,22 @@ module.exports = function (grunt) {
     docsDir: 'docs/'
   };
 
+  config.mode = grunt.option('mode') || 'standalone';
+  config.modeOptions = {
+    zipSuffix: '',
+    requirejs: {
+      paths: { config: '../config.sample' },
+      excludeConfig: true,
+    }
+  };
+
+  if (config.mode === 'backend') {
+    grunt.log.writeln('Setting backend build mode');
+    config.modeOptions.zipSuffix = '-backend';
+    config.modeOptions.requirejs.paths = {};
+    config.modeOptions.requirejs.excludeConfig = false;
+  }
+
   // load plugins
   require('load-grunt-tasks')(grunt);
 
@@ -34,5 +50,4 @@ module.exports = function (grunt) {
 
   // pass the config to grunt
   grunt.initConfig(config);
-
-};
+};

+ 4 - 3
package.json

@@ -4,7 +4,7 @@
     "company": "Coding Instinct AB"
   },
   "name": "grafana",
-  "version": "1.9.1",
+  "version": "2.0.0-alpha",
   "repository": {
     "type": "git",
     "url": "http://github.com/torkelo/grafana.git"
@@ -16,7 +16,7 @@
     "grunt-angular-templates": "^0.5.5",
     "grunt-cli": "~0.1.13",
     "grunt-contrib-clean": "~0.5.0",
-    "grunt-contrib-compress": "~0.5.2",
+    "grunt-contrib-compress": "~0.13.0",
     "grunt-contrib-concat": "^0.4.0",
     "grunt-contrib-connect": "~0.5.0",
     "grunt-contrib-copy": "~0.5.0",
@@ -33,7 +33,7 @@
     "grunt-ng-annotate": "^0.9.2",
     "grunt-ngmin": "0.0.3",
     "grunt-string-replace": "~0.2.4",
-    "grunt-usemin": "^2.1.1",
+    "grunt-usemin": "3.0.0",
     "jshint-stylish": "~0.1.5",
     "karma": "~0.12.21",
     "karma-chrome-launcher": "~0.1.4",
@@ -65,6 +65,7 @@
   "dependencies": {
     "grunt-jscs": "^0.8.1",
     "karma-sinon": "^1.0.3",
+    "lodash": "^2.4.1",
     "sinon": "^1.10.3"
   }
 }

+ 13 - 4
src/app/app.js

@@ -49,8 +49,7 @@ 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) {
     // this is how the internet told me to dynamically add modules :/
     register_fns.controller = $controllerProvider.register;
     register_fns.directive  = $compileProvider.directive;
@@ -68,7 +67,16 @@ function (angular, $, _, appLevelRequire, config) {
     'pasvaz.bindonce'
   ];
 
-  var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
+  var module_types = ['controllers', 'directives', 'factories', 'services', 'filters'];
+
+  if (window.grafanaBackend) {
+    module_types.push('routes');
+    angular.module('grafana.routes.standalone', []);
+  }
+  else {
+    module_types.push('routes.standalone');
+    angular.module('grafana.routes', []);
+  }
 
   _.each(module_types, function (type) {
     var module_name = 'grafana.'+type;
@@ -85,7 +93,8 @@ function (angular, $, _, appLevelRequire, config) {
     'directives/all',
     'filters/all',
     'components/partials',
-    'routes/standalone/default',
+    'routes/standalone/all',
+    'routes/backend/all',
   ];
 
   _.each(config.plugins.dependencies, function(dep) {

+ 12 - 0
src/app/components/config.js

@@ -0,0 +1,12 @@
+define([
+  'settings',
+],
+function (Settings) {
+  "use strict";
+
+  var bootData = window.grafanaBootData;
+  var options = bootData.settings;
+
+  return new Settings(options);
+
+});

+ 2 - 0
src/app/components/panelmeta.js

@@ -6,6 +6,8 @@ function () {
   function PanelMeta(options) {
     this.description = options.description;
     this.fullscreen = options.fullscreen;
+    this.editIcon = options.editIcon;
+    this.panelName = options.panelName;
     this.menu = [];
     this.editorTabs = [];
     this.extendedMenu = [];

+ 100 - 0
src/app/components/require.backend.js

@@ -0,0 +1,100 @@
+
+require.config({
+  urlArgs: 'bust=' + (new Date().getTime()),
+  baseUrl: 'public/app',
+
+  paths: {
+    config:                   'components/config',
+    settings:                 'components/settings',
+    kbn:                      'components/kbn',
+    store:                    'components/store',
+
+    css:                      '../vendor/require/css',
+    text:                     '../vendor/require/text',
+    moment:                   '../vendor/moment',
+    filesaver:                '../vendor/filesaver',
+    angular:                  '../vendor/angular/angular',
+    'angular-route':          '../vendor/angular/angular-route',
+    'angular-sanitize':       '../vendor/angular/angular-sanitize',
+    'angular-dragdrop':       '../vendor/angular/angular-dragdrop',
+    'angular-strap':          '../vendor/angular/angular-strap',
+    timepicker:               '../vendor/angular/timepicker',
+    datepicker:               '../vendor/angular/datepicker',
+    bindonce:                 '../vendor/angular/bindonce',
+    crypto:                   '../vendor/crypto.min',
+    spectrum:                 '../vendor/spectrum',
+
+    lodash:                   'components/lodash.extended',
+    'lodash-src':             '../vendor/lodash',
+    bootstrap:                '../vendor/bootstrap/bootstrap',
+
+    jquery:                   '../vendor/jquery/jquery-2.1.1.min',
+
+    'extend-jquery':          'components/extend-jquery',
+
+    'jquery.flot':            '../vendor/jquery/jquery.flot',
+    'jquery.flot.pie':        '../vendor/jquery/jquery.flot.pie',
+    'jquery.flot.events':     '../vendor/jquery/jquery.flot.events',
+    'jquery.flot.selection':  '../vendor/jquery/jquery.flot.selection',
+    'jquery.flot.stack':      '../vendor/jquery/jquery.flot.stack',
+    'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
+    'jquery.flot.time':       '../vendor/jquery/jquery.flot.time',
+    'jquery.flot.crosshair':  '../vendor/jquery/jquery.flot.crosshair',
+    'jquery.flot.fillbelow':  '../vendor/jquery/jquery.flot.fillbelow',
+
+    modernizr:                '../vendor/modernizr-2.6.1',
+
+    'bootstrap-tagsinput':    '../vendor/tagsinput/bootstrap-tagsinput',
+  },
+  shim: {
+
+    spectrum: {
+      deps: ['jquery']
+    },
+
+    crypto: {
+      exports: 'Crypto'
+    },
+
+    angular: {
+      deps: ['jquery','config'],
+      exports: 'angular'
+    },
+
+    bootstrap: {
+      deps: ['jquery']
+    },
+
+    modernizr: {
+      exports: 'Modernizr'
+    },
+
+    jquery: {
+      exports: 'jQuery'
+    },
+
+    // simple dependency declaration
+    //
+    'jquery.flot':          ['jquery'],
+    'jquery.flot.pie':      ['jquery', 'jquery.flot'],
+    'jquery.flot.events':   ['jquery', 'jquery.flot'],
+    'jquery.flot.selection':['jquery', 'jquery.flot'],
+    'jquery.flot.stack':    ['jquery', 'jquery.flot'],
+    'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
+    'jquery.flot.time':     ['jquery', 'jquery.flot'],
+    'jquery.flot.crosshair':['jquery', 'jquery.flot'],
+    'jquery.flot.fillbelow':['jquery', 'jquery.flot'],
+    'angular-dragdrop':     ['jquery', 'angular'],
+    'angular-mocks':        ['angular'],
+    'angular-sanitize':     ['angular'],
+    'angular-route':        ['angular'],
+    'angular-strap':        ['angular', 'bootstrap','timepicker', 'datepicker'],
+    'bindonce':             ['angular'],
+
+    timepicker:             ['jquery', 'bootstrap'],
+    datepicker:             ['jquery', 'bootstrap'],
+
+    'bootstrap-tagsinput':          ['jquery'],
+  },
+  waitSeconds: 60,
+});

+ 1 - 5
src/app/components/require.config.js → src/app/components/require.standalone.js

@@ -1,9 +1,6 @@
-/**
- * Bootstrap require with the needed config, then load the app.js module.
- */
 require.config({
-  baseUrl: 'app',
   urlArgs: 'bust=' + (new Date().getTime()),
+  baseUrl: 'app',
 
   paths: {
     config:                   ['../config', '../config.sample'],
@@ -47,7 +44,6 @@ require.config({
     modernizr:                '../vendor/modernizr-2.6.1',
 
     'bootstrap-tagsinput':    '../vendor/tagsinput/bootstrap-tagsinput',
-
   },
   shim: {
 

+ 13 - 15
src/app/components/settings.js

@@ -16,24 +16,22 @@ function (_, crypto) {
       datasources                   : {},
       window_title_prefix           : 'Grafana - ',
       panels                        : {
-        'graph': { path: 'panels/graph' },
-        'singlestat': { path: 'panels/singlestat' },
-        'text': { path: 'panels/text' }
+        'graph':      { path: 'panels/graph',      name: 'Graph' },
+        'singlestat': { path: 'panels/singlestat', name: 'Single stat' },
+        'text':       { path: 'panels/text',       name: 'Text' },
+        'dashlist':   { path: 'panels/dashlist',   name: 'Dashboard list' },
       },
-      plugins                       : {},
-      default_route                 : '/dashboard/file/default.json',
-      playlist_timespan             : "1m",
-      unsaved_changes_warning       : true,
-      search                        : { max_results: 100 },
-      admin                         : {}
+      new_panel_title: 'no title (click here)',
+      plugins: {},
+      default_route: '/dashboard/file/default.json',
+      playlist_timespan: "1m",
+      unsaved_changes_warning: true,
+      search: { max_results: 100 },
+      admin: {},
+      appSubUrl: ""
     };
 
-    // This initializes a new hash on purpose, to avoid adding parameters to
-    // config.js without providing sane defaults
-    var settings = {};
-    _.each(defaults, function(value, key) {
-      settings[key] = typeof options[key] !== 'undefined' ? options[key]  : defaults[key];
-    });
+    var settings = _.extend({}, defaults, options);
 
     var parseBasicAuth = function(datasource) {
       var passwordEnd = datasource.url.indexOf('@');

+ 7 - 1
src/app/components/store.js

@@ -8,9 +8,15 @@ define([], function() {
     set: function(key, value) {
       window.localStorage[key] = value;
     },
-    getBool: function(key) {
+    getBool: function(key, def) {
+      if (def !== void 0 && !this.exists(key)) {
+        return def;
+      }
       return window.localStorage[key] === 'true' ? true : false;
     },
+    exists: function(key) {
+      return window.localStorage[key] !== void 0;
+    },
     delete: function(key) {
       window.localStorage.removeItem(key);
     }

+ 3 - 0
src/app/controllers/all.js

@@ -6,4 +6,7 @@ define([
   './graphiteImport',
   './inspectCtrl',
   './jsonEditorCtrl',
+  './loginCtrl',
+  './sidemenuCtrl',
+  './errorCtrl',
 ], function () {});

+ 22 - 0
src/app/controllers/errorCtrl.js

@@ -0,0 +1,22 @@
+define([
+  'angular',
+  'app',
+  'lodash'
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('ErrorCtrl', function($scope) {
+
+    var showSideMenu = $scope.grafana.sidemenu;
+    $scope.grafana.sidemenu = false;
+
+    $scope.$on('$destroy', function() {
+      $scope.grafana.sidemenu = showSideMenu;
+    });
+
+  });
+
+});

+ 32 - 13
src/app/controllers/grafanaCtrl.js

@@ -10,32 +10,47 @@ function (angular, config, _, $, store) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller) {
-
-    $scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
-    $scope._ = _;
-    $rootScope.profilingEnabled = store.getBool('profilingEnabled');
-    $rootScope.performance = { loadStart: new Date().getTime() };
+  module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller, userSrv, $timeout) {
 
     $scope.init = function() {
+      $scope.grafana = {};
+      $scope.grafana.version = grafanaVersion;
+      $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.grafana = { style: 'dark' };
-    };
-
-    $scope.toggleConsole = function() {
-      $scope.consoleEnabled = !$scope.consoleEnabled;
-      store.set('grafanaConsole', $scope.consoleEnabled);
+      $scope.grafana.lightTheme = false;
+      $scope.grafana.user = userSrv.getSignedInUser();
+      $scope.grafana.sidemenu = store.getBool('grafana.sidemenu');
+      $scope.topnav = { title: 'Grafana' };
+
+      $scope.onAppEvent('logged-out', function() {
+        $scope.grafana.sidemenu = false;
+        $scope.grafana.user = {};
+      });
     };
 
     $scope.initDashboard = function(dashboardData, viewScope) {
       $controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
     };
 
+    $scope.toggleSideMenu = function() {
+      $scope.grafana.sidemenu = !$scope.grafana.sidemenu;
+      store.set('grafana.sidemenu', $scope.grafana.sidemenu);
+
+      $timeout(function() {
+        $scope.$broadcast("render");
+      }, 50);
+    };
+
     $rootScope.onAppEvent = function(name, callback) {
       var unbind = $rootScope.$on(name, callback);
       this.$on('$destroy', unbind);
@@ -81,7 +96,11 @@ function (angular, config, _, $, store) {
     $scope.initProfiling = function() {
       var count = 0;
 
-      $scope.$watch(function digestCounter() { count++; }, function() { });
+      $scope.$watch(function digestCounter() {
+        count++;
+      }, function() {
+      });
+
       $scope.onAppEvent('dashboard-loaded', function() {
         count = 0;
 

+ 103 - 0
src/app/controllers/loginCtrl.js

@@ -0,0 +1,103 @@
+define([
+  'angular',
+  'config',
+],
+function (angular, config) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('LoginCtrl', function($scope, backendSrv) {
+    $scope.formModel = {
+      user: '',
+      email: '',
+      password: '',
+    };
+
+    $scope.grafana.sidemenu = false;
+
+    $scope.googleAuthEnabled = config.googleAuthEnabled;
+    $scope.githubAuthEnabled = config.githubAuthEnabled;
+    $scope.disableUserSignUp = config.disableUserSignUp;
+
+    $scope.loginMode = true;
+    $scope.submitBtnClass = 'btn-inverse';
+    $scope.submitBtnText = 'Log in';
+    $scope.strengthClass = '';
+
+    $scope.init = function() {
+      $scope.$watch("loginMode", $scope.loginModeChanged);
+      $scope.passwordChanged();
+    };
+
+    // build info view model
+    $scope.buildInfo = {
+      version: config.buildInfo.version,
+      commit: config.buildInfo.commit,
+      buildstamp: new Date(config.buildInfo.buildstamp * 1000)
+    };
+
+    $scope.submit = function() {
+      if ($scope.loginMode) {
+        $scope.login();
+      } else {
+        $scope.signUp();
+      }
+    };
+
+    $scope.loginModeChanged = function(newValue) {
+      $scope.submitBtnText = newValue ? 'Log in' : 'Sign up';
+    };
+
+    $scope.passwordChanged = function(newValue) {
+      if (!newValue) {
+        $scope.strengthText = "";
+        $scope.strengthClass = "hidden";
+        return;
+      }
+      if (newValue.length < 4) {
+        $scope.strengthText = "strength: weak sauce.";
+        $scope.strengthClass = "password-strength-bad";
+        return;
+      }
+      if (newValue.length <= 6) {
+        $scope.strengthText = "strength: you can do better.";
+        $scope.strengthClass = "password-strength-ok";
+        return;
+      }
+
+      $scope.strengthText = "strength: strong like a bull.";
+      $scope.strengthClass = "password-strength-good";
+    };
+
+    $scope.signUp = function() {
+      if (!$scope.loginForm.$valid) {
+        return;
+      }
+
+      backendSrv.post('/api/user/signup', $scope.formModel).then(function() {
+        window.location.href = config.appSubUrl + '/';
+      });
+    };
+
+    $scope.login = function() {
+      delete $scope.loginError;
+
+      if (!$scope.loginForm.$valid) {
+        return;
+      }
+
+      backendSrv.post('/login', $scope.formModel).then(function(result) {
+        if (result.redirectUrl) {
+          window.location.href = result.redirectUrl;
+        } else {
+          window.location.href = config.appSubUrl + '/';
+        }
+      });
+    };
+
+    $scope.init();
+
+  });
+
+});

+ 9 - 24
src/app/controllers/search.js

@@ -64,33 +64,18 @@ function (angular, _, config, $) {
       $scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
     };
 
-    $scope.goToDashboard = function(id) {
+    $scope.goToDashboard = function(slug) {
       $location.search({});
-      $location.path("/dashboard/db/" + id);
-    };
-
-    $scope.shareDashboard = function(title, id, $event) {
-      $event.stopPropagation();
-      var baseUrl = window.location.href.replace(window.location.hash,'');
-
-      $scope.share = {
-        title: title,
-        url: baseUrl + '#dashboard/db/' + encodeURIComponent(id)
-      };
+      $location.path("/dashboard/db/" + slug);
     };
 
     $scope.searchDashboards = function(queryString) {
-      // bookeeping for determining stale search requests
-      var searchId = $scope.currentSearchId + 1;
-      $scope.currentSearchId = searchId > $scope.currentSearchId ? searchId : $scope.currentSearchId;
+      $scope.currentSearchId = $scope.currentSearchId + 1;
+      var localSearchId = $scope.currentSearchId;
 
       return $scope.db.searchDashboards(queryString)
         .then(function(results) {
-          // since searches are async, it's possible that these results are not for the latest search. throw
-          // them away if so
-          if (searchId < $scope.currentSearchId) {
-            return;
-          }
+          if (localSearchId < $scope.currentSearchId) { return; }
 
           $scope.tagsOnly = results.tagsOnly;
           $scope.results.dashboards = results.dashboards;
@@ -125,11 +110,11 @@ function (angular, _, config, $) {
 
     $scope.deleteDashboard = function(dash, evt) {
       evt.stopPropagation();
-      $scope.appEvent('delete-dashboard', { id: dash.id, title: dash.title });
+      $scope.appEvent('delete-dashboard', { slug: dash.slug, title: dash.title });
     };
 
-    $scope.dashboardDeleted = function(evt, id) {
-      var dash = _.findWhere($scope.results.dashboards, {id: id});
+    $scope.dashboardDeleted = function(evt, payload) {
+      var dash = _.findWhere($scope.results.dashboards, { slug: payload.slug });
       $scope.results.dashboards = _.without($scope.results.dashboards, dash);
     };
 
@@ -154,7 +139,7 @@ function (angular, _, config, $) {
     };
 
     $scope.newDashboard = function() {
-      $location.url('/dashboard/file/empty.json');
+      $location.url('dashboard/new');
     };
 
   });

+ 107 - 0
src/app/controllers/sidemenuCtrl.js

@@ -0,0 +1,107 @@
+define([
+  'angular',
+  'lodash',
+  'jquery',
+  'config',
+],
+function (angular, _, $, config) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('SideMenuCtrl', function($scope, $location) {
+
+    $scope.getUrl = function(url) {
+      return config.appSubUrl + url;
+    };
+
+    $scope.menu = [];
+    $scope.menu.push({
+      text: "Dashbords",
+      icon: "fa fa-th-large",
+      href: $scope.getUrl("/"),
+      //startsWith: config.appSubUrl + '/dashboard/',
+    });
+
+    if ($scope.grafana.user.accountRole === 'Admin') {
+      $scope.menu.push({
+        text: "Data Sources",
+        icon: "fa fa-database",
+        href: $scope.getUrl("/account/datasources"),
+      });
+      $scope.menu.push({
+        text: "Account", href: $scope.getUrl("/account"),
+        requireRole: "Admin",
+        icon: "fa fa-shield",
+      });
+      $scope.menu.push({
+        text: "Users", href: $scope.getUrl("/account/users"),
+        requireRole: "Admin",
+        icon: "fa fa-users",
+      });
+      $scope.menu.push({
+        text: "API Keys", href: $scope.getUrl("/account/apikeys"),
+        requireRole: "Admin",
+        icon: "fa fa-key",
+      });
+    }
+
+    if ($scope.grafana.user.isGrafanaAdmin) {
+      $scope.menu.push({
+        text: "Admin", href: $scope.getUrl("/admin/users"),
+        icon: "fa fa-cube",
+        requireSignedIn: true,
+        links: [
+          { text: 'Settings', href: $scope.getUrl("/admin/settings")},
+          { text: 'Users',    href: $scope.getUrl("/admin/users"), icon: "fa fa-lock" },
+          { text: 'Log',      href: "", icon: "fa fa-lock" },
+        ]
+      });
+    }
+
+    $scope.updateState = function() {
+      var currentPath = config.appSubUrl + $location.path();
+      var search = $location.search();
+
+      _.each($scope.menu, function(item) {
+        item.active = false;
+
+        if (item.href === currentPath) {
+          item.active = true;
+        }
+
+        if (item.startsWith) {
+          if (currentPath.indexOf(item.startsWith) === 0) {
+            item.active = true;
+            item.href = currentPath;
+          }
+        }
+
+        _.each(item.links, function(link) {
+          link.active = false;
+
+          if (link.editview) {
+            var params = {};
+            _.each(search, function(value, key) {
+              if (value !== null) { params[key] = value; }
+            });
+
+            params.editview = link.editview;
+            link.href = currentPath + '?' + $.param(params);
+          }
+
+          if (link.href === currentPath) {
+            item.active = true;
+            link.active = true;
+          }
+        });
+      });
+
+    };
+
+    $scope.init = function() {
+      $scope.updateState();
+    };
+  });
+
+});

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

@@ -1,7 +1,6 @@
 define([
   './arrayJoin',
   './dashUpload',
-  './grafanaPanel',
   './grafanaSimplePanel',
   './ngBlur',
   './dashEditLink',
@@ -16,4 +15,5 @@ define([
   './graphiteSegment',
   './grafanaVersionCheck',
   './dropdown.typeahead',
+  './topnav',
 ], function () {});

+ 34 - 6
src/app/directives/dashEditLink.js

@@ -5,6 +5,12 @@ define([
 function (angular, $) {
   'use strict';
 
+  var editViewMap = {
+    'settings':    { src: 'app/partials/dasheditor.html', title: "Settings" },
+    'annotations': { src: 'app/features/annotations/partials/editor.html', title: "Annotations" },
+    'templating':  { src: 'app/partials/templating_editor.html', title: "Templating" }
+  };
+
   angular
     .module('grafana.directives')
     .directive('dashEditorLink', function($timeout) {
@@ -25,7 +31,7 @@ function (angular, $) {
 
   angular
     .module('grafana.directives')
-    .directive('dashEditorView', function($compile) {
+    .directive('dashEditorView', function($compile, $location) {
       return {
         restrict: 'A',
         link: function(scope, elem) {
@@ -48,10 +54,12 @@ function (angular, $) {
             if (editorScope) { editorScope.dismiss(); }
           }
 
-          scope.$on("$destroy", hideEditorPane);
-          scope.onAppEvent('hide-dash-editor', hideEditorPane);
+          function showEditorPane(evt, payload, editview) {
+            if (editview) {
+              scope.grafana.editview = editViewMap[editview];
+              payload.src = scope.grafana.editview.src;
+            }
 
-          scope.onAppEvent('show-dash-editor', function(evt, payload) {
             if (lastEditor === payload.src) {
               hideEditorPane();
               return;
@@ -65,23 +73,43 @@ function (angular, $) {
             editorScope = payload.scope ? payload.scope.$new() : scope.$new();
 
             editorScope.dismiss = function() {
-              console.log('dismiss: ');
               editorScope.$destroy();
               elem.empty();
               lastEditor = null;
               editorScope = null;
               hideScrollbars(false);
+
+              if (editview) {
+                var urlParams = $location.search();
+                if (editview === urlParams.editview) {
+                  delete urlParams.editview;
+                  $location.search(urlParams);
+                }
+              }
             };
 
             // hide page scrollbars while edit pane is visible
             hideScrollbars(true);
 
             var src = "'" + payload.src + "'";
-            var view = $('<div class="dashboard-edit-view" ng-include="' + src + '"></div>');
+            var view = $('<div class="gf-box" ng-include="' + src + '"></div>');
             elem.append(view);
             $compile(elem.contents())(editorScope);
+          }
+
+          scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) {
+            if (newValue) {
+              showEditorPane(null, {}, newValue);
+            } else if (oldValue) {
+              scope.grafana.editview = null;
+              hideEditorPane();
+            }
           });
 
+          scope.grafana.editview = null;
+          scope.$on("$destroy", hideEditorPane);
+          scope.onAppEvent('hide-dash-editor', hideEditorPane);
+          scope.onAppEvent('show-dash-editor', showEditorPane);
         }
       };
     });

+ 0 - 89
src/app/directives/grafanaPanel.js

@@ -1,89 +0,0 @@
-define([
-  'angular',
-  'jquery',
-  'config',
-  './panelMenu',
-],
-function (angular, $, config) {
-  'use strict';
-
-  angular
-    .module('grafana.directives')
-    .directive('grafanaPanel', function($compile, $parse) {
-
-      var container = '<div class="panel-container"></div>';
-      var content = '<div class="panel-content"></div>';
-
-      var panelHeader =
-      '<div class="panel-header">'+
-          '<span class="alert-error panel-error small pointer"' +
-                'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
-            '<span data-placement="top" bs-tooltip="panelMeta.error">' +
-            '<i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span>' +
-            '</span>' +
-          '</span>' +
-
-          '<span class="panel-loading" ng-show="panelMeta.loading">' +
-            '<i class="fa fa-spinner fa-spin"></i>' +
-          '</span>' +
-
-          '<div class="panel-title-container drag-handle" panel-menu></div>' +
-        '</div>'+
-      '</div>';
-
-      return {
-        restrict: 'E',
-        link: function($scope, elem, attr) {
-          var getter = $parse(attr.type), panelType = getter($scope);
-          var newScope = $scope.$new();
-
-          $scope.kbnJqUiDraggableOptions = {
-            revert: 'invalid',
-            helper: function() {
-              return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
-            },
-            placeholder: 'keep'
-          };
-
-          // compile the module and uncloack. We're done
-          function loadModule($module) {
-            $module.appendTo(elem);
-            elem.wrap(container);
-            /* jshint indent:false */
-            $compile(elem.contents())(newScope);
-            elem.removeClass("ng-cloak");
-
-            var panelCtrlElem = $(elem.children()[0]);
-            var panelCtrlScope = panelCtrlElem.data().$scope;
-
-            panelCtrlScope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
-              panelCtrlElem.css({ minHeight: panelCtrlScope.panel.height || panelCtrlScope.row.height });
-              panelCtrlElem.toggleClass('panel-fullscreen', panelCtrlScope.fullscreen ? true : false);
-            });
-          }
-
-          newScope.$on('$destroy',function() {
-            elem.unbind();
-            elem.remove();
-          });
-
-          elem.addClass('ng-cloak');
-
-          var panelPath = config.panels[panelType].path;
-
-          $scope.require([
-            'jquery',
-            'text!'+panelPath+'/module.html',
-            panelPath + "/module",
-          ], function ($, moduleTemplate) {
-            var $module = $(moduleTemplate);
-            $module.prepend(panelHeader);
-            $module.first().find('.panel-header').nextAll().wrapAll(content);
-            loadModule($module);
-          });
-
-        }
-      };
-    });
-
-});

+ 15 - 0
src/app/directives/tip.js

@@ -18,6 +18,21 @@ function (angular, kbn) {
       };
     });
 
+  angular
+    .module('grafana.directives')
+    .directive('watchChange', function() {
+      return {
+        scope: { onchange: '&watchChange' },
+        link: function(scope, element) {
+          element.on('input', function() {
+            scope.$apply(function () {
+              scope.onchange({ inputValue: element.val() });
+            });
+          });
+        }
+      };
+    });
+
   angular
     .module('grafana.directives')
     .directive('editorOptBool', function($compile) {

+ 48 - 0
src/app/directives/topnav.js

@@ -0,0 +1,48 @@
+define([
+  'angular',
+  'kbn'
+],
+function (angular) {
+  'use strict';
+
+  angular
+    .module('grafana.directives')
+    .directive('topnav', function() {
+      return {
+        restrict: 'E',
+        transclude: true,
+        scope: {
+          title: "@",
+          section: "@",
+          titleAction: "&",
+          toggle: "&",
+          showMenuBtn: "=",
+        },
+        template:
+          '<div class="navbar navbar-static-top"><div class="navbar-inner"><div class="container-fluid">' +
+          '<div class="top-nav">' +
+            '<a class="top-nav-menu-btn pointer" ng-if="showMenuBtn" ng-click="toggle()">' +
+              '<img class="logo-icon" src="img/fav32.png"></img> ' +
+              '<i class="fa fa-angle-right"></i>' +
+            '</a>' +
+
+            '<span class="top-nav-breadcrumb">' +
+              '<i class="top-nav-icon" ng-class="icon"></i>' +
+            '</span>' +
+
+           '<span class="top-nav-section" ng-show="section">' +
+              '{{section}}' +
+              '<i class="fa fa-angle-right"></i>' +
+           '</span>' +
+
+            '<a ng-click="titleAction()" class="top-nav-title">' +
+              '{{title}}' +
+            '</a>' +
+          '</div><div ng-transclude></div></div></div></div>',
+        link: function(scope, elem, attrs) {
+          scope.icon = attrs.icon;
+        }
+      };
+    });
+
+});

+ 29 - 0
src/app/features/account/accountCtrl.js

@@ -0,0 +1,29 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('AccountCtrl', function($scope, $http, backendSrv) {
+
+    $scope.init = function() {
+      $scope.getAccount();
+    };
+
+    $scope.getAccount = function() {
+      backendSrv.get('/api/account').then(function(account) {
+        $scope.account = account;
+      });
+    };
+
+    $scope.update = function() {
+      if (!$scope.accountForm.$valid) { return; }
+      backendSrv.put('/api/account', $scope.account).then($scope.getAccount);
+    };
+
+    $scope.init();
+
+  });
+});

+ 38 - 0
src/app/features/account/accountUsersCtrl.js

@@ -0,0 +1,38 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('AccountUsersCtrl', function($scope, $http, backendSrv) {
+
+    $scope.user = {
+      loginOrEmail: '',
+      role: 'Viewer',
+    };
+
+    $scope.init = function() {
+      $scope.get();
+    };
+
+    $scope.get = function() {
+      backendSrv.get('/api/account/users').then(function(users) {
+        $scope.users = users;
+      });
+    };
+
+    $scope.removeUser = function(user) {
+      backendSrv.delete('/api/account/users/' + user.userId).then($scope.get);
+    };
+
+    $scope.addUser = function() {
+      if (!$scope.form.$valid) { return; }
+      backendSrv.post('/api/account/users', $scope.user).then($scope.get);
+    };
+
+    $scope.init();
+
+  });
+});

+ 35 - 0
src/app/features/account/apiKeysCtrl.js

@@ -0,0 +1,35 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('ApiKeysCtrl', function($scope, $http, backendSrv) {
+
+    $scope.roleTypes = ['Viewer', 'Editor', 'Admin'];
+    $scope.token = { role: 'Viewer' };
+
+    $scope.init = function() {
+      $scope.getTokens();
+    };
+
+    $scope.getTokens = function() {
+      backendSrv.get('/api/auth/keys').then(function(tokens) {
+        $scope.tokens = tokens;
+      });
+    };
+
+    $scope.removeToken = function(id) {
+      backendSrv.delete('/api/auth/keys/'+id).then($scope.getTokens);
+    };
+
+    $scope.addToken = function() {
+      backendSrv.post('/api/auth/keys', $scope.token).then($scope.getTokens);
+    };
+
+    $scope.init();
+
+  });
+});

+ 88 - 0
src/app/features/account/datasourcesCtrl.js

@@ -0,0 +1,88 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('DataSourcesCtrl', function($scope, $http, backendSrv) {
+
+    var defaults = {
+      name: '',
+      type: 'graphite',
+      url: '',
+      access: 'proxy'
+    };
+
+    $scope.types = [
+      { name: 'Graphite', type: 'graphite' },
+      { name: 'InfluxDB', type: 'influxdb' },
+      { name: 'Elasticsearch', type: 'elasticsearch' },
+      { name: 'OpenTSDB', type: 'opentsdb' },
+    ];
+
+    $scope.init = function() {
+      $scope.reset();
+      $scope.editor = {index: 0};
+      $scope.datasources = [];
+      $scope.getDatasources();
+
+      $scope.$watch('editor.index', function(newVal) {
+        if (newVal !== 2) {
+          $scope.reset();
+        }
+      });
+    };
+
+    $scope.reset = function() {
+      $scope.current = angular.copy(defaults);
+      $scope.currentIsNew = true;
+    };
+
+    $scope.edit = function(ds) {
+      $scope.current = ds;
+      $scope.currentIsNew = false;
+      $scope.editor.index = 2;
+    };
+
+    $scope.cancel = function() {
+      $scope.reset();
+      $scope.editor.index = 0;
+    };
+
+    $scope.getDatasources = function() {
+      backendSrv.get('/api/datasources').then(function(results) {
+        $scope.datasources = results;
+      });
+    };
+
+    $scope.remove = function(ds) {
+      backendSrv.delete('/api/datasources/' + ds.id).then(function() {
+        $scope.getDatasources();
+      });
+    };
+
+    $scope.update = function() {
+      backendSrv.post('/api/datasources', $scope.current).then(function() {
+        $scope.editor.index = 0;
+        $scope.getDatasources();
+      });
+    };
+
+    $scope.add = function() {
+      if (!$scope.editForm.$valid) {
+        return;
+      }
+
+      backendSrv.put('/api/datasources', $scope.current)
+        .then(function() {
+          $scope.editor.index = 0;
+          $scope.getDatasources();
+        });
+    };
+
+    $scope.init();
+
+  });
+});

+ 78 - 0
src/app/features/account/importCtrl.js

@@ -0,0 +1,78 @@
+define([
+  'angular',
+  'lodash',
+],
+function (angular, _) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('ImportCtrl', function($scope, $http, backendSrv, datasourceSrv) {
+
+    $scope.init = function() {
+      $scope.datasources = [];
+      $scope.sourceName = 'grafana';
+      $scope.destName = 'grafana';
+      $scope.imported = [];
+      $scope.dashboards = [];
+      $scope.infoText = '';
+      $scope.importing = false;
+
+      _.each(datasourceSrv.getAll(), function(ds) {
+        if (ds.type === 'influxdb' || ds.type === 'elasticsearch') {
+          $scope.sourceName = ds.name;
+          $scope.datasources.push(ds.name);
+        } else if (ds.type === 'grafana') {
+          $scope.datasources.push(ds.name);
+        }
+      });
+    };
+
+    $scope.startImport = function() {
+      $scope.sourceDs = datasourceSrv.get($scope.sourceName);
+      $scope.destDs = datasourceSrv.get($scope.destName);
+
+      $scope.sourceDs.searchDashboards('title:').then(function(results) {
+        $scope.dashboards = results.dashboards;
+
+        if ($scope.dashboards.length === 0) {
+          $scope.infoText = 'No dashboards found';
+          return;
+        }
+
+        $scope.importing = true;
+        $scope.imported = [];
+        $scope.next();
+      });
+    };
+
+    $scope.next = function() {
+      if ($scope.dashboards.length === 0) {
+        $scope.infoText = "Done! Imported " + $scope.imported.length + " dashboards";
+      }
+
+      var dash = $scope.dashboards.shift();
+      if (!dash.title) {
+        console.log(dash);
+        return;
+      }
+
+      var infoObj = {name: dash.title, info: 'Importing...'};
+      $scope.imported.push(infoObj);
+      $scope.infoText = "Importing " + $scope.imported.length + '/' + ($scope.imported.length + $scope.dashboards.length);
+
+      $scope.sourceDs.getDashboard(dash.id).then(function(loadedDash) {
+        $scope.destDs.saveDashboard(loadedDash).then(function() {
+          infoObj.info = "Done!";
+          $scope.next();
+        }, function(err) {
+          infoObj.info = "Error: " + err;
+          $scope.next();
+        });
+      });
+    };
+
+    $scope.init();
+
+  });
+});

+ 35 - 0
src/app/features/account/partials/account.html

@@ -0,0 +1,35 @@
+<topnav toggle="toggleSideMenu()" icon="fa fa-shield" section="Account" show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+<div class="gf-box" style="min-height: 500px">
+
+	<div class="gf-box-body">
+		<div class="row editor-row">
+			<div class="section">
+				<form name="accountForm">
+					<div>
+						<div class="tight-form">
+							<ul class="tight-form-list">
+								<li class="tight-form-item" style="width: 120px">
+									<strong>Account name</strong>
+								</li>
+								<li>
+									<input type="text" required ng-model="account.name" class="input-xlarge tight-form-input last" >
+								</li>
+							</ul>
+							<div class="clearfix"></div>
+						</div>
+					</div>
+					<br>
+
+					<panel-loader type="'test'"></panel-loader>
+
+					<br>
+					<button type="submit" class="pull-right btn btn-success" ng-click="update()">Update</button>
+				</form>
+			</div>
+		</div>
+	</div>
+</div>
+
+

+ 55 - 0
src/app/features/account/partials/apikeys.html

@@ -0,0 +1,55 @@
+<topnav toggle="toggleSideMenu()"
+				title="API Keys"
+				icon="fa fa-shield"
+				section="Account"
+				show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+<div class="gf-box" style="min-height: 500px">
+
+	<div class="gf-box-body">
+		<div class="editor-row">
+			<div class="section">
+				<form name="addTokenrForm" class="form-inline tight-form">
+					<ul class="tight-form-list">
+						<li class="tight-form-item">
+							Add a Token
+						</li>
+						<li>
+							<input type="text" class="input-xlarge tight-form-input" ng-model='token.name' placeholder="Name"></input>
+						</li>
+						<li class="tight-form-item">
+							Role
+						</li>
+						<li>
+							<select class="input-small tight-form-input" ng-model="token.role" ng-options="r for r in roleTypes"></select>
+						</li>
+						<button class="btn btn-success tight-form-btn" ng-click="addToken()">Add</button>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</form>
+		</div>
+
+		<div class="editor-row row">
+			<div class="section span6">
+				<table class="grafana-options-table">
+					<tr ng-repeat="t in tokens">
+						<td>{{t.name}}</td>
+						<td>{{t.role}}</td>
+						<td>{{t.key}}</td>
+						<td style="width: 1%">
+							<a ng-click="removeToken(t.id)" class="btn btn-danger btn-mini">
+								<i class="fa fa-remove"></i>
+							</a>
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+
+	</div>
+</div>
+
+
+

+ 119 - 0
src/app/features/account/partials/datasources.html

@@ -0,0 +1,119 @@
+<topnav toggle="toggleSideMenu()"
+				title="Data sources"
+				icon="fa fa-shield"
+				section="Account"
+				show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+<div class="gf-box" style="min-height: 500px">
+	<div class="gf-box-header">
+		<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
+			<div ng-repeat="tab in ['Overview', 'Add', 'Edit']" data-title="{{tab}}">
+			</div>
+		</div>
+	</div>
+
+	<form name="editForm">
+		<div class="gf-box-body">
+			<div class="editor-row row" ng-if="editor.index == 0">
+				<div class="span8">
+					<div ng-if="datasources.length === 0">
+						<em>No datasources defined</em>
+					</div>
+					<table class="grafana-options-table" ng-if="datasources.length > 0">
+						<tr>
+							<td><strong>Name</strong></td>
+							<td><strong>Url</strong></td>
+							<td></td>
+							<td></td>
+							<td></td>
+						</tr>
+						<tr ng-repeat="ds in datasources">
+							<td style="width:1%">
+								<i class="fa fa-database"></i> &nbsp;
+								{{ds.name}}
+							</td>
+							<td style="width:90%">
+								{{ds.url}}
+							</td>
+							<td style="width:2%" class="text-center">
+								<span ng-if="ds.isDefault">
+									<span class="label label-info">default</span>
+								</span>
+							</td>
+							<td style="width: 1%">
+								<a ng-click="edit(ds)" class="btn btn-success btn-mini">
+									<i class="fa fa-edit"></i>
+									Edit
+								</a>
+							</td>
+							<td style="width: 1%">
+								<a ng-click="remove(ds)" class="btn btn-danger btn-mini">
+									<i class="fa fa-remove"></i>
+								</a>
+							</td>
+						</tr>
+					</table>
+				</div>
+			</div>
+
+			<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
+				<div class="editor-row">
+					<div class="editor-option">
+						<label class="small">Data source name</label>
+						<input type="text" class="input-large" ng-model='current.name' placeholder="production" required></input>
+					</div>
+					<div class="editor-option">
+						<label class="small">Type</label>
+						<select class="input-medium" ng-model="current.type" ng-options="f.type as f.name for f in types" ng-change="typeChanged()"></select>
+					</div>
+					<editor-opt-bool text="Mark as default" model="current.isDefault" change="render()"></editor-opt-bool>
+				</div>
+
+				<div class="editor-row">
+					<div class="editor-option">
+						<label class="small">Url</label>
+						<input type="text" class="input-xxlarge" ng-model='current.url' placeholder="http://my.graphite.com:8080" required></input>
+					</div>
+					<div class="editor-option">
+						<label class="small">Access method <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</label>
+						<select class="input-medium" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
+					</div>
+				</div>
+
+				<div class="editor-row" ng-if="current.type === 'influxdb'">
+					<div class="section">
+						<h5>InfluxDB Details</h5>
+						<div class="editor-option">
+							<label class="small">Database name</label>
+							<input type="text" class="input-large" required ng-model='current.database' placeholder=""></input>
+						</div>
+						<div class="editor-option">
+							<label class="small">User</label>
+							<input type="text" class="input-large" ng-model='current.user' placeholder=""></input>
+						</div>
+						<div class="editor-option">
+							<label class="small">Password</label>
+							<input type="password" class="input-large" ng-model='current.password' placeholder=""></input>
+						</div>
+					</div>
+				</div>
+				<div class="editor-row" ng-if="current.type === 'elasticsearch'">
+					<div class="section">
+						<h5>Elastic search details</h5>
+						<div class="editor-option">
+							<label class="small">Index name</label>
+							<input type="text" class="input-large" required ng-model='current.database' placeholder=""></input>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<br>
+			<button type="submit" class="btn btn-success" ng-show="editor.index === 1" ng-click="add()">Add</button>
+			<button type="submit" class="btn btn-success" ng-show="editor.index === 2 && !currentIsNew" ng-click="update()">Update</button>
+			<button type="submit" class="btn btn-inverse" ng-show="editor.index === 2 && !currentIsNew" ng-click="cancel()">Cancel</button>
+			<br>
+		</form>
+	</div>
+</div>

+ 63 - 0
src/app/features/account/partials/import.html

@@ -0,0 +1,63 @@
+<topnav toggle="toggleSideMenu()"
+				title="Import"
+				icon="fa fa-download"
+				show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+<div class="gf-box" style="min-height: 500px">
+
+	<div class="gf-box-header">
+		<div class="gf-box-title">
+			<i class="fa fa-th-large"></i>
+			Import Dashboards
+		</div>
+	</div>
+
+	<div class="gf-box-body">
+
+		<div class="editor-row">
+			<div class="section">
+				<div class="tight-form">
+					<ul class="tight-form-list">
+						<li class="tight-form-item" style="width: 160px">
+							<strong>Dashboard source</strong>
+						</li>
+						<li>
+							<select type="text" ng-model="sourceName" class="input-small tight-form-input" ng-options="f for f in datasources">
+							</select>
+						</li>
+						<li class="tight-form-item" style="width: 160px">
+							<strong>Destination</strong>
+						</li>
+						<li>
+							<select type="text" ng-model="destName" class="input-small tight-form-input" ng-options="f for f in datasources">
+							</select>
+						</li>
+						<li>
+							<button class="btn btn-success tight-form-btn" ng-click="startImport()">Import</button>
+						</li>
+					</ul>
+					<div class="clearfix"></div>
+				</div>
+			</div>
+		</div>
+
+		<div class="editor-row" ng-if="importing">
+			<section class="section">
+				<h5>{{infoText}}</h5>
+
+				<div class="editor-row row">
+					<table class="grafana-options-table span5">
+						<tr ng-repeat="dash in imported">
+							<td>{{dash.name}}</td>
+							<td>
+								{{dash.info}}
+							</td>
+						</tr>
+					</table>
+				</div>
+			</section>
+		</div>
+	</div>
+</div>
+

+ 54 - 0
src/app/features/account/partials/users.html

@@ -0,0 +1,54 @@
+<topnav toggle="toggleSideMenu()" title="Users" icon="fa fa-shield" section="Account" show-menu-btn="!grafana.sidemenu"></topnav>
+
+<div class="gf-box" style="min-height: 500px">
+
+	<div class="gf-box-body">
+
+		<div class="editor-row">
+			<div class="section">
+				<form name="form">
+					<div class="tight-form">
+						<ul class="tight-form-list">
+							<li class="tight-form-item" style="width: 160px">
+								<strong>Username or Email</strong>
+							</li>
+							<li>
+								<input type="text" ng-model="user.loginOrEmail" required class="input-xlarge tight-form-input" placeholder="user@email.com or username">
+							</li>
+							<li class="tight-form-item">
+								role
+							</li>
+							<li>
+								<select type="text" ng-model="user.role" class="input-small tight-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Admin']">
+								</select>
+							</li>
+							<li>
+								<button class="btn btn-success tight-form-btn" ng-click="addUser()">Add</button>
+							</li>
+						</ul>
+						<div class="clearfix"></div>
+					</div>
+				</form>
+			</div>
+		</div>
+
+
+		<div class="editor-row row">
+			<table class="grafana-options-table span5">
+				<tr ng-repeat="user in users">
+					<td>{{user.email}}</td>
+					<td>
+						{{user.role}}
+					</td>
+					<td style="width: 1%">
+						<a ng-click="removeUser(user)" class="btn btn-danger btn-mini">
+							<i class="fa fa-remove"></i>
+						</a>
+					</td>
+				</tr>
+			</table>
+		</div>
+
+	</div>
+</div>
+

+ 18 - 0
src/app/features/admin/adminCtrl.js

@@ -0,0 +1,18 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('AdminCtrl', function($scope) {
+
+    $scope.init = function() {
+      $scope.editor = {index: 0};
+    };
+
+    $scope.init();
+
+  });
+});

+ 18 - 0
src/app/features/admin/adminSettingsCtrl.js

@@ -0,0 +1,18 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('AdminSettingsCtrl', function($scope) {
+
+    $scope.init = function() {
+
+    };
+
+    $scope.init();
+
+  });
+});

+ 25 - 0
src/app/features/admin/adminUsersCtrl.js

@@ -0,0 +1,25 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('AdminUsersCtrl', function($scope, backendSrv) {
+
+    $scope.init = function() {
+      $scope.accounts = [];
+      $scope.getUsers();
+    };
+
+    $scope.getUsers = function() {
+      backendSrv.get('/api/admin/users').then(function(users) {
+        $scope.users = users;
+      });
+    };
+
+    $scope.init();
+
+  });
+});

+ 9 - 0
src/app/features/admin/partials/admin.html

@@ -0,0 +1,9 @@
+<topnav toggle="toggleSideMenu()"
+				title="Users"
+				icon="fa fa-cube"
+				section="Admin"
+				show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+<div class="gf-box" style="min-height: 500px">
+</div>

+ 11 - 0
src/app/features/admin/partials/settings.html

@@ -0,0 +1,11 @@
+<div ng-include="'app/partials/navbar.html'" ng-init="pageTitle='Admin > Settings'"></div>
+
+<div class="dashboard-edit-view" style="min-height: 500px">
+	<div class="dashboard-editor-body">
+		<div class="editor-row row">
+			<div class="section span6">
+
+			</div>
+		</div>
+	</div>
+</div>

+ 43 - 0
src/app/features/admin/partials/users.html

@@ -0,0 +1,43 @@
+<topnav toggle="toggleSideMenu()"
+				title="Users"
+				icon="fa fa-cube"
+				section="Admin"
+				show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+<div class="gf-box" style="min-height: 500px">
+
+	<div class="gf-box-body">
+		<div class="editor-row row">
+			<div class="section span6">
+				<table class="grafana-options-table">
+					<tr>
+						<th style="text-align:left">Id</th>
+						<th>Login</th>
+						<th>Email</th>
+						<th>Name</th>
+						<th>Admin</th>
+						<th></th>
+					</tr>
+					<tr ng-repeat="user in users">
+						<td>{{user.id}}</td>
+						<td>{{user.login}}</td>
+						<td>{{user.email}}</td>
+						<td>{{user.name}}</td>
+						<td>{{user.isAdmin}}</td>
+						<td style="width: 1%">
+							<a ng-click="edit(variable)" class="btn btn-success btn-small">
+								<i class="fa fa-edit"></i>
+								Edit
+							</a>
+							&nbsp;&nbsp;
+							<a ng-click="edit(variable)" class="btn btn-danger btn-small">
+								<i class="fa fa-remove"></i>
+							</a>
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+	</div>
+</div>

+ 10 - 0
src/app/features/all.js

@@ -7,4 +7,14 @@ define([
   './opentsdb/datasource',
   './elasticsearch/datasource',
   './dashboard/all',
+  './panel/all',
+  './profile/profileCtrl',
+  './account/accountUsersCtrl',
+  './account/datasourcesCtrl',
+  './account/apiKeysCtrl',
+  './account/importCtrl',
+  './account/accountCtrl',
+  './admin/adminUsersCtrl',
+  './admin/adminSettingsCtrl',
+  './grafanaDatasource/datasource',
 ], function () {});

+ 12 - 10
src/app/features/annotations/partials/editor.html

@@ -1,7 +1,7 @@
 <div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
 
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-bolt"></i>
 			Annotations
 		</div>
@@ -10,10 +10,12 @@
 			<div ng-repeat="tab in ['Overview', 'Add', 'Edit']" data-title="{{tab}}">
 			</div>
 		</div>
-
+		<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
+			<i class="fa fa-remove"></i>
+		</button>
 	</div>
 
-	<div class="dashboard-editor-body">
+	<div class="gf-box-body">
 		<div class="editor-row row" ng-if="editor.index == 0">
 			<div class="span6">
 				<div ng-if="annotations.length === 0">
@@ -71,12 +73,12 @@
 			<div ng-include src="currentDatasource.editorSrc">
 			</div>
 
-		</div>
-	</div>
+			<br>
+			<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>
+			<button ng-show="editor.index === 2" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
+			<br>
+			<br>
 
-	<div class="dashboard-editor-footer">
-		<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>
-		<button ng-show="editor.index === 2" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
-		<button type="button" class="btn btn-success pull-right" ng-click="close_edit();dismiss();dashboard.refresh();">Close</button>
+		</div>
 	</div>
 </div>

+ 0 - 1
src/app/features/dashboard/all.js

@@ -9,7 +9,6 @@ define([
   './keybindings',
   './viewStateSrv',
   './playlistSrv',
-  './panelSrv',
   './timeSrv',
   './unsavedChangesSrv',
 ], function () {});

+ 11 - 14
src/app/features/dashboard/dashboardCtrl.js

@@ -4,7 +4,7 @@ define([
   'config',
   'lodash',
 ],
-function (angular, $, config, _) {
+function (angular, $, config) {
   "use strict";
 
   var module = angular.module('grafana.controllers');
@@ -20,15 +20,15 @@ function (angular, $, config, _) {
       $timeout) {
 
     $scope.editor = { index: 0 };
-    $scope.panelNames = _.map(config.panels, function(value, key) { return key; });
+    $scope.panels = config.panels;
+
     var resizeEventTimeout;
 
-    this.init = function(dashboardData) {
-      $scope.availablePanels = config.panels;
+    this.init = function(dashboard) {
       $scope.reset_row();
       $scope.registerWindowResizeEvent();
       $scope.onAppEvent('show-json-editor', $scope.showJsonEditor);
-      $scope.setupDashboard(dashboardData);
+      $scope.setupDashboard(dashboard);
     };
 
     $scope.registerWindowResizeEvent = function() {
@@ -38,13 +38,14 @@ function (angular, $, config, _) {
       });
     };
 
-    $scope.setupDashboard = function(dashboardData) {
+    $scope.setupDashboard = function(dashboard) {
       $rootScope.performance.dashboardLoadStart = new Date().getTime();
       $rootScope.performance.panelsInitialized = 0;
       $rootScope.performance.panelsRendered = 0;
 
-      $scope.dashboard = dashboardSrv.create(dashboardData);
+      $scope.dashboard = dashboardSrv.create(dashboard.model);
       $scope.dashboardViewState = dashboardViewStateSrv.create($scope);
+      $scope.dashboardMeta = dashboard.meta;
 
       // init services
       timeSrv.init($scope.dashboard);
@@ -60,15 +61,11 @@ function (angular, $, config, _) {
 
     $scope.setWindowTitleAndTheme = function() {
       window.document.title = config.window_title_prefix + $scope.dashboard.title;
-      $scope.grafana.style = $scope.dashboard.style;
+      $scope.grafana.lightTheme = $scope.dashboard.style === 'light';
     };
 
-    $scope.isPanel = function(obj) {
-      if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
-        return true;
-      } else {
-        return false;
-      }
+    $scope.styleUpdated = function() {
+      $scope.grafana.lightTheme = $scope.dashboard.style === 'light';
     };
 
     $scope.add_row = function(dash, row) {

+ 25 - 18
src/app/features/dashboard/dashboardNavCtrl.js

@@ -22,7 +22,6 @@ function (angular, _, moment, config, store) {
       $scope.onAppEvent('zoom-out', function() {
         $scope.zoom(2);
       });
-
     };
 
     $scope.set_default = function() {
@@ -35,17 +34,29 @@ function (angular, _, moment, config, store) {
       alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default','success', 5000);
     };
 
-    $scope.saveForSharing = function() {
-      var clone = angular.copy($scope.dashboard);
-      clone.temp = true;
-      $scope.db.saveDashboard(clone)
-        .then(function(result) {
-
-          $scope.share = { url: result.url, title: result.title };
+    $scope.openEditView = function(editview) {
+      var search = _.extend($location.search(), {editview: editview});
+      $location.search(search);
+    };
 
-        }, function(err) {
-          alertSrv.set('Save for sharing failed', err, 'error',5000);
+    $scope.starDashboard = function() {
+      if ($scope.dashboardMeta.isStarred) {
+        $scope.db.unstarDashboard($scope.dashboard.id).then(function() {
+          $scope.dashboardMeta.isStarred = false;
         });
+      }
+      else {
+        $scope.db.starDashboard($scope.dashboard.id).then(function() {
+          $scope.dashboardMeta.isStarred = true;
+        });
+      }
+    };
+
+    $scope.shareDashboard = function() {
+      $scope.appEvent('show-modal', {
+        src: './app/features/dashboard/partials/shareModal.html',
+        scope: $scope.$new(),
+      });
     };
 
     $scope.passwordCache = function(pwd) {
@@ -105,10 +116,10 @@ function (angular, _, moment, config, store) {
     };
 
     $scope.deleteDashboardConfirmed = function(options) {
-      var id = options.id;
-      $scope.db.deleteDashboard(id).then(function(id) {
-        $scope.appEvent('dashboard-deleted', id);
-        $scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']);
+      var slug = options.slug;
+      $scope.db.deleteDashboard(slug).then(function() {
+        $scope.appEvent('dashboard-deleted', options);
+        $scope.appEvent('alert-success', ['Dashboard Deleted', options.title + ' has been deleted']);
       }, function(err) {
         $scope.appEvent('alert-error', ['Deleted failed', err]);
       });
@@ -140,10 +151,6 @@ function (angular, _, moment, config, store) {
       });
     };
 
-    $scope.styleUpdated = function() {
-      $scope.grafana.style = $scope.dashboard.style;
-    };
-
     $scope.editJson = function() {
       $scope.appEvent('show-json-editor', { object: $scope.dashboard });
     };

+ 35 - 0
src/app/features/dashboard/partials/shareModal.html

@@ -0,0 +1,35 @@
+<div class="modal-body gf-box gf-box-no-margin" ng-controller="SharePanelCtrl">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
+			<i class="fa fa-share"></i>
+			Share
+		</div>
+
+		<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
+			<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
+			</div>
+		</div>
+
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
+	</div>
+
+	<div class="gf-box-body">
+		<div class="editor-row">
+			<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
+			<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
+			<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
+		</div>
+
+		<div class="editor-row" style="margin-top: 20px;">
+			<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
+		</div>
+
+		<div class="editor-row" style="margin-top: 20px;">
+			<a href="{{imageUrl}}" target="_blank">Link to rendered image</a>
+		</div>
+	</div>
+
+</div>
+

+ 7 - 6
src/app/features/dashboard/rowCtrl.js

@@ -1,9 +1,10 @@
 define([
   'angular',
   'app',
-  'lodash'
+  'lodash',
+  'config'
 ],
-function (angular, app, _) {
+function (angular, app, _, config) {
   'use strict';
 
   var module = angular.module('grafana.controllers');
@@ -107,11 +108,11 @@ function (angular, app, _) {
       var _as = 12 - $scope.dashboard.rowSpan($scope.row);
 
       $scope.panel = {
-        title: 'no title (click here)',
-        error   : false,
-        span    : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
+        title: config.new_panel_title,
+        error: false,
+        span: _as < defaultSpan && _as > 0 ? _as : defaultSpan,
         editable: true,
-        type    : type
+        type: type
       };
 
       function fixRowHeight(height) {

+ 10 - 5
src/app/features/dashboard/sharePanelCtrl.js

@@ -12,9 +12,12 @@ function (angular, _) {
     $scope.init = function() {
       $scope.editor = { index: 0 };
       $scope.forCurrent = true;
-      $scope.toPanel = true;
-      $scope.includeTemplateVars = true;
 
+      if ($scope.panel) {
+        $scope.toPanel = true;
+      }
+
+      $scope.includeTemplateVars = true;
       $scope.buildUrl();
     };
 
@@ -26,7 +29,6 @@ function (angular, _) {
         baseUrl = baseUrl.substring(0, queryStart);
       }
 
-      var panelId = $scope.panel.id;
       var params = angular.copy($location.search());
 
       var range = timeSrv.timeRangeForUrl();
@@ -50,7 +52,7 @@ function (angular, _) {
       }
 
       if ($scope.toPanel) {
-        params.panelId = panelId;
+        params.panelId = $scope.panel.id;
         params.fullscreen = true;
       } else {
         delete params.panelId;
@@ -68,7 +70,10 @@ function (angular, _) {
         }
       });
 
-      $scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ;
+      $scope.shareUrl = baseUrl + "?" + paramsArray.join('&');
+      $scope.imageUrl = $scope.shareUrl.replace('/dashboard/db/', '/render/dashboard/solo/');
+      $scope.imageUrl += '&width=1000';
+      $scope.imageUrl += '&height=500';
 
       $timeout(function() {
         var input = $element.find('[data-share-panel-url]');

+ 1 - 0
src/app/features/dashboard/unsavedChangesSrv.js

@@ -60,6 +60,7 @@ function(angular, _, config) {
     this.open_modal = function() {
       var confirmModal = $modal({
         template: './app/partials/unsaved-changes.html',
+        modalClass: 'confirm-modal',
         persist: true,
         show: false,
         scope: modalScope,

+ 4 - 3
src/app/features/dashboard/viewStateSrv.js

@@ -53,12 +53,13 @@ function (angular, _, $) {
       state.panelId = parseInt(state.panelId) || null;
       state.fullscreen = state.fullscreen ? true : null;
       state.edit =  (state.edit === "true" || state.edit === true) || null;
+      state.editview = state.editview || null;
       return state;
     };
 
     DashboardViewState.prototype.serializeToUrl = function() {
       var urlState = _.clone(this.state);
-      urlState.fullscreen = this.state.fullscreen ? true : null,
+      urlState.fullscreen = this.state.fullscreen ? true : null;
       urlState.edit = this.state.edit ? true : null;
       return urlState;
     };
@@ -117,7 +118,7 @@ function (angular, _, $) {
           self.$scope.dashboard.emit_refresh();
         }
         else {
-          self.fullscreenPanel.$emit('render');
+          self.fullscreenPanel.$broadcast('render');
         }
         delete self.fullscreenPanel;
       });
@@ -138,7 +139,7 @@ function (angular, _, $) {
       panelScope.fullscreen = true;
 
       $timeout(function() {
-        panelScope.$emit('render');
+        panelScope.$broadcast('render');
       });
     };
 

+ 1 - 1
src/app/features/elasticsearch/datasource.js

@@ -13,7 +13,7 @@ function (angular, _, config, kbn, moment) {
   module.factory('ElasticDatasource', function($q, $http, templateSrv) {
 
     function ElasticDatasource(datasource) {
-      this.type = 'elastic';
+      this.type = 'elasticsearch';
       this.basicAuth = datasource.basicAuth;
       this.url = datasource.url;
       this.name = datasource.name;

+ 78 - 0
src/app/features/grafanaDatasource/datasource.js

@@ -0,0 +1,78 @@
+define([
+  'angular',
+  'lodash',
+  'kbn',
+],
+function (angular, _, kbn) {
+  'use strict';
+
+  var module = angular.module('grafana.services');
+
+  module.factory('GrafanaDatasource', function($q, backendSrv) {
+
+    function GrafanaDatasource() {
+      this.type = 'grafana';
+      this.grafanaDB = true;
+      this.name = "grafana";
+      this.supportMetrics = true;
+      this.editorSrc = 'app/features/grafanaDatasource/partials/query.editor.html';
+    }
+
+    GrafanaDatasource.prototype.getDashboard = function(slug, isTemp) {
+      var url = '/dashboards/' + slug;
+
+      if (isTemp) {
+        url = '/temp/' + slug;
+      }
+
+      return backendSrv.get('/api/dashboards/db/' + slug);
+    };
+
+    GrafanaDatasource.prototype.query = function(options) {
+      // get from & to in seconds
+      var from = kbn.parseDate(options.range.from).getTime();
+      var to = kbn.parseDate(options.range.to).getTime();
+
+      return backendSrv.get('/api/metrics/test', { from: from, to: to, maxDataPoints: options.maxDataPoints });
+    };
+
+    GrafanaDatasource.prototype.starDashboard = function(dashId) {
+      return backendSrv.post('/api/user/stars/dashboard/' + dashId);
+    };
+
+    GrafanaDatasource.prototype.unstarDashboard = function(dashId) {
+      return backendSrv.delete('/api/user/stars/dashboard/' + dashId);
+    };
+
+    GrafanaDatasource.prototype.saveDashboard = function(dashboard) {
+      // remove id if title has changed
+      if (dashboard.title !== dashboard.originalTitle) {
+        dashboard.id = null;
+      }
+
+      return backendSrv.post('/api/dashboards/db/', { dashboard: dashboard })
+        .then(function(data) {
+          return { title: dashboard.title, url: '/dashboard/db/' + data.slug };
+        }, function(err) {
+          err.isHandled = true;
+          err.data = err.data || {};
+          throw err.data.message || "Unknown error";
+        });
+    };
+
+    GrafanaDatasource.prototype.deleteDashboard = function(id) {
+      return backendSrv.delete('/api/dashboards/db/' + id);
+    };
+
+    GrafanaDatasource.prototype.searchDashboards = function(query) {
+      return backendSrv.get('/api/search/', {q: query})
+        .then(function(data) {
+          return data;
+        });
+    };
+
+    return GrafanaDatasource;
+
+  });
+
+});

+ 17 - 0
src/app/features/grafanaDatasource/partials/query.editor.html

@@ -0,0 +1,17 @@
+
+<div class="fluid-row" style="margin-top: 20px">
+	<div class="span2"></div>
+	<div class="grafana-info-box span8">
+		<h5>Test graph</h5>
+
+		<p>
+		This is just a test data source that generates random walk series. If this is your only data source
+		open the left side menu and navigate to the data sources admin screen and add your data sources. You can change
+		data source using the button to the left of the <strong>Add query</strong> button.
+		</p>
+	</div>
+	<div class="span2"></div>
+
+	<div class="clearfix"></div>
+</div>
+

+ 1 - 1
src/app/features/influxdb/datasource.js

@@ -15,7 +15,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
   module.factory('InfluxDatasource', function($q, $http, templateSrv) {
 
     function InfluxDatasource(datasource) {
-      this.type = 'influxDB';
+      this.type = 'influxdb';
       this.urls = datasource.urls;
       this.username = datasource.username;
       this.password = datasource.password;

+ 6 - 0
src/app/features/panel/all.js

@@ -0,0 +1,6 @@
+define([
+  './panelMenu',
+  './panelDirective',
+  './panelSrv',
+  './soloPanelCtrl',
+], function () {});

+ 40 - 0
src/app/features/panel/panelDirective.js

@@ -0,0 +1,40 @@
+define([
+  'angular',
+  'jquery',
+  'config',
+],
+function (angular, $, config) {
+  'use strict';
+
+  angular
+    .module('grafana.directives')
+    .directive('panelLoader', function($compile, $parse) {
+      return {
+        restrict: 'E',
+        link: function(scope, elem, attr) {
+          var getter = $parse(attr.type), panelType = getter(scope);
+          var panelPath = config.panels[panelType].path;
+
+          scope.require([panelPath + "/module"], function () {
+            var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
+            elem.append(panelEl);
+            $compile(panelEl)(scope);
+          });
+        }
+      };
+    }).directive('grafanaPanel', function() {
+      return {
+        restrict: 'E',
+        templateUrl: 'app/features/panel/partials/panel.html',
+        transclude: true,
+        link: function(scope, elem) {
+          var panelContainer = elem.find('.panel-container');
+
+          scope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
+            panelContainer.css({ minHeight: scope.panel.height || scope.row.height, display: 'block' });
+            elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
+          });
+        }
+      };
+    });
+});

+ 0 - 0
src/app/directives/panelMenu.js → src/app/features/panel/panelMenu.js


+ 7 - 2
src/app/features/dashboard/panelSrv.js → src/app/features/panel/panelSrv.js

@@ -1,8 +1,9 @@
 define([
   'angular',
   'lodash',
+  'config',
 ],
-function (angular, _) {
+function (angular, _, config) {
   'use strict';
 
   var module = angular.module('grafana.services');
@@ -24,7 +25,7 @@ function (angular, _) {
 
       $scope.sharePanel = function() {
         $scope.appEvent('show-modal', {
-          src: './app/partials/share-panel.html',
+          src: './app/features/dashboard/partials/shareModal.html',
           scope: $scope.$new()
         });
       };
@@ -77,6 +78,10 @@ function (angular, _) {
         $scope.editorHelpIndex = index;
       };
 
+      $scope.isNewPanel = function() {
+        return $scope.panel.title === config.new_panel_title;
+      };
+
       $scope.toggleFullscreen = function(edit) {
         $scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id });
       };

+ 42 - 0
src/app/features/panel/partials/panel.html

@@ -0,0 +1,42 @@
+<div class="panel-container">
+	<div class="panel-header">
+		<span class="alert-error panel-error small pointer" config-modal="app/partials/inspector.html" ng-if="panelMeta.error">
+			<span data-placement="top" bs-tooltip="panelMeta.error">
+				<i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span>
+			</span>
+		</span>
+
+		<span class="panel-loading" ng-show="panelMeta.loading">
+			<i class="fa fa-spinner fa-spin"></i>
+		</span>
+
+		<div class="panel-title-container drag-handle" panel-menu></div>
+	</div>
+
+	<div class="panel-content">
+		<ng-transclude></ng-transclude>
+	</div>
+</div>
+
+<div class="panel-full-edit" ng-if="editMode">
+	<div class="gf-box">
+		<div class="gf-box-header">
+			<div class="gf-box-title">
+				<i ng-class="panelMeta.editIcon"></i>
+				{{panelMeta.panelName}}
+			</div>
+
+			<div ng-model="editor.index" bs-tabs>
+				<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
+				</div>
+			</div>
+		</div>
+
+		<div class="gf-box-body">
+			<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
+				<div ng-include src="tab.src"></div>
+			</div>
+		</div>
+	</div>
+</div>
+

+ 0 - 0
src/app/features/dashboard/partials/panelTime.html → src/app/features/panel/partials/panelTime.html


+ 8 - 0
src/app/features/panel/partials/soloPanel.html

@@ -0,0 +1,8 @@
+<div class="container-fluid main">
+	<div class="row-fluid">
+		<div class="span12">
+			<div class="panel nospace" ng-if="panel" style="width: 100%">
+				<panel-loader type="panel.type" ng-cloak></panel-loader>
+			</div>
+		</div>
+</div>

+ 70 - 0
src/app/features/panel/soloPanelCtrl.js

@@ -0,0 +1,70 @@
+define([
+  'angular',
+  'jquery',
+],
+function (angular, $) {
+  "use strict";
+
+  var module = angular.module('grafana.routes');
+
+  module.controller('SoloPanelCtrl', function($scope, $rootScope, datasourceSrv, $routeParams, dashboardSrv, timeSrv, $location) {
+    var panelId;
+
+    $scope.init = function() {
+      var db = datasourceSrv.getGrafanaDB();
+      var params = $location.search();
+      panelId = parseInt(params.panelId);
+
+      db.getDashboard($routeParams.id, false)
+        .then(function(dashboard) {
+          $scope.initPanelScope(dashboard);
+        }).then(null, function(error) {
+          $scope.appEvent('alert-error', ['Load panel error', error]);
+        });
+    };
+
+    $scope.initPanelScope = function(dashboard) {
+      $scope.dashboard = dashboardSrv.create(dashboard.model);
+      $scope.grafana.style = $scope.dashboard.style;
+      $scope.row = {
+        height: $(window).height() + 'px',
+      };
+      $scope.test = "Hej";
+      $scope.$index = 0;
+      $scope.panel = $scope.getPanelById(panelId);
+
+      if (!$scope.panel) {
+        $scope.appEvent('alert-error', ['Panel not found', '']);
+        return;
+      }
+
+      $scope.panel.span = 12;
+      $scope.dashboardViewState = {
+        registerPanel: function() {
+        }
+      };
+
+      timeSrv.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;
+    };
+
+    if (!$scope.skipAutoInit) {
+      $scope.init();
+    }
+
+  });
+
+});

+ 127 - 0
src/app/features/profile/partials/profile.html

@@ -0,0 +1,127 @@
+<topnav toggle="toggleSideMenu()"
+				title="Details"
+				icon="fa fa-user"
+				section="Profile"
+				show-menu-btn="!grafana.sidemenu">
+</topnav>
+
+
+<div class="editor-row">
+	<div class="section">
+
+		<div class="gf-box">
+			<div class="gf-box-header">
+				<div class="gf-box-title">
+					<i class="fa fa-user"></i>
+					Personal information
+				</div>
+			</div>
+
+			<div class="gf-box-body">
+				<div class="row">
+					<form name="userForm">
+						<div>
+							<div class="tight-form">
+								<ul class="tight-form-list">
+									<li class="tight-form-item" style="width: 80px">
+										<strong>Name</strong>
+									</li>
+									<li>
+										<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
+									</li>
+								</ul>
+								<div class="clearfix"></div>
+							</div>
+							<div class="tight-form" style="margin-top: 10px">
+								<ul class="tight-form-list">
+									<li class="tight-form-item" style="width: 80px">
+										<strong>Email</strong>
+									</li>
+									<li>
+										<input type="text" required ng-model="user.email" class="input-xxlarge tight-form-input last" >
+									</li>
+								</ul>
+								<div class="clearfix"></div>
+							</div>
+							<div class="tight-form" style="margin-top: 10px">
+								<ul class="tight-form-list">
+									<li class="tight-form-item" style="width: 80px">
+										<strong>Username</strong>
+									</li>
+									<li>
+										<input type="text" required ng-model="user.login" class="input-xxlarge tight-form-input last" >
+									</li>
+								</ul>
+								<div class="clearfix"></div>
+							</div>
+						</div>
+
+						<br>
+						<button type="submit" class="pull-right btn btn-success" ng-click="update()">Update</button>
+					</form>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div class="section">
+
+		<div class="gf-box">
+			<div class="gf-box-header">
+				<div class="gf-box-title">
+					<i class="fa fa-shield"></i>
+					Your accounts
+				</div>
+			</div>
+			<div class="gf-box-body">
+				<table class="grafana-options-table">
+					<tr ng-repeat="ac in accounts">
+						<td>Name: {{ac.name}}</td>
+						<td>Role: {{ac.role}}</td>
+						<td ng-show="ac.isUsing">
+							<span class="label label-info">
+								active now
+							</span>
+						</td>
+						<td ng-show="!ac.isUsing">
+							<a ng-click="setUsingAccount(ac)" class="btn btn-success btn-mini">
+								Select
+							</a>
+						</td>
+					</tr>
+				</table>
+			</div>
+		</div>
+	</div>
+
+	<div class="section">
+		<div class="gf-box">
+			<div class="gf-box-header">
+				<div class="gf-box-title">
+					<i class="fa fa-plus-square"></i>
+					Add account
+				</div>
+			</div>
+			<div class="gf-box-body">
+				<form name="form">
+					<div class="tight-form">
+						<ul class="tight-form-list">
+							<li class="tight-form-item">
+								<strong>Account name</strong>
+							</li>
+							<li>
+								<input type="text" ng-model="newAccount.name" required class="input-xlarge tight-form-input" placeholder="account name">
+							</li>
+							<li>
+								<button class="btn btn-success tight-form-btn" ng-click="createAccount()">Create</button>
+							</li>
+						</ul>
+						<div class="clearfix"></div>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</div>
+
+

+ 46 - 0
src/app/features/profile/profileCtrl.js

@@ -0,0 +1,46 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.controllers');
+
+  module.controller('ProfileCtrl', function($scope, $http, backendSrv) {
+
+    $scope.newAccount = {name: ''};
+
+    $scope.init = function() {
+      $scope.getUser();
+      $scope.getUserAccounts();
+    };
+
+    $scope.getUser = function() {
+      backendSrv.get('/api/user').then(function(user) {
+        $scope.user = user;
+      });
+    };
+
+    $scope.getUserAccounts = function() {
+      backendSrv.get('/api/user/accounts').then(function(accounts) {
+        $scope.accounts = accounts;
+      });
+    };
+
+    $scope.setUsingAccount = function(account) {
+      backendSrv.post('/api/user/using/' + account.accountId).then($scope.getUserAccounts);
+    };
+
+    $scope.update = function() {
+      if (!$scope.userForm.$valid) { return; }
+      backendSrv.put('/api/user/', $scope.user);
+    };
+
+    $scope.createAccount = function() {
+      backendSrv.post('/api/account/', $scope.newAccount).then($scope.getUserAccounts);
+    };
+
+    $scope.init();
+
+  });
+});

+ 41 - 0
src/app/panels/dashlist/editor.html

@@ -0,0 +1,41 @@
+
+<div class="editor-row">
+	<div class="section" style="margin-bottom: 20px">
+		<div class="tight-form">
+			<ul class="tight-form-list">
+				<li class="tight-form-item" style="width: 110px">
+					<strong>Mode</strong>
+				</li>
+				<li>
+					<select class="input-small tight-form-input" ng-model="panel.mode" ng-options="f for f in modes" ng-change="get_data()"></select>
+				</li>
+			</ul>
+			<div class="clearfix"></div>
+		</div>
+	</div>
+	<div class="section" style="margin-bottom: 20px" ng-if="panel.mode === 'search'">
+		<div class="tight-form">
+			<ul class="tight-form-list">
+				<li class="tight-form-item" style="width: 110px">
+					<strong>Search options</strong>
+				</li>
+				<li class="tight-form-item">
+					Query
+				</li>
+				<li>
+					<input type="text" class="input-small tight-form-input" placeholder="title query"
+					ng-model="panel.query" ng-change="get_data()" ng-model-onblur>
+				</li>
+				<li class="tight-form-item">
+					Tag
+				</li>
+				<li>
+					<input type="text" class="input-small tight-form-input" placeholder="full tag name"
+					ng-model="panel.tag" ng-change="get_data()" ng-model-onblur>
+				</li>
+			</ul>
+			<div class="clearfix"></div>
+		</div>
+	</div>
+</div>
+

+ 13 - 0
src/app/panels/dashlist/module.html

@@ -0,0 +1,13 @@
+<grafana-panel>
+	<div class="dashlist">
+	<div class="dashlist-item" ng-repeat="dash in dashList">
+		<a class="dashlist-link" href="{{dash.url}}">
+			<span class="dashlist-title">
+				{{dash.title}}
+			</span>
+			<span class="dashlist-star">
+				<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': !dash.isStarred}"></i>
+			</span>
+		</a>
+	</div>
+</grafana-panel>

+ 69 - 0
src/app/panels/dashlist/module.js

@@ -0,0 +1,69 @@
+define([
+  'angular',
+  'app',
+  'lodash',
+  'config',
+  'components/panelmeta',
+],
+function (angular, app, _, config, PanelMeta) {
+  'use strict';
+
+  var module = angular.module('grafana.panels.dashlist', []);
+  app.useModule(module);
+
+  module.directive('grafanaPanelDashlist', function() {
+    return {
+      controller: 'DashListPanelCtrl',
+      templateUrl: 'app/panels/dashlist/module.html',
+    };
+  });
+
+  module.controller('DashListPanelCtrl', function($scope, panelSrv, backendSrv) {
+
+    $scope.panelMeta = new PanelMeta({
+      panelName: 'Dash list',
+      editIcon:  "fa fa-star",
+      fullscreen: true,
+    });
+
+    $scope.panelMeta.addEditorTab('Options', 'app/panels/dashlist/editor.html');
+
+    var defaults = {
+      mode: 'starred',
+      query: '',
+      tag: '',
+    };
+
+    $scope.modes = ['starred', 'search'];
+
+    _.defaults($scope.panel, defaults);
+
+    $scope.dashList = [];
+
+    $scope.init = function() {
+      panelSrv.init($scope);
+
+      if ($scope.isNewPanel()) {
+        $scope.panel.title = "Starred Dashboards";
+      }
+
+      $scope.$on('refresh', $scope.get_data);
+    };
+
+    $scope.get_data = function() {
+      var params = {};
+      if ($scope.panel.mode === 'starred') {
+        params.starred = 1;
+      } else {
+        params.q = "tags:" + $scope.panel.tag + " AND title:" + $scope.panel.query;
+      }
+
+      backendSrv.get('/api/search', params).then(function(result) {
+        $scope.dashList = result.dashboards;
+        $scope.panelMeta.loading = false;
+      });
+    };
+
+    $scope.init();
+  });
+});

+ 2 - 19
src/app/panels/graph/module.html

@@ -1,4 +1,4 @@
-<div ng-controller='GraphCtrl'>
+<grafana-panel>
 
 	<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
 		<div class="graph-canvas-wrapper">
@@ -24,23 +24,6 @@
 
 	<div class="clearfix"></div>
 
-	<div style="margin-top: 30px" ng-if="editMode">
-		<div class="dashboard-editor-header">
-			<div class="dashboard-editor-title">
-				<i class="fa fa-bar-chart"></i>
-				Graph
-			</div>
+</grafana-panel>
 
-			<div ng-model="editor.index" bs-tabs>
-				<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
-				</div>
-			</div>
-		</div>
 
-		<div class="dashboard-editor-body">
-			<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
-				<div ng-include src="tab.src"></div>
-			</div>
-		</div>
-	</div>
-</div>

+ 10 - 2
src/app/panels/graph/module.js

@@ -16,17 +16,25 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
 
   var module = angular.module('grafana.panels.graph');
 
+  module.directive('grafanaPanelGraph', function() {
+    return {
+      controller: 'GraphCtrl',
+      templateUrl: 'app/panels/graph/module.html',
+    };
+  });
+
   module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) {
 
     $scope.panelMeta = new PanelMeta({
-      description: 'Graph panel',
+      panelName: 'Graph',
+      editIcon:  "fa fa-bar-chart",
       fullscreen: true,
       metricsEditor: true
     });
 
     $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
     $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html');
-    $scope.panelMeta.addEditorTab('Time range', 'app/features/dashboard/partials/panelTime.html');
+    $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
 
     $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
     $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');

+ 2 - 24
src/app/panels/singlestat/module.html

@@ -1,26 +1,4 @@
-<div ng-controller='SingleStatCtrl'>
-
+<grafana-panel>
 	<div class="singlestat-panel" singlestat-panel></div>
-
   <div class="clearfix"></div>
-
-	<div style="margin-top: 30px" ng-if="editMode">
-		<div class="dashboard-editor-header">
-			<div class="dashboard-editor-title">
-				<i class="fa fa-dashboard"></i>
-			  Singlestat
-			</div>
-
-			<div ng-model="editor.index" bs-tabs>
-				<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
-				</div>
-			</div>
-		</div>
-
-		<div class="dashboard-editor-body">
-			<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
-				<div ng-include src="tab.src"></div>
-			</div>
-		</div>
-	</div>
-</div>
+</grafana-panel>

+ 10 - 2
src/app/panels/singlestat/module.js

@@ -13,10 +13,18 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) {
   var module = angular.module('grafana.panels.singlestat');
   app.useModule(module);
 
+  module.directive('grafanaPanelSinglestat', function() {
+    return {
+      controller: 'SingleStatCtrl',
+      templateUrl: 'app/panels/singlestat/module.html',
+    };
+  });
+
   module.controller('SingleStatCtrl', function($scope, panelSrv, timeSrv) {
 
     $scope.panelMeta = new PanelMeta({
-      description: 'Singlestat panel',
+      panelName: 'Singlestat',
+      editIcon:  "fa fa-dashboard",
       fullscreen: true,
       metricsEditor: true
     });
@@ -192,7 +200,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) {
       data.colorMap = $scope.panel.colors;
 
       $scope.data = data;
-      $scope.$emit('render');
+      $scope.$broadcast('render');
     };
 
     $scope.getFormatedValue = function(mainValue) {

+ 3 - 4
src/app/panels/text/module.html

@@ -1,4 +1,3 @@
-<div ng-controller='text'>
-  <p ng-bind-html="content" ng-show="content">
-  </p>
-</div>
+<grafana-panel>
+  <p ng-bind-html="content" ng-show="content"></p>
+</grafana-panel>

+ 12 - 3
src/app/panels/text/module.js

@@ -8,15 +8,24 @@ define([
 function (angular, app, _, require, PanelMeta) {
   'use strict';
 
+  var converter;
+
   var module = angular.module('grafana.panels.text', []);
   app.useModule(module);
 
-  var converter;
+  module.directive('grafanaPanelText', function() {
+    return {
+      controller: 'TextPanelCtrl',
+      templateUrl: 'app/panels/text/module.html',
+    };
+  });
 
-  module.controller('text', function($scope, templateSrv, $sce, panelSrv) {
+  module.controller('TextPanelCtrl', function($scope, templateSrv, $sce, panelSrv) {
 
     $scope.panelMeta = new PanelMeta({
-      description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
+      panelName: 'Text',
+      editIcon:  "fa fa-text-width",
+      fullscreen: true,
     });
 
     $scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html');

+ 6 - 6
src/app/panels/timepicker/custom.html

@@ -1,11 +1,14 @@
-<div class="dashboard-editor-header">
-	<div class="dashboard-editor-title">
+<div class="gf-box-header">
+	<div class="gf-box-title">
 		<i class="fa fa-clock-o"></i>
 		Custom time range
 	</div>
+	<button class="gf-box-header-close-btn" ng-click="dismiss();">
+		<i class="fa fa-remove"></i>
+	</button>
 </div>
 
-<div class="dashboard-editor-body">
+<div class="gf-box-body">
 	<style>
 		.timepicker-to-column {
 			margin-top: 10px;
@@ -73,12 +76,9 @@
 		</form>
 		<div class="clearfix"></div>
 	</div>
-</div>
 
-<div class="dashboard-editor-footer">
 	<form name="input" style="margin-bottom:0">
 		<span class="" ng-hide="input.$valid">Invalid date or range</span>
 		<button ng-click="setAbsoluteTimeFilter(validate(temptime));dismiss();" ng-disabled="!input.$valid" class="btn btn-success">Apply</button>
-		<button ng-click="dismiss();" class="btn btn-success pull-right">Cancel</button>
 	</form>
 </div>

+ 2 - 0
src/app/panels/timepicker/editor.html

@@ -1,5 +1,6 @@
   <div class="editor-row">
 		<div class="section">
+			<div>
 			<div class="tight-form">
 				<ul class="tight-form-list">
 					<li class="tight-form-item" style="width: 148px">
@@ -34,6 +35,7 @@
 				</ul>
 				<div class="clearfix"></div>
 			</div>
+			</div>
 
 			<p>
 			<br>

+ 1 - 0
src/app/panels/timepicker/module.html

@@ -21,6 +21,7 @@
 			<li class="dropdown">
 
 				<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
+					<i class="fa fa-clock-o"></i>
 					<span ng-bind="time.rangeString"></span>
 					<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
 					<i class="fa fa-caret-down"></i>

+ 4 - 4
src/app/partials/confirm_modal.html

@@ -1,12 +1,12 @@
-<div class="modal-body">
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+<div class="modal-body gf-box gf-box-no-margin">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-check"></i>
 			{{title}}
 		</div>
 	</div>
 
-	<div class="dashboard-editor-body">
+	<div class="gf-box-body" style="min-height: 0px;">
 		<p class="row-fluid text-center large">
 			{{text}}
 			<br>

+ 0 - 11
src/app/partials/dashLoaderShare.html

@@ -1,11 +0,0 @@
-<div class="modal-header">
-  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-  <h3>{{share.title}} <small>shareable link</small></h3>
-</div>
-<div class="modal-body">
-  <label>Share this dashboard with this URL</label>
-  <input ng-model='share.url' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()">
-</div>
-<div class="modal-footer">
-  <button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>
-</div>

+ 5 - 6
src/app/partials/dashboard.html

@@ -35,8 +35,8 @@
 								<li class="dropdown-submenu">
 									<a href="javascript:void(0);">Add Panel</a>
 									<ul class="dropdown-menu">
-										<li bindonce ng-repeat="name in panelNames">
-											<a ng-click="add_panel_default(name)" bo-text="name"></a>
+										<li bindonce ng-repeat="(key, value) in panels">
+											<a ng-click="add_panel_default(key)" bo-text="value.name"></a>
 										</li>
 									</ul>
 								</li>
@@ -79,12 +79,11 @@
 					</div>
 
 					<!-- Panels, draggable needs to be disabled in fullscreen because Firefox bug -->
-					<div ng-repeat="(name, panel) in row.panels"
-						class="panel"
+					<div ng-repeat="(name, panel) in row.panels" class="panel"
 						ui-draggable="{{!dashboardViewState.fullscreen}}" drag="panel.id"
 						ui-on-Drop="onDrop($data, row, panel)"
-						drag-handle-class="drag-handle" panel-width ng-model="panel">
-						<grafana-panel type="panel.type" ng-cloak></grafana-panel>
+						drag-handle-class="drag-handle" panel-width>
+						<panel-loader type="panel.type" class="panel-margin"></panel-loader>
 					</div>
 
 					<div panel-drop-zone class="panel panel-drop-zone"

+ 47 - 55
src/app/partials/dashboard_topnav.html

@@ -1,71 +1,63 @@
-<div class="navbar navbar-static-top">
+<div class="navbar navbar-static-top" ng-controller='DashboardNavCtrl' ng-init="init()">
 	<div class="navbar-inner">
 		<div class="container-fluid">
-			<span class="brand">
-				<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
-				<span class="page-title">{{dashboard.title}}</span>
-			</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 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>
+			<div class="top-nav">
+				<a class="pointer top-nav-menu-btn" ng-if="!grafana.sidemenu" ng-click="toggleSideMenu()">
+					<img class="logo-icon" src="img/fav32.png"></img>
+					<i class="fa fa-bars"></i>
+				</a>
 
-				<li class="dropdown grafana-menu-save">
-					<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
-						<i class='fa fa-save'></i>
+				<div class="top-nav-dashboards-btn">
+					<a class="pointer" ng-click="openSearch()">
+						<i class="fa fa-th-large"></i>
+						<i class="fa fa-caret-down"></i>
 					</a>
+				</div>
+
+				<span class="fa fa-angle-right top-nav-breadcrumb-icon">
+				</span>
 
-					<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="fa fa-save"></i></button>
-							</form>
-						</li>
+				<a ng-click="asd()" class="top-nav-title pointer">
+					 {{dashboard.title}}
+				 </a>
+			 </div>
 
-						<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="editJson()">Dashboard JSON</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 class="nav pull-left" ng-if="!dashboardMeta.isHome">
+				<li>
+					<a class="pointer" ng-click="starDashboard()">
+						<i class="fa" ng-class="{'fa-star-o': !dashboardMeta.isStarred, 'fa-star': dashboardMeta.isStarred,}" style="color: orange;"></i>
+					</a>
+				</li>
+				<li>
+					<a class="pointer" ng-click="shareDashboard()"><i class="fa fa-share-square-o"></i></a>
+				</li>
+				<li>
+					<a ng-click="saveDashboard()"><i class="fa fa-save"></i></a>
+				</li>
+				<li class="dropdown">
+					<a class="pointer" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
+					<ul class="dropdown-menu">
+						<li><a class="pointer" ng-click="openEditView('settings');">Settings</a></li>
+						<li><a class="pointer" ng-click="openEditView('annotations');">Annotations</a></li>
+						<li><a class="pointer" ng-click="openEditView('templating');">Templating</a></li>
+						<li><a class="pointer" ng-click="exportDashboard();">Export</a></li>
+						<li><a class="pointer" ng-click="editJson();">View JSON</a></li>
 					</ul>
 				</li>
+			</ul>
 
-				<li class="dropdown grafana-menu-load">
-					<a ng-click="openSearch()" bs-tooltip="'Search'" data-placement="bottom">
-						<i class='fa fa-folder-open'></i>
+			<ul class="nav pull-right">
+				<li ng-show="dashboardViewState.fullscreen">
+					<a ng-click="exitFullscreen()">
+						Back to dashboard
 					</a>
 				</li>
 
-				<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='fa fa-home'></i></a></li>
-
-				<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" dash-editor-link="app/partials/dasheditor.html"><i class='fa fa-cog pointer'></i></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="grafana-menu-stop-playlist hide">
 					<a class='small' ng-click='stopPlaylist(2)'>

+ 10 - 8
src/app/partials/dasheditor.html

@@ -1,7 +1,7 @@
-<div class="dashboard-editor-header">
-	<div class="dashboard-editor-title">
+<div class="gf-box-header">
+	<div class="gf-box-title">
 		<i class="fa fa-cogs"></i>
-		Dashboard settings
+		Settings
 	</div>
 
 	<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
@@ -11,9 +11,13 @@
 		</div>
 	</div>
 
+	<button class="gf-box-header-close-btn" ng-click="dismiss();">
+		<i class="fa fa-remove"></i>
+	</button>
+
 </div>
 
-<div class="dashboard-editor-body">
+<div class="gf-box-body">
 
 		<div ng-if="editor.index == 0">
 			<div class="editor-row">
@@ -93,14 +97,12 @@
 	<div class="clearfix"></div>
 </div>
 
-<div class="dashboard-editor-footer">
+<div class="gf-box-footer">
 	<div class="grafana-version-info" ng-show="editor.index === 0">
 		<span class="editor-option small">
-			Grafana version: {{grafanaVersion}} &nbsp;&nbsp;
+			Grafana version: {{grafana.version}} &nbsp;&nbsp;
 		</span>
 		<span grafana-version-check>
 		</span>
 	</div>
-
-	<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss();reset_panel();dashboard.emit_refresh()">Close</button>
 </div>

+ 11 - 8
src/app/partials/edit_json.html

@@ -1,19 +1,22 @@
 <div ng-controller="JsonEditorCtrl">
 
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-edit"></i>
 		  JSON
 		</div>
-	</div>
 
-	<div class="dashboard-editor-body" style="height: 500px">
-		<textarea ng-model="json" rows="20" spellcheck="false" style="width: 90%;"></textarea>
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
 	</div>
 
-	<div class="dashboard-editor-footer">
-		<button type="button" class="btn btn-success pull-left" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
-		<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
+	<div class="gf-box-body" style="height: 500px">
+		<textarea ng-model="json" rows="20" spellcheck="false" style="width: 100%;"></textarea>
+		<br>
+		<br>
+
+		<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
 	</div>
 
 </div>

+ 11 - 0
src/app/partials/error.html

@@ -0,0 +1,11 @@
+
+<div class="row-fluid" style="margin-top: 100px;">
+	<div class="span2"></div>
+
+	<div class="grafana-info-box span8 text-center">
+			<h3>Page not found (404)</h3>
+		</div>
+
+	<div class="span2"></div>
+
+</div>

+ 8 - 7
src/app/partials/help_modal.html

@@ -1,12 +1,16 @@
-<div class="modal-body">
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+<div class="modal-body gf-box-no-margin">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-keyboard-o"></i>
 			Keyboard shutcuts
 		</div>
+
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
 	</div>
 
-	<div class="dashboard-editor-body">
+	<div class="gf-box-body">
 		<table class="shortcut-table">
 			<tr>
 				<th></th>
@@ -45,6 +49,3 @@
 
 </div>
 
-<div class="modal-footer">
-	<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
-</div>

+ 4 - 8
src/app/partials/inspector.html

@@ -1,6 +1,6 @@
-<div class="modal-body" ng-controller="InspectCtrl" ng-init="init()">
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+<div class="modal-body gf-box gf-box-no-margin" ng-controller="InspectCtrl" ng-init="init()">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-eye"></i>
 			Inspector
 		</div>
@@ -12,7 +12,7 @@
 
 	</div>
 
-	<div class="dashboard-editor-body">
+	<div class="gf-box-body">
 
 		<div ng-if="editor.index == 0">
 			<h5>Request details</h5>
@@ -72,9 +72,5 @@
 		</div>
 
 	</div>
-
 </div>
 
-<div class="modal-footer">
-	<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
-</div>

+ 0 - 36
src/app/partials/loadmetrics.html

@@ -1,36 +0,0 @@
-<div ng-controller="MetricKeysCtrl" ng-init="init()">
-  <h5>Load metrics keys into elastic search</h5>
-
-  <p>
-    Work in progress...
-  </p>
-  <!-- <div class="row-fluid">
-    <div class="span12">
-      <label class="small">Load metrics recursive starting from this metric path</label>
-      <input type="text" class="input-xlarge" ng-model="metricPath"> </input>
-    </div>
-  </div>
-
-  <div class="row-fluid" style="margin-top: 15px;">
-    <div class="span12">
-      <button class="btn btn-success" ng-click="createIndex()">Clear/Create index</button>
-      <button class="btn btn-success" ng-click="loadMetricsFromPath()">Load from metric path</button>
-      <button class="btn btn-danger" ng-click="loadAll()">Load all</button>
-      <tip>Load all will fetch all metrics in one call, can be intensive for graphite and for the browser if you have a lot of metrics</tip>
-    </div>
-  </div>
-
-  <div class="row-fluid" style="margin-top: 15px;">
-    <div class="span12" ng-show="infoText" style="padding-top: 10px;">
-      {{infoText}}
-    </div>
-    <div class="span12 alert alert-error" ng-show="errorText">
-      {{errorText}}
-    </div>
-  </div>
-  <div class="row-fluid" ng-show="metricCounter">
-    <div class="span12" style="padding-top: 10px;">
-      Metrics indexed: {{metricCounter}}
-    </div>
-  </div> -->
-</div>

+ 103 - 0
src/app/partials/login.html

@@ -0,0 +1,103 @@
+<div class="container">
+
+	<div class="login-box">
+
+		<div class="login-box-logo">
+			<img src="img/logo_transparent_200x75.png">
+		</div>
+
+    <div class="login-inner-box">
+			<div class="login-tab-header">
+				<button class="btn-login-tab" ng-click="loginMode = true;" ng-class="{active: loginMode}">
+					Log in
+				</button>
+				<button class="btn-login-tab" ng-click="loginMode = false;" ng-class="{active: !loginMode}" ng-show="!disableUserSignUp">
+					Sign up
+				</button>
+			</div>
+
+      <form name="loginForm" class="login-form">
+        <div class="tight-form" ng-if="loginMode">
+          <ul class="tight-form-list">
+            <li class="tight-form-item" style="width: 80px">
+							<strong>User</strong>
+            </li>
+            <li>
+              <input type="text" name="username" class="tight-form-input last" required ng-model='formModel.user' placeholder="email or username" style="width: 246px">
+            </li>
+          </ul>
+          <div class="clearfix"></div>
+        </div>
+        <div class="tight-form" ng-if="loginMode">
+          <ul class="tight-form-list">
+            <li class="tight-form-item" style="width: 80px">
+							<strong>Password</strong>
+            </li>
+            <li>
+							<input type="password" name="password" class="tight-form-input last" required ng-model="formModel.password" id="inputPassword" style="width: 246px" placeholder="password">
+            </li>
+          </ul>
+          <div class="clearfix"></div>
+				</div>
+
+				<div class="tight-form" ng-if="!loginMode">
+          <ul class="tight-form-list">
+            <li class="tight-form-item" style="width: 80px">
+							<strong>Email</strong>
+            </li>
+            <li>
+              <input type="email" class="tight-form-input last" required ng-model='formModel.email' placeholder="email" style="width: 246px">
+            </li>
+          </ul>
+          <div class="clearfix"></div>
+        </div>
+
+				<div class="tight-form" ng-if="!loginMode">
+          <ul class="tight-form-list">
+            <li class="tight-form-item" style="width: 80px">
+							<strong>Password</strong>
+            </li>
+            <li>
+							<input type="password" class="tight-form-input last" watch-change="passwordChanged(inputValue)" ng-minlength="4" required ng-model='formModel.password' placeholder="password" style="width: 246px">
+            </li>
+          </ul>
+          <div class="clearfix"></div>
+        </div>
+
+				<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">
+					<em>{{strengthText}}</em>
+				</div>
+
+
+				<div class="login-submit-button-row">
+					<button type="submit" class="btn" ng-click="submit();"
+						      ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
+						{{submitBtnText}}
+					</button>
+				</div>
+			</form>
+
+			<div class="clearfix"></div>
+
+			<div class="login-oauth text-center">
+				<a class="btn btn-google" href="login/google" target="_self" ng-if="googleAuthEnabled">
+					<i class="fa fa-google"></i>
+					with Google
+				</a>
+				<a class="btn btn-github" href="login/github" target="_self" ng-if="githubAuthEnabled">
+					<i class="fa fa-github"></i>
+					with Github
+				</a>
+			</div>
+	</div>
+
+	<div class="row" style="margin-top: 100px">
+		<div class="version-footer text-center small">
+			Grafana version: {{buildInfo.version}}, commit: {{buildInfo.commit}},
+			build date: {{buildInfo.buildstamp | date: 'yyyy-MM-dd HH:mm:ss' }}
+		</div>
+	</div>
+
+</div>
+
+

+ 14 - 0
src/app/partials/navbar.html

@@ -0,0 +1,14 @@
+<div class="navbar navbar-static-top">
+	<div class="navbar-inner">
+		<div class="container-fluid">
+			<topnav toggle="toggleSideMenu()"
+			        title="{{pageTitle}}"
+							icon="{{pageIcon}}"
+							section="{{pageSection}}"
+							show-menu-btn="!grafana.sidemenu">
+			</topnav>
+		</div>
+	</div>
+</div>
+
+

+ 0 - 22
src/app/partials/paneleditor.html

@@ -1,22 +0,0 @@
-<div bindonce class="dashboard-editor-header">
-	<div class="dashboard-editor-title">
-		<i class="fa fa-text-width"></i>
-		<span bo-text="panel.type+' settings'"></span>
-	</div>
-
-	<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-		<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
-		</div>
-	</div>
-
-</div>
-
-<div class="dashboard-editor-body">
-	<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editor.index == $index">
-		<div ng-include src="tab.src"></div>
-	</div>
-</div>
-
-<div class="dashboard-editor-footer">
-	<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss()">Close</button>
-</div>

+ 12 - 7
src/app/partials/playlist.html

@@ -1,12 +1,17 @@
 <div ng-controller="PlaylistCtrl" ng-init="init()">
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-play"></i>
 			Start dashboard playlist
 		</div>
+
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
+
 	</div>
 
-	<div class="dashboard-editor-body">
+	<div class="gf-box-body">
 
 		<div class="editor-row">
 			<div class="section">
@@ -52,11 +57,11 @@
 					<input type="text" class="input-small" ng-model="timespan" />
 				</div>
 			</div>
+
 		</div>
-	</div>
 
-	<div class="dashboard-editor-footer">
-		<button class="btn btn-success" ng-click="start();dismiss();"><i class="fa fa-play"></i> Start</button>
-		<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
+		<br>
+		<button class="btn btn-success pull-right" ng-click="start();dismiss();"><i class="fa fa-play"></i> Start</button>
+		<br>
 	</div>
 </div>

+ 37 - 39
src/app/partials/roweditor.html

@@ -1,56 +1,54 @@
-<div class="dashboard-editor-header">
-	<div class="dashboard-editor-title">
+<div class="gf-box-header">
+	<div class="gf-box-title">
 		<i class="fa fa-th-list"></i>
 		Row settings
 	</div>
 
 	<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-    <div ng-repeat="tab in ['General','Panels']" data-title="{{tab}}">
+		<div ng-repeat="tab in ['General','Panels']" data-title="{{tab}}">
 		</div>
 	</div>
 
+	<button class="gf-box-header-close-btn" ng-click="dismiss();">
+		<i class="fa fa-remove"></i>
+	</button>
 </div>
 
-<div class="dashboard-editor-body">
+<div class="gf-box-body">
 
- <div class="editor-row" ng-if="editor.index == 0">
-    <div class="editor-option">
-      <label class="small">Title</label><input type="text" class="input-medium" ng-model='row.title'></input>
-    </div>
-    <div class="editor-option">
-      <label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
-    </div>
+	<div class="editor-row" ng-if="editor.index == 0">
+		<div class="editor-option">
+			<label class="small">Title</label><input type="text" class="input-medium" ng-model='row.title'></input>
+		</div>
+		<div class="editor-option">
+			<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
+		</div>
 		<editor-opt-bool text="Editable" model="row.editable"></editor-opt-bool>
 		<editor-opt-bool text="Show title" model="row.showTitle"></editor-opt-bool>
 	</div>
-  <div class="row-fluid" ng-if="editor.index == 1">
-    <div class="span12">
+	<div class="row-fluid" ng-if="editor.index == 1">
+		<div class="span12">
 			<table class="grafana-options-table" style="max-width: 400px; width: auto">
-        <thead>
-          <th>Title</th>
-          <th>Type</th>
-          <th>Span</span></th>
-          <th></th>
-          <th></th>
-					<th></th>
-        </thead>
-        <tr ng-repeat="panel in row.panels">
-          <td style="width: 95%">{{panel.title}}</td>
-          <td>{{panel.type}}</td>
-          <td><select ng-hide="panel.sizeable == false" class="input-mini" style="margin-bottom: 0;" ng-model="panel.span" ng-options="size for size in [1,2,3,4,5,6,7,8,9,10,11,12]"></select></td>
-					<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
-					<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
-					<td>
-						<a ng-click="row.panels = _.without(row.panels,panel)" class="btn btn-danger btn-small">
-							<i class="fa fa-remove"></i>
-						</a>
-					</td>
-				</tr>
-			</table>
-		</div>
+				<thead>
+					<th>Title</th>
+					<th>Type</th>
+					<th>Span</span></th>
+				<th></th>
+				<th></th>
+				<th></th>
+			</thead>
+			<tr ng-repeat="panel in row.panels">
+				<td style="width: 95%">{{panel.title}}</td>
+				<td>{{panel.type}}</td>
+				<td><select ng-hide="panel.sizeable == false" class="input-mini" style="margin-bottom: 0;" ng-model="panel.span" ng-options="size for size in [1,2,3,4,5,6,7,8,9,10,11,12]"></select></td>
+				<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
+				<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
+				<td>
+					<a ng-click="row.panels = _.without(row.panels,panel)" class="btn btn-danger btn-small">
+						<i class="fa fa-remove"></i>
+					</a>
+				</td>
+			</tr>
+		</table>
 	</div>
 </div>
-
-<div class="dashboard-editor-footer">
-	<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss();reset_panel();close_edit()">Close</button>
-</div>

+ 7 - 8
src/app/partials/search.html

@@ -1,7 +1,7 @@
-<div ng-controller="SearchCtrl" ng-init="init()">
+<div ng-controller="SearchCtrl" ng-init="init()" class="search-box">
 
-	<div class="dashboard-editor-header">
-		<div class="dashboard-editor-title" style="border: 0; line-height: 41px;">
+	<div class="gf-box-header">
+		<div class="gf-box-title" style="border: 0; line-height: 41px;">
 			<i class="fa fa-search"></i>
 			Search
 		</div>
@@ -30,7 +30,6 @@
 	</div>
 
 	<div ng-if="!showImport">
-		<h6 ng-hide="results.dashboards.length">No dashboards matching your query were found.</h6>
 		<div class="search-results-container" ng-if="tagsOnly">
 			<div class="row">
 				<div class="span6 offset1">
@@ -47,13 +46,12 @@
 		</div>
 
 		<div class="search-results-container" ng-if="!tagsOnly">
+			<h6 ng-hide="results.dashboards.length">No dashboards matching your query were found.</h6>
+
 			<div class="search-result-item pointer" bindonce ng-repeat="row in results.dashboards"
-				ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.id)">
+				ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.slug)">
 
 				<div class="search-result-actions small">
-					<a ng-click="shareDashboard(row.id, row.id, $event)" config-modal="app/partials/dashLoaderShare.html">
-						<i class="fa fa-share"></i> share &nbsp;&nbsp;&nbsp;
-					</a>
 					<a ng-click="deleteDashboard(row, $event)">
 						<i class="fa fa-remove"></i> delete
 					</a>
@@ -63,6 +61,7 @@
 					<a ng-click="filterByTag(tag, $event)" ng-repeat="tag in row.tags" tag-color-from-name  class="label label-tag">
 						{{tag}}
 					</a>
+					<i class="fa" ng-class="{'fa-star': row.isStarred, 'fa-star-o': !row.isStarred}"></i>
 				</div>
 
 				<a class="search-result-link">

+ 0 - 33
src/app/partials/share-panel.html

@@ -1,33 +0,0 @@
-<div ng-controller="SharePanelCtrl">
-	<div class="modal-header">
-		<div class="dashboard-editor-header">
-			<div class="dashboard-editor-title">
-				<i class="fa fa-share"></i>
-				Share
-			</div>
-
-			<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-				<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
-				</div>
-			</div>
-
-		</div>
-	</div>
-
-	<div class="modal-body">
-
-		<div class="editor-row">
-			<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
-			<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
-			<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
-		</div>
-
-		<div class="editor-row" style="margin-top: 20px;">
-			<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
-		</div>
-	</div>
-
-	<div class="modal-footer">
-		<button class="btn btn-success pull-right" ng-click="dismiss();">close</button>
-	</div>
-</div>

+ 18 - 0
src/app/partials/shareDashboard.html

@@ -0,0 +1,18 @@
+<div class="modal-body gf-box gf-box-no-margin">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
+			<i class="fa fa-share-alt"></i>
+			Share dashboard
+		</div>
+
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
+	</div>
+
+	<div class="gf-box-body">
+		<label>Share this dashboard with this URL</label>
+		<input ng-model='share.url' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()">
+	</div>
+
+</div>

+ 39 - 0
src/app/partials/sidemenu.html

@@ -0,0 +1,39 @@
+
+<div ng-controller="SideMenuCtrl" ng-init="init()">
+	<ul class="sidemenu">
+		<li style="margin-bottom: 2px;">
+			<a class="pointer sidemenu-top-btn" ng-click="toggleSideMenu()">
+				<img class="logo-icon" src="img/fav32.png"></img>
+				<i class="pull-right fa fa-angle-left"></i>
+			</a>
+		</li>
+
+		<li ng-repeat="item in menu">
+			<a href="{{item.href}}" class="sidemenu-item" target="{{item.target}}">
+				<span class="sidemenu-icon"><i class="{{item.icon}}"></i></span>
+				<span class="sidemenu-item-text">{{item.text}}</span>
+	   	</a>
+		</li>
+
+		<li ng-if="grafana.user.isSignedIn" style="margin-top:50px">
+			<a href="profile" class="sidemenu-item sidemenu-user">
+				<img ng-src="{{grafana.user.gravatarUrl}}">
+				<span class="sidemenu-item-text">{{grafana.user.name}}</a>
+			</a>
+		</li>
+
+		<li ng-if="grafana.user.isSignedIn">
+			<a href="logout" class="sidemenu-item" target="_self">
+				<span class="sidemenu-item-text no-icon">Sign out</span>
+	   	</a>
+		</li>
+
+		<li ng-if="!grafana.user.isSignedIn" style="margin-top:50px">
+			<a href="/login" class="sidemenu-item" target="_self">
+				<span class="sidemenu-icon"><i class="fa fa-sign-in"></i></span>
+				<span class="sidemenu-item-text">Sign in</span>
+	   	</a>
+		</li>
+
+	</ul>
+</div>

+ 0 - 14
src/app/partials/submenu.html

@@ -1,20 +1,6 @@
 <div class="submenu-controls" ng-controller="SubmenuCtrl">
 	<div class="tight-form" style="border-top: none">
 
-		<ul class="tight-form-list">
-			<li class="tight-form-item">
-				<div class="dropdown">
-					<a class="pointer" data-toggle="dropdown">
-						<i class="fa fa-cog"></i>
-					</a>
-					<ul class="dropdown-menu">
-						<li><a class="pointer" dash-editor-link="app/partials/templating_editor.html">Templating</a></li>
-						<li><a class="pointer" dash-editor-link="app/features/annotations/partials/editor.html">Annotations</a></li>
-					</ul>
-				</div>
-			</li>
-		</ul>
-
 		<ul class="tight-form-list" ng-if="dashboard.templating.enable">
 			<li ng-repeat-start="variable in variables" class="tight-form-item template-param-name">
 				<span class="template-variable ">

+ 77 - 78
src/app/partials/templating_editor.html

@@ -1,17 +1,23 @@
-<div ng-controller="TemplateEditorCtrl" ng-init="init()"> <div class="dashboard-editor-header">
-		<div class="dashboard-editor-title">
+<div ng-controller="TemplateEditorCtrl" ng-init="init()">
+	<div class="gf-box-header">
+		<div class="gf-box-title">
 			<i class="fa fa-code"></i>
 			Templating
 		</div>
 
+
 		<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
 			<div ng-repeat="tab in ['Variables', 'Add', 'Edit']" data-title="{{tab}}">
 			</div>
 		</div>
 
+		<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
+			<i class="fa fa-remove"></i>
+		</button>
+
 	</div>
 
-	<div class="dashboard-editor-body">
+	<div class="gf-box-body">
 
 		<div ng-if="editor.index == 0">
 
@@ -50,104 +56,97 @@
 		</div>
 
 		<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
-			<div class="row">
-				<div class="editor-option">
+			<div class="editor-option">
+				<div class="editor-row">
+					<div class="editor-option">
+						<label class="small">Variable name</label>
+						<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
+					</div>
+					<div class="editor-option">
+						<label class="small">Type</label>
+						<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
+					</div>
+					<div class="editor-option" ng-show="current.type === 'query'">
+						<label class="small">Datasource</label>
+						<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
+					</div>
+
+					<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
+						tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
+						model="current.refresh"></editor-opt-bool>
+				</div>
+
+				<div ng-show="current.type === 'interval'">
 					<div class="editor-row">
 						<div class="editor-option">
-							<label class="small">Variable name</label>
-							<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
-						</div>
-						<div class="editor-option">
-							<label class="small">Type</label>
-							<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
+							<label class="small">Values</label>
+							<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
 						</div>
-						<div class="editor-option" ng-show="current.type === 'query'">
-							<label class="small">Datasource</label>
-							<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
+					</div>
+					<div class="editor-row">
+						<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
+						<div class="editor-option" ng-show="current.auto">
+							<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
+							<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
 						</div>
-
-						<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
-						                 tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
-						                 model="current.refresh"></editor-opt-bool>
 					</div>
+				</div>
 
-					<div ng-show="current.type === 'interval'">
-						<div class="editor-row">
-							<div class="editor-option">
-								<label class="small">Values</label>
-								<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
-							</div>
-						</div>
-						<div class="editor-row">
-							<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
-							<div class="editor-option" ng-show="current.auto">
-								<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
-								<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
-							</div>
+				<div ng-show="current.type === 'custom'">
+					<div class="editor-row">
+						<div class="editor-option">
+							<label class="small">Values seperated by comma</label>
+							<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
 						</div>
 					</div>
+				</div>
 
-					<div ng-show="current.type === 'custom'">
-						<div class="editor-row">
-							<div class="editor-option">
-								<label class="small">Values seperated by comma</label>
-								<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
-							</div>
+				<div ng-show="current.type === 'query'">
+					<div class="editor-row">
+						<div class="editor-option form-inline">
+							<label class="small">Variable values query</label>
+							<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
+							<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
 						</div>
 					</div>
 
-					<div ng-show="current.type === 'query'">
-						<div class="editor-row">
-							<div class="editor-option form-inline">
-								<label class="small">Variable values query</label>
-								<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
-								<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
-							</div>
+					<div class="editor-row" style="margin: 15px 0">
+						<div class="editor-option form-inline">
+							<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
+							<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
+							<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
 						</div>
+					</div>
 
-						<div class="editor-row" style="margin: 15px 0">
-							<div class="editor-option form-inline">
-								<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
-								<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
-								<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
-							</div>
+					<div class="editor-row" style="margin: 15px 0">
+						<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
+						<div class="editor-option" ng-show="current.includeAll">
+							<label class="small">All format</label>
+							<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
 						</div>
-
-						<div class="editor-row" style="margin: 15px 0">
-							<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
-							<div class="editor-option" ng-show="current.includeAll">
-								<label class="small">All format</label>
-								<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
-							</div>
-							<div class="editor-option" ng-show="current.includeAll">
-								<label class="small">All value</label>
-								<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
-							</div>
+						<div class="editor-option" ng-show="current.includeAll">
+							<label class="small">All value</label>
+							<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
 						</div>
 					</div>
 				</div>
-				<div class="editor-option">
-					<div class="editor-row">
-						<div class="editor-option" >
-							<label class="small">Variable values (showing 20/{{current.options.length}})</label>
-							<ul class="grafana-options-list">
-								<li ng-repeat="option in current.options | limitTo: 20">
-									{{option.text}}
-								</li>
-							</ul>
-						</div>
+			</div>
+			<div class="editor-option">
+				<div class="editor-row">
+					<div class="editor-option" >
+						<label class="small">Variable values (showing 20/{{current.options.length}})</label>
+						<ul class="grafana-options-list">
+							<li ng-repeat="option in current.options | limitTo: 20">
+								{{option.text}}
+							</li>
+						</ul>
 					</div>
 				</div>
 			</div>
-
 		</div>
 
-	</div>
-
-	<div class="dashboard-editor-footer">
-		<button type="button" class="btn btn-success pull-left" ng-show="editor.index === 2" ng-click="update();">Update</button>
-		<button type="button" class="btn btn-success pull-left" ng-show="editor.index === 1" ng-click="add();">Add</button>
-		<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
+		<button type="button" class="btn btn-success" ng-show="editor.index === 2" ng-click="update();">Update</button>
+		<button type="button" class="btn btn-success" ng-show="editor.index === 1" ng-click="add();">Add</button>
 	</div>
 </div>
 

+ 17 - 15
src/app/partials/unsaved-changes.html

@@ -1,18 +1,20 @@
-<div class="modal-header">
-</div>
-
-<div class="modal-body">
-  <h4 class="text-center"><i class="fa fa-warning"></i> Unsaved changes</h4>
-  <div class="row-fluid">
-		<span class="span3">
-			{{changes}}
+<div class="modal-body gf-box gf-box-no-margin">
+	<div class="gf-box-header text-center">
+		<span class="gf-box-title">
+			<i class="fa fa-exclamation"></i>
+			Unsaved changes
 		</span>
-	<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
-	<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
-	<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>
-	<span class="span3"></span>
-  </div>
-</div>
+		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</button>
+	</div>
 
-<div class="modal-footer">
+	<div class="gf-box-body" style="min-height: 0;">
+		<div class="row-fluid text-center">
+			<button type="button" class="btn btn-success span4" ng-click="save();dismiss();">Save</button>
+			<button type="button" class="btn btn-danger span4" ng-click="ignore();dismiss();">Ignore</button>
+			<button type="button" class="btn btn-inverse span4" ng-click="dismiss()">Cancel</button>
+		</div>
+	</div>
 </div>
+

+ 79 - 0
src/app/routes/backend/all.js

@@ -0,0 +1,79 @@
+define([
+  'angular',
+  './dashboard',
+], function(angular) {
+  "use strict";
+
+  var module = angular.module('grafana.routes');
+
+  module.config(function($routeProvider, $locationProvider) {
+    $locationProvider.html5Mode(true);
+
+    $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/import/:id', {
+        templateUrl: 'app/partials/dashboard.html',
+        controller : 'DashFromImportCtrl',
+        reloadOnSearch: false,
+      })
+      .when('/dashboard/new', {
+        templateUrl: 'app/partials/dashboard.html',
+        controller : 'NewDashboardCtrl',
+        reloadOnSearch: false,
+      })
+      .when('/account', {
+        templateUrl: 'app/features/account/partials/account.html',
+        controller : 'AccountCtrl',
+      })
+      .when('/account/datasources', {
+        templateUrl: 'app/features/account/partials/datasources.html',
+        controller : 'DataSourcesCtrl',
+      })
+      .when('/account/users', {
+        templateUrl: 'app/features/account/partials/users.html',
+        controller : 'AccountUsersCtrl',
+      })
+      .when('/account/apikeys', {
+        templateUrl: 'app/features/account/partials/apikeys.html',
+        controller : 'ApiKeysCtrl',
+      })
+      .when('/account/import', {
+        templateUrl: 'app/features/account/partials/import.html',
+        controller : 'ImportCtrl',
+      })
+      .when('/profile', {
+        templateUrl: 'app/features/profile/partials/profile.html',
+        controller : 'ProfileCtrl',
+      })
+      .when('/admin/settings', {
+        templateUrl: 'app/features/admin/partials/settings.html',
+        controller : 'AdminSettingsCtrl',
+      })
+      .when('/admin/users', {
+        templateUrl: 'app/features/admin/partials/users.html',
+        controller : 'AdminUsersCtrl',
+      })
+      .when('/login', {
+        templateUrl: 'app/partials/login.html',
+        controller : 'LoginCtrl',
+      })
+      .when('/dashboard/solo/:id/', {
+        templateUrl: 'app/features/panel/partials/soloPanel.html',
+        controller : 'SoloPanelCtrl',
+      })
+      .otherwise({
+        templateUrl: 'app/partials/error.html',
+        controller: 'ErrorCtrl'
+      });
+  });
+
+});

+ 54 - 0
src/app/routes/backend/dashboard.js

@@ -0,0 +1,54 @@
+define([
+  'angular',
+  'store',
+],
+function (angular) {
+  "use strict";
+
+  var module = angular.module('grafana.routes');
+
+  module.controller('DashFromDBProvider', function($scope, datasourceSrv, $routeParams, backendSrv) {
+
+    var db = datasourceSrv.getGrafanaDB();
+
+    if (!$routeParams.id) {
+      backendSrv.get('/api/dashboards/home').then(function(result) {
+        $scope.initDashboard(result, $scope);
+      },function() {
+        $scope.initDashboard({}, $scope);
+        $scope.appEvent('alert-error', ['Load dashboard failed', '']);
+      });
+
+      return;
+    }
+
+    db.getDashboard($routeParams.id, false).then(function(result) {
+      $scope.initDashboard(result, $scope);
+    }).then(null, function() {
+      $scope.initDashboard({
+        meta: {},
+        model: { title: 'Not found' }
+      }, $scope);
+    });
+  });
+
+  module.controller('DashFromImportCtrl', function($scope, $location, alertSrv) {
+    if (!window.grafanaImportDashboard) {
+      alertSrv.set('Not found', 'Cannot reload page with unsaved imported dashboard', 'warning', 7000);
+      $location.path('');
+      return;
+    }
+    $scope.initDashboard({ meta: {}, model: window.grafanaImportDashboard }, $scope);
+  });
+
+  module.controller('NewDashboardCtrl', function($scope) {
+    $scope.initDashboard({
+      meta: {},
+      model: {
+        title: "New dashboard",
+        rows: [{ height: '250px', panels:[] }]
+      },
+    }, $scope);
+  });
+
+});

+ 24 - 0
src/app/routes/standalone/all.js

@@ -0,0 +1,24 @@
+define([
+  'angular',
+  'config',
+  'store',
+  './fromDB',
+  './fromFile',
+  './fromScript',
+],
+function (angular, config, store) {
+  'use strict';
+
+  var module = angular.module('grafana.routes.standalone');
+
+  module.config(function($routeProvider) {
+    $routeProvider
+      .otherwise({ redirectTo: config.default_route })
+      .when('/', {
+        redirectTo: function() {
+          return store.get('grafanaDashboardDefault') || config.default_route;
+        }
+      });
+  });
+
+});

+ 0 - 23
src/app/routes/standalone/default.js

@@ -1,23 +0,0 @@
-define([
-  'angular',
-  'config',
-  'store',
-  './fromDB',
-  './fromFile',
-  './fromScript',
-],
-function (angular, config, store) {
-  'use strict';
-
-  var module = angular.module('grafana.routes');
-
-  module.config(function($routeProvider) {
-    $routeProvider
-    .when('/', {
-      redirectTo: function() {
-        return store.get('grafanaDashboardDefault') || config.default_route;
-      }
-    });
-  });
-
-});

+ 1 - 1
src/app/routes/standalone/fromDB.js

@@ -4,7 +4,7 @@ define([
 function (angular) {
   "use strict";
 
-  var module = angular.module('grafana.routes');
+  var module = angular.module('grafana.routes.standalone');
 
   module.config(function($routeProvider) {
     $routeProvider

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor