Просмотр исходного кода

Merge branch 'master' of github.com:torkelo/grafana-private into pro

Torkel Ödegaard 11 лет назад
Родитель
Сommit
9f4ea7301a

+ 5 - 0
CHANGELOG.md

@@ -1,8 +1,13 @@
 # 2.0.0 (unreleased)
 
+**New features**
+- [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
+
 **Enhancements**
 - [Issue #1297](https://github.com/grafana/grafana/issues/1297). Graphite: Added cumulative and minimumBelow graphite functions
 - [Issue #1296](https://github.com/grafana/grafana/issues/1296). InfluxDB: Auto escape column names with special characters. Thanks @steven-aerts
+- [Issue #1321](https://github.com/grafana/grafana/issues/1321). SingleStatPanel: You can now use template variables in pre & postfix
+- [Issue #599](https://github.com/grafana/grafana/issues/599).   Graph: Added right y axis label setting and graph support
 
 **Fixes**
 - [Issue #1298](https://github.com/grafana/grafana/issues/1298). InfluxDB: Fix handling of empty array in templating variable query

+ 51 - 0
src/app/components/kbn.js

@@ -362,8 +362,14 @@ function($, _, moment) {
 
   kbn.valueFormats.bits = kbn.formatFuncCreator(1024, [' b', ' Kib', ' Mib', ' Gib', ' Tib', ' Pib', ' Eib', ' Zib', ' Yib']);
   kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
+  kbn.valueFormats.kbytes = kbn.formatFuncCreator(1024, [' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
+  kbn.valueFormats.mbytes = kbn.formatFuncCreator(1024, [' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
   kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
+  kbn.valueFormats.Bps = kbn.formatFuncCreator(1000, [' Bps', ' KBps', ' MBps', ' GBps', ' TBps', ' PBps', ' EBps', ' ZBps', ' YBps']);
   kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
+  kbn.valueFormats.joule = kbn.formatFuncCreator(1000, [' J', ' kJ', ' MJ', 'GJ', 'TJ', 'PJ', 'EJ', 'ZJ', 'YJ']);
+  kbn.valueFormats.watt = kbn.formatFuncCreator(1000, [' W', ' kW', ' MW', 'GW', 'TW', 'PW', 'EW', 'ZW', 'YW']);
+  kbn.valueFormats.ev = kbn.formatFuncCreator(1000, [' eV', ' keV', ' MeV', 'GeV', 'TeV', 'PeV', 'EeV', 'ZeV', 'YeV']);
   kbn.valueFormats.none = kbn.toFixed;
 
   kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
@@ -479,5 +485,50 @@ function($, _, moment) {
     return new RegExp(match[1], match[2]);
   };
 
+  kbn.getUnitFormats = function() {
+    return [
+      {
+        text: 'none',
+        submenu: [
+          {text: 'none' , value: 'none'},
+          {text: 'short', value: 'short'},
+          {text: 'percent', value: 'percent'},
+        ]
+      },
+      {
+        text: 'duration',
+        submenu: [
+          {text: 'nanoseconds (ns)' , value: 'ns'},
+          {text: 'microseconds (µs)', value: 'µs'},
+          {text: 'milliseconds (ms)', value: 'ms'},
+          {text: 'seconds (s)', value: 's'},
+        ]
+      },
+      {
+        text: 'data',
+        submenu: [
+          {text: 'bits', value: 'bits'},
+          {text: 'bytes', value: 'bytes'},
+          {text: 'kilobytes', value: 'kbytes'},
+        ]
+      },
+      {
+        text: 'data rate',
+        submenu: [
+          {text: 'bits/sec', value: 'bps'},
+          {text: 'bytes/sec', value: 'Bps'},
+        ]
+      },
+      {
+        text: 'energy',
+        submenu: [
+          {text: 'watt', value: 'watt'},
+          {text: 'joule', value: 'joule'},
+          {text: 'eV', value: 'ev'},
+        ]
+      },
+    ];
+  };
+
   return kbn;
 });

+ 1 - 0
src/app/directives/arrayJoin.js

@@ -31,4 +31,5 @@ function (angular, app, _) {
         }
       };
     });
+
 });

+ 31 - 20
src/app/directives/dropdown.typeahead.js

@@ -17,35 +17,47 @@ function (angular, app, _, $) {
 
       var buttonTemplate = '<a  class="grafana-target-segment grafana-target-function dropdown-toggle"' +
                               ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
-                              ' data-placement="top"><i class="icon-plus"></i></a>';
+                              ' data-placement="top"><i class="fa fa-plus"></i></a>';
 
       return {
         scope: {
-          "menuItems": "=dropdownTypeahead",
-          "dropdownTypeaheadOnSelect": "&dropdownTypeaheadOnSelect"
+          menuItems: "=dropdownTypeahead",
+          dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect",
+          model: '=ngModel'
         },
-        link: function($scope, elem) {
+        link: function($scope, elem, attrs) {
           var $input = $(inputTemplate);
           var $button = $(buttonTemplate);
           $input.appendTo(elem);
           $button.appendTo(elem);
 
-          var typeaheadValues = _.reduce($scope.menuItems, function(memo, value) {
-            _.each(value.submenu, function(item) {
+          if (attrs.linkText) {
+            $button.html(attrs.linkText);
+          }
+
+          if (attrs.ngModel) {
+            $scope.$watch('model', function(newValue) {
+              _.each($scope.menuItems, function(item) {
+                _.each(item.submenu, function(subItem) {
+                  if (subItem.value === newValue) {
+                    $button.html(subItem.text);
+                  }
+                });
+              });
+            });
+          }
+
+          var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
+            _.each(value.submenu, function(item, subIndex) {
+              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
               memo.push(value.text + ' ' + item.text);
             });
             return memo;
           }, []);
 
-          $scope.menuItemSelected = function(optionIndex, valueIndex) {
-            var option = $scope.menuItems[optionIndex];
-            var result = {
-              $item: option.submenu[valueIndex],
-              $optionIndex: optionIndex,
-              $valueIndex: valueIndex
-            };
-
-            $scope.dropdownTypeaheadOnSelect(result);
+          $scope.menuItemSelected = function(index, subIndex) {
+            var item = $scope.menuItems[index];
+            $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]});
           };
 
           $input.attr('data-provide', 'typeahead');
@@ -55,12 +67,11 @@ function (angular, app, _, $) {
             items: 10,
             updater: function (value) {
               var result = {};
-              _.each($scope.menuItems, function(menuItem, optionIndex) {
-                _.each(menuItem.submenu, function(submenuItem, valueIndex) {
+              _.each($scope.menuItems, function(menuItem) {
+                _.each(menuItem.submenu, function(submenuItem) {
                   if (value === (menuItem.text + ' ' + submenuItem.text)) {
-                    result.$item  = submenuItem;
-                    result.$optionIndex = optionIndex;
-                    result.$valueIndex = valueIndex;
+                    result.$item = menuItem;
+                    result.$subItem = submenuItem;
                   }
                 });
               });

+ 12 - 0
src/app/directives/ngModelOnBlur.js

@@ -22,5 +22,17 @@ function (angular) {
           });
         }
       };
+    })
+    .directive('emptyToNull', function () {
+      return {
+        restrict: 'A',
+        require: 'ngModel',
+        link: function (scope, elm, attrs, ctrl) {
+          ctrl.$parsers.push(function (viewValue) {
+            if(viewValue === "") { return null; }
+            return viewValue;
+          });
+        }
+      };
     });
 });

+ 2 - 2
src/app/features/annotations/partials/editor.html

@@ -53,7 +53,7 @@
 					<label class="small">Datasource</label>
 					<select ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
 				</div>
-				<div class="editor-option">
+				<div class="editor-option text-center">
 					<label class="small">Icon color</label>
 					<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
 				</div>
@@ -62,7 +62,7 @@
 					<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
 				</div>
 				<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool>
-				<div class="editor-option">
+				<div class="editor-option text-center">
 					<label class="small">Line color</label>
 					<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
 				</div>

+ 50 - 48
src/app/features/graphite/partials/query.editor.html

@@ -7,66 +7,68 @@
         ng-init="init()">
 
     <div class="grafana-target-inner">
-      <ul class="grafana-target-controls">
-        <li ng-show="parserError">
+      <ul class="grafana-segment-list pull-right">
+        <li ng-show="parserError" class="grafana-target-segment">
           <a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
             <i class="fa fa-warning"></i>
           </a>
         </li>
-        <li>
+        <li class="grafana-target-segment">
           <a class="pointer" tabindex="1" ng-click="showTextEditor = !showTextEditor">
             <i class="fa fa-pencil"></i>
           </a>
         </li>
-        <li class="dropdown">
-          <a  class="pointer dropdown-toggle"
-              data-toggle="dropdown"
-              tabindex="1">
-            <i class="fa fa-bars"></i>
-          </a>
-          <ul class="dropdown-menu pull-right" role="menu">
-            <li role="menuitem">
-              <a  tabindex="1"
-                  ng-click="duplicate()">
-                Duplicate
-              </a>
-            </li>
-						<li role="menuitem">
-              <a  tabindex="1"
-                  ng-click="moveMetricQuery($index, $index-1)">
-                Move up
-              </a>
-            </li>
-						<li role="menuitem">
-							<a  tabindex="1"
-                  ng-click="moveMetricQuery($index, $index+1)">
-                Move down
-              </a>
-            </li>
-          </ul>
-        </li>
-        <li>
-          <a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
-            <i class="fa fa-remove"></i>
-          </a>
-        </li>
-      </ul>
+        <li class="grafana-target-segment">
+					<div class="dropdown">
+						<a  class="pointer dropdown-toggle"
+							data-toggle="dropdown"
+							tabindex="1">
+							<i class="fa fa-bars"></i>
+						</a>
+						<ul class="dropdown-menu pull-right" role="menu">
+							<li role="menuitem">
+								<a  tabindex="1"
+									ng-click="duplicate()">
+									Duplicate
+								</a>
+							</li>
+							<li role="menuitem">
+								<a  tabindex="1"
+									ng-click="moveMetricQuery($index, $index-1)">
+									Move up
+								</a>
+							</li>
+							<li role="menuitem">
+								<a  tabindex="1"
+									ng-click="moveMetricQuery($index, $index+1)">
+									Move down
+								</a>
+							</li>
+						</ul>
+					</div>
+				</li>
+				<li class="grafana-target-segment last">
+					<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+						<i class="fa fa-remove"></i>
+					</a>
+				</li>
+			</ul>
 
-      <ul class="grafana-segment-list">
+			<ul class="grafana-segment-list">
 				<li class="grafana-target-segment" style="min-width: 15px; text-align: center">
 					{{targetLetters[$index]}}
-        </li>
-        <li>
-          <a  class="grafana-target-segment"
-              ng-click="target.hide = !target.hide; get_data();"
-              role="menuitem">
-            <i class="fa fa-eye"></i>
-          </a>
-        </li>
-      </ul>
+				</li>
+				<li>
+					<a  class="grafana-target-segment"
+						ng-click="target.hide = !target.hide; get_data();"
+						role="menuitem">
+						<i class="fa fa-eye"></i>
+					</a>
+				</li>
+			</ul>
 
-      <input  type="text"
-              class="grafana-target-text-input span10"
+			<input  type="text"
+			class="grafana-target-text-input span10"
               ng-model="target.target"
               focus-me="showTextEditor"
               spellcheck='false'

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

@@ -133,4 +133,4 @@ function (angular, _, $) {
 
     });
 
-});
+});

+ 29 - 27
src/app/features/influxdb/partials/query.editor.html

@@ -8,22 +8,24 @@
 
     <div class="grafana-target-inner-wrapper">
       <div class="grafana-target-inner">
-        <ul class="grafana-target-controls">
-          <li class="dropdown">
-            <a class="pointer dropdown-toggle"
-               data-toggle="dropdown"
-               tabindex="1">
-              <i class="fa fa-bars"></i>
-            </a>
-						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
-						</ul>
+				<ul class="grafana-segment-list pull-right">
+					<li class="grafana-target-segment">
+						<div class="dropdown">
+							<a class="pointer dropdown-toggle"
+								data-toggle="dropdown"
+								tabindex="1">
+								<i class="fa fa-bars"></i>
+							</a>
+							<ul class="dropdown-menu pull-right" role="menu">
+								<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
+								<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
+								<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
+								<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
+								<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
+							</ul>
+						</div>
 					</li>
-					<li>
+					<li class="grafana-target-segment last">
 						<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
 							<i class="fa fa-remove"></i>
 						</a>
@@ -42,7 +44,7 @@
 				<ul class="grafana-segment-list" ng-show="target.rawQuery">
 					<li>
 						<input type="text"
-               class="grafana-target-text-input span10"
+               class="grafana-target-segment-input span10"
                ng-model="target.query"
                placeholder="select ..."
                focus-me="target.rawQuery"
@@ -60,7 +62,7 @@
 					</li>
           <li>
             <input type="text"
-                   class="grafana-target-text-input span8"
+                   class="grafana-target-segment-input span8"
                    ng-model="target.series"
                    spellcheck='false'
                    bs-typeahead="listSeries"
@@ -76,7 +78,7 @@
 					</li>
 
 					<li>
-						<input type="text" class="input-medium grafana-target-text-input" ng-model="target.alias"
+						<input type="text" class="input-medium grafana-target-segment-input" ng-model="target.alias"
 						spellcheck='false' placeholder="alias" ng-blur="get_data()">
 					</li>
 
@@ -89,14 +91,14 @@
 				<!-- Raw Query mode  -->
 				<ul class="grafana-segment-list" ng-show="target.rawQuery">
 					<li class="grafana-target-segment">
-						<i class="fa eye invisible"></i>
+						<i class="fa fa-eye invisible"></i>
 					</li>
 					<li class="grafana-target-segment">
             alias
           </li>
           <li>
             <input type="text"
-                   class="input-medium grafana-target-text-input"
+                   class="input-medium grafana-target-segment-input"
                    ng-model="target.alias"
                    spellcheck='false'
                    placeholder="alias"
@@ -106,7 +108,7 @@
 						group by time
 					</li>
 					<li>
-						<input type="text" class="input-mini grafana-target-text-input" ng-model="target.interval"
+						<input type="text" class="input-mini grafana-target-segment-input" ng-model="target.interval"
 									 spellcheck='false' placeholder="{{interval}}" data-placement="right"
 									 bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
 									 ng-model-onblur ng-change="get_data()" >
@@ -122,7 +124,7 @@
 						select
 					</li>
 					<li class="dropdown">
-						<span influxdb-func-editor class="grafana-target-segment">
+						<span influxdb-func-editor class="grafana-target-segment grafana-target-function">
 						</span>
 					</li>
 
@@ -130,7 +132,7 @@
 						where
 					</li>
 					<li>
-						<input type="text" class="input-medium grafana-target-text-input" ng-model="target.condition"
+						<input type="text" class="input-medium grafana-target-segment-input" ng-model="target.condition"
 									 bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
 					</li>
 
@@ -138,18 +140,18 @@
 						group by time
 					</li>
 					<li>
-						<input type="text" class="input-mini grafana-target-text-input" ng-model="target.interval"
+						<input type="text" class="input-mini grafana-target-segment-input" ng-model="target.interval"
 									 spellcheck='false' placeholder="{{interval}}" data-placement="right"
 									 bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
 									 ng-model-onblur ng-change="get_data()" >
 					</li>
 
 					<li class="grafana-target-segment">
-							<i class="fa fa-plus"></i>
+						and
 					</li>
 
 					<li>
-						<input type="text" class="input-small grafana-target-text-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
+						<input type="text" class="input-small grafana-target-segment-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
 									 placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
 					</li>
 
@@ -190,7 +192,7 @@
 					group by time
 				</li>
 				<li>
-					<input type="text" class="input-medium grafana-target-text-input" ng-model="panel.interval" ng-blur="get_data();"
+					<input type="text" class="input-medium grafana-target-segment-input" ng-model="panel.interval" ng-blur="get_data();"
 					       spellcheck='false' placeholder="example: >10s">
 				</li>
 				<li class="grafana-target-segment">

+ 20 - 18
src/app/features/opentsdb/partials/query.editor.html

@@ -7,24 +7,26 @@
 
     <div class="grafana-target-inner-wrapper">
       <div class="grafana-target-inner">
-        <ul class="grafana-target-controls">
-          <li class="dropdown">
-            <a  class="pointer dropdown-toggle"
-                data-toggle="dropdown"
-                tabindex="1">
-              <i class="fa fa-bars"></i>
-            </a>
-            <ul class="dropdown-menu pull-right" role="menu">
-              <li role="menuitem">
-                <a  tabindex="1"
-                    ng-click="duplicate()">
-                  Duplicate
-                </a>
-              </li>
-            </ul>
-          </li>
-          <li>
-            <a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+				<ul class="grafana-segment-list pull-right">
+					<li class="grafana-target-segment">
+						<div class="dropdown">
+							<a  class="pointer dropdown-toggle"
+								data-toggle="dropdown"
+								tabindex="1">
+								<i class="fa fa-bars"></i>
+							</a>
+							<ul class="dropdown-menu pull-right" role="menu">
+								<li role="menuitem">
+									<a  tabindex="1"
+										ng-click="duplicate()">
+										Duplicate
+									</a>
+								</li>
+							</ul>
+						</div>
+					</li>
+					<li class="grafana-target-segment last">
+						<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
               <i class="fa fa-remove"></i>
             </a>
           </li>

+ 220 - 79
src/app/panels/graph/axisEditor.html

@@ -1,86 +1,227 @@
 
 <div class="editor-row">
-  <div class="section">
-    <h5>Left Y Axis</h5>
-       <div class="editor-option">
-        <label class="small">Format <tip>Y-axis formatting</tip></label>
-        <select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
-      </div>
-      <div class="editor-option">
-        <label class="small">Min / <a ng-click="toggleGridMinMax('leftMin')">Auto <i class="fa fa-star" ng-show="_.isNull(panel.grid.leftMin)"></i></a></label>
-        <input type="number" class="input-small" ng-model="panel.grid.leftMin" ng-change="render()" ng-model-onblur />
-      </div>
-      <div class="editor-option">
-        <label class="small">Max / <a ng-click="toggleGridMinMax('leftMax')">Auto <i class="fa fa-star" ng-show="_.isNull(panel.grid.leftMax)"></i></a></label>
-        <input type="number" class="input-small" ng-model="panel.grid.leftMax" ng-change="render()" ng-model-onblur />
-      </div>
-      <div class="editor-option">
-        <label class="small">Label</label>
-        <input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
-      </div>
-  </div>
-  <div class="section">
-    <h5>Right Y Axis</h5>
-       <div class="editor-option">
-        <label class="small">Format <tip>Y-axis formatting</tip></label>
-        <select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
-      </div>
-      <div class="editor-option">
-        <label class="small">Min / <a ng-click="toggleGridMinMax('rightMin')">Auto <i class="fa fa-star" ng-show="_.isNull(panel.grid.rightMin)"></i></a></label>
-        <input type="number" class="input-small" ng-model="panel.grid.rightMin" ng-change="render()" ng-model-onblur />
-      </div>
-      <div class="editor-option">
-        <label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="fa fa-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label>
-        <input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur />
-      </div>
-  </div>
-</div>
-
-
-<div class="editor-row">
-  <div class="section">
-    <h5>Legend styles</h5>
-		<editor-opt-bool text="Show" model="panel.legend.show" change="get_data();"></editor-opt-bool>
-		<editor-opt-bool text="Values" model="panel.legend.values" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Table" model="panel.legend.alignAsTable" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Right side" model="panel.legend.rightSide" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Hide empty" model="panel.legend.hideEmpty" tip="Hides series with only null values" change="render()"></editor-opt-bool>
+	<div class="section" style="margin-bottom: 20px">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Left Y</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Unit
+					</li>
+					<li class="dropdown" style="width: 130px;"
+						ng-model="panel.y_formats[0]"
+						dropdown-typeahead="unitFormats"
+						dropdown-typeahead-on-select="setUnitFormat(0, $subItem)">
+					</li>
+					<li class="grafana-target-segment">
+						&nbsp;&nbsp; Grid Max
+					</li>
+					<li>
+						<input type="number" class="input-small grafana-target-segment-input" placeholder="auto"
+					   	empty-to-null ng-model="panel.grid.leftMax"
+							ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						Min
+					</li>
+					<li>
+						<input type="number" class="input-small grafana-target-segment-input" placeholder="auto"
+					  	empty-to-null ng-model="panel.grid.leftMin"
+							ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						Label
+					</li>
+					<li>
+						<input type="text" class="input-small grafana-target-segment-input last"
+					  	ng-model="panel.leftYAxisLabel" ng-change="render()" ng-model-onblur>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Right Y</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Unit
+					</li>
+					<li class="dropdown" style="width: 130px"
+						ng-model="panel.y_formats[1]"
+						dropdown-typeahead="unitFormats"
+						dropdown-typeahead-on-select="setUnitFormat(1, $subItem)">
+					</li>
+					<li class="grafana-target-segment">
+						&nbsp;&nbsp; Grid Max
+					</li>
+					<li>
+						<input type="number" class="input-small grafana-target-segment-input" placeholder="auto"
+					   	empty-to-null ng-model="panel.grid.rightMax"
+							ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						Min
+					</li>
+					<li>
+						<input type="number" class="input-small grafana-target-segment-input" placeholder="auto"
+					  	empty-to-null ng-model="panel.grid.rightMin"
+							ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						Label
+					</li>
+					<li>
+						<input type="text" class="input-small grafana-target-segment-input last"
+					  	ng-model="panel.rightYAxisLabel" ng-change="render()" ng-model-onblur>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+		</div>
 	</div>
 
-  <div class="section" ng-if="panel.legend.values">
-    <h5>Legend values</h5>
-		<editor-opt-bool text="Min" model="panel.legend.min" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Max" model="panel.legend.max" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Current" model="panel.legend.current" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Total" model="panel.legend.total" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Avg" model="panel.legend.avg" change="render()"></editor-opt-bool>
-  </div>
-
-  <div class="section">
-    <h5>Grid thresholds</h5>
-    <div class="editor-option">
-      <label class="small">Level1</label>
-      <input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
-    </div>
-    <div class="editor-option">
-      <label class="small">Color</label>
-      <spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
-    </div>
-    <div class="editor-option">
-      <label class="small">Level2</label>
-      <input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
-    </div>
-    <div class="editor-option">
-      <label class="small">Color</label>
-      <spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
-    </div>
-		<editor-opt-bool text="Line mode" model="panel.grid.thresholdLine" change="render()"></editor-opt-bool>
-  </div>
+	<div class="section" style="margin-bottom: 20px">
+		<div class="grafana-target">
+		<div class="grafana-target-inner">
+			<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Show Axis</strong>
+					</li>
+					<li class="grafana-target-segment">
+						X-Axis&nbsp;
+						<input class="cr1" id="hideXAxis" type="checkbox"
+								ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
+						<label for="hideXAxis" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment last">
+						Y-Axis&nbsp;
+						<input class="cr1" id="hideYAxis" type="checkbox"
+						    ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
+						<label for="hideYAxis" class="cr1"></label>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Thresholds</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Level 1
+					</li>
+					<li>
+						<input type="number" class="input-small grafana-target-segment-input"
+					  	ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
+					</li>
+					<li class="grafana-target-segment">
+						Level 2
+					</li>
+					<li>
+						<input type="number" class="input-small grafana-target-segment-input"
+					  	ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
+					</li>
+					<li class="grafana-target-segment last">
+						Line mode&nbsp;
+						<input class="cr1" id="panel.grid.thresholdLine" type="checkbox"
+								ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render()">
+						<label for="panel.grid.thresholdLine" class="cr1"></label>
 
-  <div class="section">
-    <h5>Show Axes</h5>
-		<editor-opt-bool text="X-Axis" model="panel['x-axis']" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Y-axis" model="panel['y-axis']" change="render()"></editor-opt-bool>
-  </div>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+		</div>
+	</div>
+</div>
 
+<div class="editor-row">
+	<div class="section">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Legend</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Show&nbsp;
+						<input class="cr1" id="panel.legend.show" type="checkbox"
+						    ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render()">
+						<label for="panel.legend.show" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Table&nbsp;
+						<input class="cr1" id="panel.legend.alignAsTable" type="checkbox"
+						    ng-model="panel.legend.alignAsTable" ng-checked="panel.legend.alignAsTable" ng-change="render()">
+						<label for="panel.legend.alignAsTable" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Right side&nbsp;
+						<input class="cr1" id="panel.legend.rightSide" type="checkbox"
+						    ng-model="panel.legend.rightSide" ng-checked="panel.legend.rightSide" ng-change="render()">
+						<label for="panel.legend.rightSide" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment last">
+						<span bs-tooltip="'Hides series with only null values'">Hide empty&nbsp;<span>
+						<input class="cr1" id="panel.legend.hideEmpty" type="checkbox"
+						    ng-model="panel.legend.hideEmpty" ng-checked="panel.legend.hideEmpty" ng-change="render()">
+						<label for="panel.legend.hideEmpty" class="cr1"></label>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+		</div>
+	</div>
+	<div class="section">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 100px">
+						<strong>Legend values</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Min&nbsp;
+						<input class="cr1" id="panel.legend.min" type="checkbox"
+						    ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="legendValuesOptionChanged()">
+						<label for="panel.legend.min" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Max&nbsp;
+						<input class="cr1" id="panel.legend.max" type="checkbox"
+						    ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="legendValuesOptionChanged()">
+						<label for="panel.legend.max" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Avg&nbsp;
+						<input class="cr1" id="panel.legend.avg" type="checkbox"
+						    ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="legendValuesOptionChanged()">
+						<label for="panel.legend.avg" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Current&nbsp;
+						<input class="cr1" id="panel.legend.current" type="checkbox"
+						    ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="legendValuesOptionChanged()">
+						<label for="panel.legend.current" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment last">
+						Total&nbsp;
+						<input class="cr1" id="panel.legend.total" type="checkbox"
+						    ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="legendValuesOptionChanged()">
+						<label for="panel.legend.total" class="cr1"></label>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+		</div>
+	</div>
 </div>
+

+ 43 - 19
src/app/panels/graph/graph.js

@@ -109,9 +109,9 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
           }
         }
 
-        function updateLegendValues(plot) {
+        function drawHook(plot) {
+          // Update legend values
           var yaxis = plot.getYAxes();
-
           for (var i = 0; i < data.length; i++) {
             var series = data[i];
             var axis = yaxis[series.yaxis - 1];
@@ -124,6 +124,29 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
             series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
             if(!scope.$$phase) { scope.$digest(); }
           }
+
+          // add left axis labels
+          if (scope.panel.leftYAxisLabel) {
+            var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>")
+              .text(scope.panel.leftYAxisLabel)
+              .appendTo(elem);
+
+            yaxisLabel.css("margin-top", yaxisLabel.width() / 2);
+          }
+
+          // add right axis labels
+          if (scope.panel.rightYAxisLabel) {
+            var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>")
+              .text(scope.panel.rightYAxisLabel)
+              .appendTo(elem);
+
+            rightLabel.css("margin-top", rightLabel.width() / 2);
+          }
+        }
+
+        function processOffsetHook(plot, gridMargin) {
+          if (scope.panel.leftYAxisLabel) { gridMargin.left = 20; }
+          if (scope.panel.rightYAxisLabel) { gridMargin.right = 20; }
         }
 
         // Function for rendering panel
@@ -137,7 +160,10 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
 
           // Populate element
           var options = {
-            hooks: { draw: [updateLegendValues] },
+            hooks: {
+              draw: [drawHook],
+              processOffset: [processOffsetHook],
+            },
             legend: { show: false },
             series: {
               stackpercent: panel.stack ? panel.percentage : false,
@@ -173,7 +199,8 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
               backgroundColor: null,
               borderWidth: 0,
               hoverable: true,
-              color: '#c8c8c8'
+              color: '#c8c8c8',
+              margin: { left: 0, right: 0 },
             },
             selection: {
               mode: "x",
@@ -213,8 +240,6 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
             } catch (e) {
               console.log('flotcharts error', e);
             }
-
-            addAxisLabels();
           }
 
           if (shouldDelayDraw(panel)) {
@@ -317,19 +342,6 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
           };
         }
 
-        function addAxisLabels() {
-          if (scope.panel.leftYAxisLabel) {
-            elem.css('margin-left', '10px');
-            var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
-              .text(scope.panel.leftYAxisLabel)
-              .appendTo(elem);
-
-            yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
-          } else if (elem.css('margin-left')) {
-            elem.css('margin-left', '');
-          }
-        }
-
         function configureAxisOptions(data, options) {
           var defaults = {
             position: 'left',
@@ -406,9 +418,21 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
           case 'bps':
             url += '&yUnitSystem=si';
             break;
+          case 'Bps':
+            url += '&yUnitSystem=si';
+            break;
           case 'short':
             url += '&yUnitSystem=si';
             break;
+          case 'joule':
+            url += '&yUnitSystem=si';
+            break;
+          case 'watt':
+            url += '&yUnitSystem=si';
+            break;
+          case 'ev':
+            url += '&yUnitSystem=si';
+            break;
           case 'none':
             url += '&yUnitSystem=none';
             break;

+ 12 - 5
src/app/panels/graph/module.js

@@ -104,6 +104,12 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
 
     $scope.hiddenSeries = {};
     $scope.seriesList = [];
+    $scope.unitFormats = kbn.getUnitFormats();
+
+    $scope.setUnitFormat = function(axis, subItem) {
+      $scope.panel.y_formats[axis] = subItem.value;
+      $scope.render();
+    };
 
     $scope.updateTimeRange = function () {
       $scope.range = timeSrv.timeRange();
@@ -262,11 +268,6 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
       $scope.render();
     };
 
-    $scope.toggleGridMinMax = function(key) {
-      $scope.panel.grid[key] = _.toggle($scope.panel.grid[key], null, 0);
-      $scope.render();
-    };
-
     $scope.addSeriesOverride = function(override) {
       $scope.panel.seriesOverrides.push(override || {});
     };
@@ -282,6 +283,12 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
       $scope.get_data();
     };
 
+    $scope.legendValuesOptionChanged = function() {
+      var legend = $scope.panel.legend;
+      legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
+      $scope.render();
+    };
+
     $scope.exportCsv = function() {
       kbn.exportSeriesListToCsv($scope.seriesList);
     };

+ 6 - 11
src/app/panels/graph/seriesOverridesCtrl.js

@@ -20,26 +20,21 @@ define([
       option.index = $scope.overrideMenu.length;
       option.values = values;
 
-      option.submenu = _.map(values, function(value, index) {
-        return {
-          text: String(value),
-          click: 'menuItemSelected(' + option.index + ',' + index + ')'
-        };
+      option.submenu = _.map(values, function(value) {
+        return { text: String(value), value: value };
       });
 
       $scope.overrideMenu.push(option);
     };
 
-    $scope.setOverride = function(optionIndex, valueIndex) {
-      var option = $scope.overrideMenu[optionIndex];
-      var value = option.values[valueIndex];
-      $scope.override[option.propertyName] = value;
+    $scope.setOverride = function(item, subItem) {
+      $scope.override[item.propertyName] = subItem.value;
 
       // automatically disable lines for this series and the fill bellow to series
       // can be removed by the user if they still want lines
-      if (option.propertyName === 'fillBelowTo') {
+      if (item.propertyName === 'fillBelowTo') {
         $scope.override['lines'] = false;
-        $scope.addSeriesOverride({ alias: value, lines: false });
+        $scope.addSeriesOverride({ alias: subItem.value, lines: false });
       }
 
       $scope.updateCurrentOverrides();

+ 1 - 1
src/app/panels/graph/styleEditor.html

@@ -89,7 +89,7 @@
 							{{option.name}}: {{option.value}}
 						</li>
 
-						<li class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($optionIndex, $valueIndex)">
+						<li class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($item, $subItem)">
 						</li>
 
 					</ul>

+ 170 - 95
src/app/panels/singlestat/editor.html

@@ -1,115 +1,190 @@
 <div class="editor-row">
-	<div class="section">
-    <h5>Big value</h5>
-		<div class="editor-option">
-			<label class="small">Prefix</label>
-			<input type="text" class="input-small" ng-model="panel.prefix" ng-blur="render()"></input>
-		</div>
-		<div class="editor-option">
-			<label class="small">Value</label>
-			<select class="input-small" ng-model="panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="render()"></select>
-		</div>
-		<div class="editor-option">
-			<label class="small">Postfix</label>
-			<input type="text" class="input-small" ng-model="panel.postfix" ng-blur="render()" ng-trim="false"></input>
-		</div>
-    <div class="editor-option">
-      <label class="small">Null point mode<tip>Define how null values should handled, connected = ignored</tip></label>
-      <select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="get_data()"></select>
-    </div>
-	</div>
-
-	<div class="section">
-    <h5>Big value font size</h5>
-		<div class="editor-option">
-			<label class="small">Prefix</label>
-			<select class="input-mini" style="width: 75px;" ng-model="panel.prefixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
-		</div>
-		<div class="editor-option">
-			<label class="small">Value</label>
-			<select class="input-mini" style="width: 75px;" ng-model="panel.valueFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
-		</div>
-		<div class="editor-option">
-			<label class="small">Postfix</label>
-			<select class="input-mini" style="width: 75px;" ng-model="panel.postfixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
-		</div>
-	</div>
-
-	<div class="section">
-    <h5>Formats</h5>
-		<div class="editor-option">
-			<label class="small">Unit format</label>
-			<select class="input-small" ng-model="panel.format" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
+	<div class="section" style="margin-bottom: 20px">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Big value</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Prefix
+					</li>
+					<li>
+						<input type="text" class="input-small grafana-target-segment-input"
+					  	ng-model="panel.prefix" ng-change="render()" ng-model-onblur>
+					</li>
+					<li class="grafana-target-segment">
+						Value
+					</li>
+					<li>
+						<select class="input-small grafana-target-segment-input" ng-model="panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="render()"></select>
+					</li>
+					<li class="grafana-target-segment">
+						Postfix
+					</li>
+					<li>
+						<input type="text" class="input-small grafana-target-segment-input last"
+					  	ng-model="panel.postfix" ng-change="render()" ng-model-onblur>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Font size</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Prefix
+					</li>
+					<li>
+						<select class="input-small grafana-target-segment-input" style="width: 99px;" ng-model="panel.prefixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
+					</li>
+					<li class="grafana-target-segment">
+						Value
+					</li>
+					<li>
+						<select class="input-small grafana-target-segment-input" ng-model="panel.valueFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
+					</li>
+					<li class="grafana-target-segment">
+						Postfix
+					</li>
+					<li>
+						<select class="input-small grafana-target-segment-input last" style="width: 99px" ng-model="panel.postfixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Unit</strong>
+					</li>
+					<li class="dropdown" style="width: 130px;"
+						ng-model="panel.format"
+						dropdown-typeahead="unitFormats"
+						dropdown-typeahead-on-select="setUnitFormat($subItem)">
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
 		</div>
 	</div>
 </div>
 
 <div class="editor-row">
-	<div class="section">
-    <h5>Coloring</h5>
-		<editor-opt-bool text="Background" model="panel.colorBackground" change="setColoring({background: true})"></editor-opt-bool>
-		<editor-opt-bool text="Value" model="panel.colorValue" change="setColoring({value: true})"></editor-opt-bool>
-		<div class="editor-option" ng-show="panel.colorBackground || panel.colorValue">
-			<label class="small">Thresholds<tip>Comma seperated values</tip></label>
-			<input type="text" class="input-large" ng-model="panel.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
-		</div>
-		<div class="editor-option" ng-show="panel.colorBackground || panel.colorValue">
-      <label class="small">Colors</label>
-      <spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
-      <spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
-			<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
-			<a class="pointer" ng-click="invertColorOrder()">invert order</a>
+	<div class="section" style="margin-bottom: 20px">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Coloring</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Background&nbsp;
+						<input class="cr1" id="panel.colorBackground" type="checkbox"
+						    ng-model="panel.colorBackground" ng-checked="panel.colorBackground" ng-change="render()">
+						<label for="panel.colorBackground" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Value&nbsp;
+						<input class="cr1" id="panel.colorValue" type="checkbox"
+						    ng-model="panel.colorValue" ng-checked="panel.colorValue" ng-change="render()">
+						<label for="panel.colorValue" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Thresholds<tip>Comma seperated values</tip>
+					</li>
+					<li>
+						<input type="text" class="input-large grafana-target-segment-input" ng-model="panel.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
+					</li>
+					<li class="grafana-target-segment">
+						Colors
+					</li>
+					<li class="grafana-target-segment">
+						<spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
+						<spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
+						<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
+					</li>
+					<li class="grafana-target-segment last">
+						<a class="pointer" ng-click="invertColorOrder()">invert order</a>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
 		</div>
 	</div>
+</div>
 
-	<div class="section">
-		<h5>Spark lines</h5>
-		<editor-opt-bool text="Spark line" model="panel.sparkline.show" change="render()"></editor-opt-bool>
-		<editor-opt-bool text="Background mode" model="panel.sparkline.full" change="render()"></editor-opt-bool>
-		<div class="editor-option">
-			<label class="small">Line color</label>
-			<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
-		</div>
-		<div class="editor-option">
-			<label class="small">Fill color</label>
-			<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
+<div class="editor-row">
+	<div class="section" style="margin-bottom: 20px">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment" style="width: 80px">
+						<strong>Spark lines</strong>
+					</li>
+					<li class="grafana-target-segment">
+						Show&nbsp;
+						<input class="cr1" id="panel.sparkline.show" type="checkbox"
+						    ng-model="panel.sparkline.show" ng-checked="panel.sparkline.show" ng-change="render()">
+						<label for="panel.sparkline.show" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Background mode&nbsp;
+						<input class="cr1" id="panel.sparkline.full" type="checkbox"
+						    ng-model="panel.sparkline.full" ng-checked="panel.sparkline.full" ng-change="render()">
+						<label for="panel.sparkline.full" class="cr1"></label>
+					</li>
+					<li class="grafana-target-segment">
+						Line Color
+					</li>
+					<li class="grafana-target-segment">
+						<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
+					</li>
+					<li class="grafana-target-segment">
+						Fill Color
+					</li>
+					<li class="grafana-target-segment">
+						<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
 		</div>
 	</div>
 </div>
 
 <div class="editor-row">
-	<div class="section">
-		<h5>Value to text mapping</h5>
-		<div class="editor-option">
-			<label class="small">Specify mappings</label>
-			<div class="grafana-target">
-				<div class="grafana-target-inner">
-					<ul class="grafana-segment-list">
-						<li class="grafana-target-segment"  ng-repeat-start="map in panel.valueMaps">
-							<i class="fa fa-remove pointer" ng-click="removeValueMap(map)"></i>
-						</li>
+	<div class="section" style="margin-bottom: 20px">
+		<div class="grafana-target">
+			<div class="grafana-target-inner">
+				<ul class="grafana-segment-list">
+					<li class="grafana-target-segment">
+						<strong>Value to text mapping</strong>
+					</li>
+					<li class="grafana-target-segment"  ng-repeat-start="map in panel.valueMaps">
+						<i class="fa fa-remove pointer" ng-click="removeValueMap(map)"></i>
+					</li>
+					<li>
+						<input type="text" ng-model="map.value" placeholder="value" class="input-mini grafana-target-segment-input" ng-blur="render()">
+					</li>
+					<li class="grafana-target-segment">
+						<i class="fa fa-arrow-right"></i>
+					</li>
+					<li ng-repeat-end>
+						<input type="text" placeholder="text" ng-model="map.text" class="input-mini grafana-target-segment-input" ng-blur="render()">
+					</li>
 
-						<li>
-							<input type="text" ng-model="map.value" placeholder="value" class="input-mini grafana-target-segment-input" ng-blur="render()">
-						</li>
-						<li class="grafana-target-segment">
-							<i class="fa fa-arrow-right"></i>
-						</li>
-						<li ng-repeat-end>
-							<input type="text" placeholder="text" ng-model="map.text" class="input-mini grafana-target-segment-input" ng-blur="render()">
-						</li>
+					<li>
+						<a class="pointer grafana-target-segment" ng-click="addValueMap();">
+							<i class="fa fa-plus"></i>
+						</a>
+					</li>
 
-						<li>
-							<a class="pointer grafana-target-segment" ng-click="addValueMap();">
-								<i class="fa fa-plus"></i>
-							</a>
-						</li>
-
-					</ul>
-					<div class="clearfix"></div>
-				</div>
+				</ul>
+				<div class="clearfix"></div>
 			</div>
 		</div>
 	</div>
 </div>
-

+ 6 - 0
src/app/panels/singlestat/module.js

@@ -57,6 +57,12 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) {
     };
 
     _.defaults($scope.panel, _d);
+    $scope.unitFormats = kbn.getUnitFormats();
+
+    $scope.setUnitFormat = function(subItem) {
+      $scope.panel.format = subItem.value;
+      $scope.render();
+    };
 
     $scope.init = function() {
       panelSrv.init($scope);

+ 3 - 4
src/app/panels/singlestat/singleStatPanel.js

@@ -11,7 +11,7 @@ function (angular, app, _, $) {
   var module = angular.module('grafana.panels.singlestat', []);
   app.useModule(module);
 
-  module.directive('singlestatPanel', function($location, linkSrv, $timeout) {
+  module.directive('singlestatPanel', function($location, linkSrv, $timeout, templateSrv) {
 
     return {
       link: function(scope, elem) {
@@ -63,6 +63,7 @@ function (angular, app, _, $) {
         }
 
         function getSpan(className, fontSize, value)  {
+          value = templateSrv.replace(value);
           return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
             value + '</span>';
         }
@@ -133,9 +134,7 @@ function (angular, app, _, $) {
             color: panel.sparkline.lineColor
           };
 
-          setTimeout(function() {
-            $.plot(plotCanvas, [plotSeries], options);
-          }, 10);
+          $.plot(plotCanvas, [plotSeries], options);
         }
 
         function render() {

+ 1 - 1
src/css/less/forms.less

@@ -15,7 +15,7 @@ input[type="checkbox"]+.cr1 {
   height: 19px;
   clear: none;
   text-indent: 2px;
-  margin-top: 4px;
+  margin: 0;
   padding: 0 0 0 20px;
   vertical-align:middle;
   background: url(@checkboxImageUrl) left top no-repeat;

+ 11 - 56
src/css/less/grafana.less

@@ -88,29 +88,6 @@
   }
 }
 
-.yaxisLabel {
-  top: 50%;
-  left: -20px;
-  transform: rotate(-90deg);
-  -o-transform: rotate(-90deg);
-  -ms-transform: rotate(-90deg);
-  -moz-transform: rotate(-90deg);
-  -webkit-transform: rotate(-90deg);
-  transform-origin: 0 0;
-  -o-transform-origin: 0 0;
-  -ms-transform-origin: 0 0;
-  -moz-transform-origin: 0 0;
-  -webkit-transform-origin: 0 0;
-}
-
-.axisLabel {
-  color: @textColor;
-  font-size: @fontSizeSmall;
-  position: absolute;
-  text-align: center;
-  font-size: 12px;
-}
-
 .dashboard-fullscreen {
   .main-view-container {
     overflow: hidden;
@@ -146,7 +123,6 @@
 .grafana-segment-list {
   list-style: none;
   margin: 0;
-  margin-right: 90px;
   >li {
     float: left;
   }
@@ -192,6 +168,9 @@
     padding: 8px 15px;
   }
 
+  &.last {
+    border-right: none;
+  }
 }
 
 .grafana-target-segment-icon {
@@ -225,37 +204,6 @@ input[type=text].grafana-function-param-input {
   padding: 0;
 }
 
-.grafana-target-controls {
-  float: right;
-  list-style: none;
-  margin: 0;
-  text-align: right;
-
-  >li {
-    display: inline-block;
-    white-space: nowrap;
-  }
-
-  .fa {
-    position: relative;
-    top: 8px;
-  }
-
-  a {
-    padding: 8px 7px;
-    color: @grafanaTargetColor;
-    font-size: 16px;
-
-    .grafana-target-hidden & {
-      color: @grafanaTargetColorHide;
-    }
-
-    &:hover, &:focus {
-      text-decoration: none;
-    }
-  }
-}
-
 input[type=text].grafana-target-text-input {
   padding: 8px 7px;
   border: none;
@@ -267,12 +215,15 @@ input[type=text].grafana-target-text-input {
   border-right: 1px solid @grafanaTargetSegmentBorder;
 }
 
-input[type=text].grafana-target-segment-input {
+[type=text].grafana-target-segment-input, [type=number].grafana-target-segment-input {
   border: none;
   border-right: 1px solid @grafanaTargetSegmentBorder;
   margin: 0px;
   border-radius: 0;
   padding: 8px 4px;
+  &.last {
+    border-right: none;
+  }
 }
 
 input[type=checkbox].grafana-target-option-checkbox {
@@ -286,6 +237,9 @@ select.grafana-target-segment-input {
   border-radius: 0;
   height: 36px;
   padding: 8px 5px;
+  &.last {
+    border-right: none;
+  }
 }
 
 .grafana-target .dropdown {
@@ -371,6 +325,7 @@ select.grafana-target-segment-input {
   background: inherit;
   border: none;
   color: inherit;
+  padding: 0;
 }
 
 .sp-replacer:hover, .sp-replacer.sp-active {

+ 41 - 0
src/css/less/graph.less

@@ -228,3 +228,44 @@
     text-align: right;
   }
 }
+
+.left-yaxis-label {
+  top: 50%;
+  left: -5px;
+  transform: rotate(-90deg);
+  -o-transform: rotate(-90deg);
+  -ms-transform: rotate(-90deg);
+  -moz-transform: rotate(-90deg);
+  -webkit-transform: rotate(-90deg);
+  transform-origin: left top;
+  -o-transform-origin: left top;
+  -ms-transform-origin: left top;
+  -moz-transform-origin: left top;
+  -webkit-transform-origin: left top;
+}
+
+.right-yaxis-label {
+  top: 50%;
+  right: -5px;
+
+  -webkit-transform: rotate(90deg);
+  -webkit-transform-origin: right top;
+  -moz-transform: rotate(90deg);
+  -moz-transform-origin: right top;
+  -ms-transform: rotate(90deg);
+  -ms-transform-origin: right top;
+  -o-transform: rotate(90deg);
+  -o-transform-origin: right top;
+  transform: rotate(90deg);
+  transform-origin: right top;
+}
+
+
+.axisLabel {
+  color: @textColor;
+  font-size: @fontSizeSmall;
+  position: absolute;
+  text-align: center;
+  font-size: 12px;
+}
+

+ 1 - 1
src/index.html

@@ -31,7 +31,7 @@
 		<div class="page-alert-list">
 			<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
 				<button type="button" class="alert-close" ng-click="dashAlerts.clear(alert)">
-					<i class="icon-remove-sign"></i>
+					<i class="fa fa-times-circle"></i>
 				</button>
 				<div class="alert-title">{{alert.title}}</div>
 				<div ng-bind-html='alert.text'></div>

+ 6 - 10
src/plugins/custom.panel.example/module.js

@@ -2,9 +2,9 @@ define([
   'angular',
   'app',
   'lodash',
-  'require',
+  'components/panelmeta',
 ],
-function (angular, app, _) {
+function (angular, app, _, PanelMeta) {
   'use strict';
 
   var module = angular.module('grafana.panels.custom', []);
@@ -12,9 +12,9 @@ function (angular, app, _) {
 
   module.controller('CustomPanelCtrl', function($scope, panelSrv) {
 
-    $scope.panelMeta = {
-      description : "Example plugin panel",
-    };
+    $scope.panelMeta = new PanelMeta({
+      description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
+    });
 
     // set and populate defaults
     var _d = {
@@ -22,10 +22,6 @@ function (angular, app, _) {
 
     _.defaults($scope.panel, _d);
 
-    $scope.init = function() {
-      panelSrv.init($scope);
-    };
-
-    $scope.init();
+    panelSrv.init($scope);
   });
 });

+ 1 - 7
src/test/specs/seriesOverridesCtrl-specs.js

@@ -16,15 +16,9 @@ define([
       ctx.scope.render = function() {};
     });
 
-    describe('Controller should init overrideMenu', function() {
-      it('click should include option and value index', function() {
-        expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('menuItemSelected(1,1)');
-      });
-    });
-
     describe('When setting an override', function() {
       beforeEach(function() {
-        ctx.scope.setOverride(1, 0);
+        ctx.scope.setOverride({propertyName: 'lines'}, {value: true});
       });
 
       it('should set override property', function() {