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

Merge remote-tracking branch 'grafana/master' into influx-db-query2

* grafana/master: (35 commits)
  Update CHANGELOG.md
  Update CHANGELOG.md
  fixed some images (#9237)
  release: bumped version to v4.5.0 stable
  docs: minor update
  plugins: add styles for ol tags in markdown
  docs: minor fixes to images
  Docs image updates (#9225)
  fix: improve behavior when switching back and forth between x-axis modes, fixes #9229
  fixes for metrics tab when data source was not found
  provide ace editor for external datasource plugin (#9224)
  ux: increased code editor auto complete width from 320px to 550px, fixes #9203
  docs: windows - add note about ini comments
  prometheus: added completer unit test, #9208
  docs: minor update
  docs: kiosk mode options add to playlist doc
  influxdb: small css fix for order by in query editor
  style: corrected indentation in sass file
  replaced old images and gifs with new ones (#9217)
  ux: success/error alerts refactoring, #9214
  ...
ryan 8 лет назад
Родитель
Сommit
bc03640d6b
54 измененных файлов с 364 добавлено и 238 удалено
  1. 5 3
      CHANGELOG.md
  2. 13 3
      docs/sources/features/datasources/graphite.md
  3. 3 5
      docs/sources/features/datasources/influxdb.md
  4. 2 4
      docs/sources/features/datasources/mysql.md
  5. 2 1
      docs/sources/features/datasources/prometheus.md
  6. 2 6
      docs/sources/features/panels/graph.md
  7. 25 19
      docs/sources/features/panels/table_panel.md
  8. 1 1
      docs/sources/features/shortcuts.md
  9. 4 7
      docs/sources/guides/whats-new-in-v4-5.md
  10. 1 1
      docs/sources/installation/windows.md
  11. 23 1
      docs/sources/reference/playlist.md
  12. 3 1
      docs/sources/tutorials/hubot_howto.md
  13. 1 5
      package.json
  14. 1 1
      pkg/api/metrics.go
  15. 6 0
      pkg/services/alerting/conditions/query.go
  16. 3 2
      pkg/util/encryption.go
  17. 21 4
      public/app/core/components/code_editor/code_editor.ts
  18. 10 1
      public/app/core/services/alert_srv.ts
  19. 8 2
      public/app/core/services/backend_srv.ts
  20. 31 34
      public/app/core/utils/kbn.js
  21. 1 1
      public/app/features/dashboard/dashboard_srv.ts
  22. 3 5
      public/app/features/dashboard/time_srv.ts
  23. 10 7
      public/app/features/panel/metrics_tab.ts
  24. 3 3
      public/app/features/panel/partials/metrics_tab.html
  25. 2 0
      public/app/features/panel/query_ctrl.ts
  26. 2 5
      public/app/features/plugins/ds_edit_ctrl.ts
  27. 8 3
      public/app/features/plugins/partials/ds_edit.html
  28. 1 1
      public/app/plugins/datasource/cloudwatch/datasource.js
  29. 4 4
      public/app/plugins/datasource/elasticsearch/datasource.js
  30. 1 1
      public/app/plugins/datasource/graphite/datasource.ts
  31. 5 0
      public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts
  32. 3 3
      public/app/plugins/datasource/influxdb/datasource.ts
  33. 1 1
      public/app/plugins/datasource/influxdb/partials/query.editor.html
  34. 6 2
      public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts
  35. 3 3
      public/app/plugins/datasource/mysql/datasource.ts
  36. 1 1
      public/app/plugins/datasource/opentsdb/datasource.js
  37. 5 1
      public/app/plugins/datasource/opentsdb/specs/query-ctrl-specs.ts
  38. 1 1
      public/app/plugins/datasource/prometheus/datasource.ts
  39. 6 6
      public/app/plugins/datasource/prometheus/mode-prometheus.js
  40. 2 2
      public/app/plugins/datasource/prometheus/partials/query.editor.html
  41. 0 0
      public/app/plugins/datasource/prometheus/snippets/prometheus.js
  42. 27 0
      public/app/plugins/datasource/prometheus/specs/completer_specs.ts
  43. 2 14
      public/app/plugins/panel/graph/axes_editor.html
  44. 6 4
      public/app/plugins/panel/graph/axes_editor.ts
  45. 1 1
      public/app/plugins/panel/text/editor.html
  46. 12 12
      public/sass/_variables.dark.scss
  47. 9 15
      public/sass/_variables.light.scss
  48. 11 11
      public/sass/base/_type.scss
  49. 36 24
      public/sass/components/_alerts.scss
  50. 5 1
      public/sass/components/_code_editor.scss
  51. 1 1
      public/sass/components/_drop.scss
  52. 1 1
      public/sass/pages/_dashboard.scss
  53. 14 0
      public/test/core/utils/kbn_specs.js
  54. 6 3
      public/views/index.html

+ 5 - 3
CHANGELOG.md

@@ -7,11 +7,13 @@
 - UX changes to nav & side menu
 - New dashboard grid layout system
 
-# 4.5.0 (unreleased)
-
-## Enhancements
+# 4.5.0 (2017-09-14)
 
+## Fixes & Enhancements since beta1
+* **Security**: Security fix for api vulnerability (in multiple org setups).
 * **Shortcuts**: Adds shortcut for creating new dashboard [#8876](https://github.com/grafana/grafana/pull/8876) thx [@mtanda](https://github.com/mtanda)
+* **Graph**: Right Y-Axis label position fixed [#9172](https://github.com/grafana/grafana/pull/9172) 
+* **General**: Improve rounding of time intervals [#9197](https://github.com/grafana/grafana/pull/9197), thx [@alin-amana](https://github.com/alin-amana)
 
 # 4.5.0-beta1 (2017-09-05)
 

+ 13 - 3
docs/sources/features/datasources/graphite.md

@@ -41,7 +41,9 @@ Proxy access means that the Grafana backend will proxy all requests from the bro
 Click the ``Select metric`` link to start navigating the metric space. One you start you can continue using the mouse
 or keyboard arrow keys. You can select a wildcard and still continue.
 
-![](/img/docs/animated_gifs/graphite_query1.gif)
+{{< docs-imagebox img="/img/docs/v45/graphite_query1_still.png"
+                  animated-gif="/img/docs/v45/graphite_query1.gif" >}}
+
 
 ### Functions
 
@@ -50,18 +52,26 @@ a function is selected it will be added and your focus will be in the text box o
 a parameter just click on it and it will turn into a text box. To delete a function click the function name followed
 by the x icon.
 
-![](/img/docs/animated_gifs/graphite_query2.gif)
+{{< docs-imagebox img="/img/docs/v45/graphite_query2_still.png"
+                  animated-gif="/img/docs/v45/graphite_query2.gif" >}}
+
 
 ### Optional parameters
 
 Some functions like aliasByNode support an optional second argument. To add this parameter specify for example 3,-2 as the first parameter and the function editor will adapt and move the -2 to a second parameter. To remove the second optional parameter just click on it and leave it blank and the editor will remove it.
 
-![](/img/docs/animated_gifs/func_editor_optional_params.gif)
+{{< docs-imagebox img="/img/docs/v45/graphite_query3_still.png"
+                  animated-gif="/img/docs/v45/graphite_query3.gif" >}}
+
 
 ### Nested Queries
 
 You can reference queries by the row “letter” that they’re on (similar to  Microsoft Excel). If you add a second query to a graph, you can reference the first query simply by typing in #A. This provides an easy and convenient way to build compounded queries.
 
+{{< docs-imagebox img="/img/docs/v45/graphite_nested_queries_still.png"
+                  animated-gif="/img/docs/v45/graphite_nested_queries.gif" >}}
+
+
 ## Point consolidation
 
 All Graphite metrics are consolidated so that Graphite doesn't return more data points than there are pixels in the graph. By default,

+ 3 - 5
docs/sources/features/datasources/influxdb.md

@@ -41,7 +41,7 @@ mode is also more secure as the username & password will never reach the browser
 
 ## Query Editor
 
-![](/assets/img/blog/v2.6/influxdb_editor_v3.gif)
+{{< docs-imagebox img="/img/docs/v45/influxdb_query_still.png" class="docs-image--no-shadow" animated-gif="/img/docs/v45/influxdb_query.gif" >}}
 
 You find the InfluxDB editor in the metrics tab in Graph or Singlestat panel's edit mode. You enter edit mode by clicking the
 panel title, then edit. The editor allows you to select metrics and tags.
@@ -57,10 +57,8 @@ will automatically adjust the filter tag condition to use the InfluxDB regex mat
 
 ### Field & Aggregation functions
 In the `SELECT` row you can specify what fields and functions you want to use. If you have a
-group by time you need an aggregation function. Some functions like derivative require an aggregation function.
-
-The editor tries simplify and unify this part of the query. For example:
-![](/img/docs/influxdb/select_editor.png)
+group by time you need an aggregation function. Some functions like derivative require an aggregation function. The editor tries simplify and unify this part of the query. For example:<br>
+![](/img/docs/influxdb/select_editor.png)<br>
 
 The above will generate the following InfluxDB `SELECT` clause:
 

+ 2 - 4
docs/sources/features/datasources/mysql.md

@@ -11,8 +11,7 @@ weight = 7
 
 # Using MySQL in Grafana
 
-> Only available in Grafana v4.3+. This data source is not ready for
-> production use, currently in development (alpha state).
+> Only available in Grafana v4.3+.
 
 Grafana ships with a built-in MySQL data source plugin that allow you to query any visualize
 data from a MySQL compatible database.
@@ -58,8 +57,7 @@ If the `Format as` query option is set to `Table` then you can basically do any
 
 Query editor with example query:
 
-![](/img/docs/v43/mysql_table_query.png)
-
+{{< docs-imagebox img="/img/docs/v45/mysql_table_query.png" >}}
 
 The query:
 

+ 2 - 1
docs/sources/features/datasources/prometheus.md

@@ -39,7 +39,8 @@ Name | Description
 
 Open a graph in edit mode by click the title > Edit (or by pressing `e` key while hovering over panel).
 
-![](/img/docs/v43/prometheus_query_editor.png)
+{{< docs-imagebox img="/img/docs/v45/prometheus_query_editor_still.png"
+                  animated-gif="/img/docs/v45/prometheus_query_editor.gif" >}}
 
 Name | Description
 ------- | --------

+ 2 - 6
docs/sources/features/panels/graph.md

@@ -50,15 +50,11 @@ populate the template variable to a desired value from the link.
 The metrics tab defines what series data and sources to render.  Each datasource provides different
 options.
 
-## Axes & Grid
+## Axes
 
 ![](/img/docs/v43/graph_axes_grid_options.png)
 
-The Axes & Grid tab controls the display of axes, grids and legend.
-
-### Axes
-
-The ``Left Y`` and ``Right Y`` can be customized using:
+The Axes tab controls the display of axes, grids and legend.  The ``Left Y`` and ``Right Y`` can be customized using:
 
 - ``Unit`` - The display unit for the Y value
 - ``Grid Max`` - The maximum Y value. (default auto)

+ 25 - 19
docs/sources/features/panels/table_panel.md

@@ -12,7 +12,7 @@ weight = 2
 
 # Table Panel
 
-<img src="/assets/img/features/table-panel.png">
+<img class="screenshot" src="/assets/img/features/table-panel.png">
 
 The new table panel is very flexible, supporting both multiple modes for time series as well as for
 table, annotation and raw JSON data. It also provides date formatting and value formatting and coloring options.
@@ -22,55 +22,63 @@ To view table panels in action and test different configurations with sample dat
 ## Options overview
 
 The table panel has many ways to manipulate your data for optimal presentation.
+{{< docs-imagebox img="/img/docs/v45/table_options.png" class="docs-image--no-shadow" max-width= "500px" >}}
 
-<img class="no-shadow" src="/img/docs/v2/table-config2.png">
 
 1. `Data`: Control how your query is transformed into a table.
-2. `Table Display`: Table display options.
-3. `Column Styles`: Column value formatting and display options.
+2. `Paging`: Table display options.
+
 
 ## Data to Table
 
-<img class="no-shadow" src="/img/docs/v2/table-data-options.png">
+{{< docs-imagebox img="/img/docs/v45/table_data_options.png" max-width="500px" class="docs-image--right">}}
 
 The data section contains the **To Table Transform (1)**. This is the primary option for how your data/metric
 query should be transformed into a table format.  The **Columns (2)** option allows you to select what columns
 you want in the table. Only applicable for some transforms.
 
+<div class="clearfix"></div>
+
 ### Time series to rows
 
-<img src="/img/docs/v2/table_ts_to_rows2.png">
+{{< docs-imagebox img="/img/docs/v45/table_ts_to_rows.png"  >}}
 
 In the most simple mode you can turn time series to rows. This means you get a `Time`, `Metric` and a `Value` column. Where `Metric` is the name of the time series.
 
 ### Time series to columns
 
-![](/img/docs/v2/table_ts_to_columns2.png)
+{{< docs-imagebox img="/img/docs/v45/table_ts_to_columns.png" >}}
+
 
 This transform allows you to take multiple time series and group them by time. Which will result in the primary column being `Time` and a column for each time series.
 
 ### Time series aggregations
 
-![](/img/docs/v2/table_ts_to_aggregations2.png)
+{{< docs-imagebox img="/img/docs/v45/table_ts_to_aggregations.png" >}}
+
 This table transformation will lay out your table into rows by metric, allowing columns of `Avg`, `Min`, `Max`, `Total`, `Current` and `Count`. More than one column can be added.
 
 ### Annotations
-![](/img/docs/v2/table_annotations.png)
+
+{{< docs-imagebox img="/img/docs/v45/table_annotations.png" >}}
+
 
 If you have annotations enabled in the dashboard you can have the table show them. If you configure this
 mode then any queries you have in the metrics tab will be ignored.
 
 ### JSON Data
-![](/img/docs/v2/table_json_data.png)
+
+{{< docs-imagebox img="/img/docs/v45/table_json_data.png" max-width="500px" >}}
 
 If you have an Elasticsearch **Raw Document** query or an Elasticsearch query without a `date histogram` use this
 transform mode and pick the columns using the **Columns** section.
 
-![](/img/docs/v2/elastic_raw_doc.png)
+
+{{< docs-imagebox img="/img/docs/v45/elastic_raw_doc.png" >}}
 
 ## Table Display
 
-<img class="no-shadow" src="/img/docs/v2/table-display.png">
+{{< docs-imagebox img="/img/docs/v45/table_paging.png" class="docs-image--no-shadow docs-image--right" max-width="350px" >}}
 
 1. `Pagination (Page Size)`: The table display fields allow you to control The `Pagination` (page size) is the threshold at which the table rows will be broken into pages. For example, if your table had 95 records with a pagination value of 10, your table would be split across 9 pages.
 2. `Scroll`: The `scroll bar` checkbox toggles the ability to scroll within the panel, when unchecked, the panel height will grow to display all rows.
@@ -81,13 +89,11 @@ transform mode and pick the columns using the **Columns** section.
 
 The column styles allow you control how dates and numbers are formatted.
 
-<img class="no-shadow" src="/img/docs/v2/Column-Options.png">
+{{< docs-imagebox img="/img/docs/v45/table_column_styles.png" class="docs-image--no-shadow" >}}
 
 1. `Name or regex`: The Name or Regex field controls what columns the rule should be applied to. The regex or name filter will be matched against the column name not against column values.
-2. `Type`: The three supported types of types are `Number`, `String` and `Date`.
-3. `Title`: Title for the column, when using a Regex the title can include replacement strings like `$1`.
-4. `Format`: Specify date format. Only available when `Type` is set to `Date`.
-5. `Coloring` and `Thresholds`: Specify color mode and thresholds limits.
-6. `Unit` and `Decimals`: Specify unit and decimal precision for numbers.
-7.  `Add column style rule`: Add new column rule.
+2. `Column Header`: Title for the column, when using a Regex the title can include replacement strings like `$1`.
+3.  `Add column style rule`: Add new column rule.
+4. `Thresholds` and `Coloring`: Specify color mode and thresholds limits.
+5. `Type`: The three supported types of types are `Number`, `String` and `Date`. `Unit` and `Decimals`: Specify unit and decimal precision for numbers.`Format`: Specify date format for dates.
 

+ 1 - 1
docs/sources/features/shortcuts.md

@@ -8,7 +8,7 @@ weight = 7
 
 # Keyboard shortcuts
 
-{{< docs-imagebox img="/img/docs/v4/shortcuts.png" max-width="20rem" >}}
+{{< docs-imagebox img="/img/docs/v4/shortcuts.png" max-width="20rem" class="docs-image--right" >}}
 
 Grafana v4 introduces a number of really powerful keyboard shortcuts. You can now focus a panel
 by hovering over it with your mouse. With a panel focused you can simple hit `e` to toggle panel

+ 4 - 7
docs/sources/guides/whats-new-in-v4-5.md

@@ -16,16 +16,13 @@ weight = -4
 
 ### New prometheus query editor
 
-The new query editor has full syntax highlighting. As well as auto complete for metrics, functions, and range vectors.
+The new query editor has full syntax highlighting. As well as auto complete for metrics, functions, and range vectors. There is also integrated function docs right from the query editor!
 
-![](/img/docs/v45/new_prom_editor_1.png)
-
-There is also integrated function docs right from the query editor!
-
-![](/img/docs/v45/new_prom_editor_2.png)
+{{< docs-imagebox img="/img/docs/v45/prometheus_query_editor_still.png" class="docs-image--block" animated-gif="/img/docs/v45/prometheus_query_editor.gif" >}}
 
 ### Elasticsearch: Add ad-hoc filters from the table panel
-![](/img/docs/v45/elastic_ad_hoc_filters.png)
+
+{{< docs-imagebox img="/img/docs/v45/elastic_ad_hoc_filters.png" class="docs-image--block" >}}
 
 ### Table cell links!
 Create column styles that turn cells into links that use the value in the cell  (or other other row values) to generate a url to another dashboard or system:

+ 1 - 1
docs/sources/installation/windows.md

@@ -27,7 +27,7 @@ this folder to anywhere you want Grafana to run from.  Go into the
 
 The default Grafana port is `3000`, this port requires extra permissions
 on windows. Edit `custom.ini` and uncomment the `http_port`
-configuration option and change it to something like `8080` or similar.
+configuration option (`;` is the comment character in ini files) and change it to something like `8080` or similar.
 That port should not require extra Windows privileges.
 
 Start Grafana by executing `grafana-server.exe`, preferably from the

+ 23 - 1
docs/sources/reference/playlist.md

@@ -16,7 +16,7 @@ Since Grafana automatically scales Dashboards to any resolution they're perfect
 
 ## Creating a Playlist
 
-{{< docs-imagebox img="/img/docs/v3/playlist.png" max-width="25rem" >}}
+{{< docs-imagebox img="/img/docs/v3/playlist.png" max-width="25rem" class="docs-image--right">}}
 
 The Playlist feature can be accessed from Grafana's sidemenu, in the Dashboard submenu.
 
@@ -43,3 +43,25 @@ Playlists can also be manually controlled utilizing the Playlist controls at the
 Click the stop button to stop the Playlist, and exit to the current Dashboard.
 Click the next button to advance to the next Dashboard in the Playlist.
 Click the back button to rewind to the previous Dashboard in the Playlist.
+
+## TV or Kiosk Mode
+
+In TV mode the top navbar, row & panel controls will all fade to transparent.
+
+This happens automatically after one minute of user inactivity but can also be toggled manually
+with the `d v` sequence shortcut. Any mouse movement or keyboard action will
+restore navbar & controls.
+
+Another feature is the kiosk mode - in kiosk mode the navbar is completely hidden/removed from view. This can be enabled with the `d k`
+shortcut.
+
+To put a playlist into kiosk mode, use the `d k` shortcut after the playlist has started. The same shortcut will toggle the playlist out of kiosk mode.
+
+### Linking to the Playlist in Kiosk Mode
+
+If you want to create a link to the playlist with kiosk mode enabled:
+
+1. Copy the Start Url (by right clicking on the Play button and choosing Copy link address).
+2. Add the `?kiosk` parameter to the url.
+
+For example, to open the first playlist on the Grafana Play site in kiosk mode: [http://play.grafana.org/playlists/play/1?kiosk](http://play.grafana.org/playlists/play/1?kiosk)

+ 3 - 1
docs/sources/tutorials/hubot_howto.md

@@ -74,7 +74,9 @@ If you do not get an image when opening this link verify that the required font
 
 ### Grafana API Key
 
-<img src="/img/docs/v2/orgdropdown_api_keys.png" style="width: 150px" class="right"></img>
+<!--<img src="/img/docs/v2/orgdropdown_api_keys.png" style="width: 150px" class="right"></img>-->
+{{< docs-imagebox img="/img/docs/v2/orgdropdown_api_keys.png" max-width="150px" class="docs-image--right">}}
+
 You need to set the environment variable `HUBOT_GRAFANA_API_KEY` to a Grafana API Key.
 You can add these from the API Keys page which you find in the Organization dropdown.
 

+ 1 - 5
package.json

@@ -4,7 +4,7 @@
     "company": "Grafana Labs"
   },
   "name": "grafana",
-  "version": "4.5.0-beta1",
+  "version": "4.5.0",
   "repository": {
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"
@@ -53,10 +53,6 @@
     "systemjs": "0.19.41",
     "zone.js": "^0.7.2"
   },
-  "engines": {
-    "node": "4.x",
-    "npm": "2.14.x"
-  },
   "scripts": {
     "build": "./node_modules/grunt-cli/bin/grunt",
     "test": "./node_modules/grunt-cli/bin/grunt test",

+ 1 - 1
pkg/api/metrics.go

@@ -29,7 +29,7 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response {
 		return ApiError(400, "Query missing datasourceId", nil)
 	}
 
-	dsQuery := models.GetDataSourceByIdQuery{Id: dsId}
+	dsQuery := models.GetDataSourceByIdQuery{Id: dsId, OrgId: c.OrgId}
 	if err := bus.Dispatch(&dsQuery); err != nil {
 		return ApiError(500, "failed to fetch data source", err)
 	}

+ 6 - 0
pkg/services/alerting/conditions/query.go

@@ -5,6 +5,8 @@ import (
 	"strings"
 	"time"
 
+	gocontext "context"
+
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/null"
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -112,6 +114,10 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
 
 	resp, err := c.HandleRequest(context.Ctx, req)
 	if err != nil {
+		if err == gocontext.DeadlineExceeded {
+			return nil, fmt.Errorf("Alert execution exceeded the timeout")
+		}
+
 		return nil, fmt.Errorf("tsdb.HandleRequest() error %v", err)
 	}
 

+ 3 - 2
pkg/util/encryption.go

@@ -27,12 +27,13 @@ func Decrypt(payload []byte, secret string) ([]byte, error) {
 	}
 	iv := payload[saltLength : saltLength+aes.BlockSize]
 	payload = payload[saltLength+aes.BlockSize:]
+	payloadDst := make([]byte, len(payload))
 
 	stream := cipher.NewCFBDecrypter(block, iv)
 
 	// XORKeyStream can work in-place if the two arguments are the same.
-	stream.XORKeyStream(payload, payload)
-	return payload, nil
+	stream.XORKeyStream(payloadDst, payload)
+	return payloadDst, nil
 }
 
 func Encrypt(payload []byte, secret string) ([]byte, error) {

+ 21 - 4
public/app/core/components/code_editor/code_editor.ts

@@ -40,11 +40,11 @@ const DEFAULT_MAX_LINES = 10;
 const DEFAULT_TAB_SIZE = 2;
 const DEFAULT_BEHAVIOURS = true;
 
-const GRAFANA_MODULES = ['mode-prometheus', 'snippets-prometheus', 'theme-grafana-dark'];
+const GRAFANA_MODULES = ['theme-grafana-dark'];
 const GRAFANA_MODULE_BASE = "public/app/core/components/code_editor/";
 
 // Trick for loading additional modules
-function setModuleUrl(moduleType, name) {
+function setModuleUrl(moduleType, name, pluginBaseUrl = null) {
   let baseUrl = ACE_SRC_BASE;
   let aceModeName = `ace/${moduleType}/${name}`;
   let moduleName = `${moduleType}-${name}`;
@@ -54,6 +54,10 @@ function setModuleUrl(moduleType, name) {
     baseUrl = GRAFANA_MODULE_BASE;
   }
 
+  if (pluginBaseUrl) {
+    baseUrl = pluginBaseUrl + '/';
+  }
+
   if (moduleType === 'snippets') {
     componentName = `${moduleType}/${name}.js`;
   }
@@ -111,6 +115,17 @@ function link(scope, elem, attrs) {
   let textarea = elem.find("textarea");
   textarea.addClass('gf-form-input');
 
+  if (scope.codeEditorFocus) {
+    setTimeout(function () {
+      textarea.focus();
+      var domEl = textarea[0];
+      if (domEl.setSelectionRange) {
+        var pos = textarea.val().length * 2;
+        domEl.setSelectionRange(pos, pos);
+      }
+    }, 100);
+  }
+
   // Event handlers
   editorSession.on('change', (e) => {
     scope.$apply(() => {
@@ -148,8 +163,8 @@ function link(scope, elem, attrs) {
 
   function setLangMode(lang) {
     let aceModeName = `ace/mode/${lang}`;
-    setModuleUrl("mode", lang);
-    setModuleUrl("snippets", lang);
+    setModuleUrl("mode", lang, scope.datasource.meta.baseUrl || null);
+    setModuleUrl("snippets", lang, scope.datasource.meta.baseUrl || null);
     editorSession.setMode(aceModeName);
 
     ace.config.loadModule("ace/ext/language_tools", (language_tools) => {
@@ -199,6 +214,8 @@ export function codeEditorDirective() {
     template: editorTemplate,
     scope: {
       content: "=",
+      datasource: "=",
+      codeEditorFocus: "<",
       onChange: "&",
       getCompleter: "&"
     },

+ 10 - 1
public/app/core/services/alert_srv.ts

@@ -16,7 +16,7 @@ export class AlertSrv {
 
   init() {
     this.$rootScope.onAppEvent('alert-error', (e, alert) => {
-      this.set(alert[0], alert[1], 'error', 7000);
+      this.set(alert[0], alert[1], 'error', 12000);
     }, this.$rootScope);
 
     this.$rootScope.onAppEvent('alert-warning', (e, alert) => {
@@ -33,6 +33,14 @@ export class AlertSrv {
     appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
   }
 
+  getIconForSeverity(severity) {
+    switch (severity) {
+      case 'success': return 'fa fa-check';
+      case 'error': return 'fa fa-exclamation-triangle';
+      default: return 'fa fa-exclamation';
+    }
+  }
+
   set(title, text, severity, timeout) {
     if (_.isObject(text)) {
       console.log('alert error', text);
@@ -45,6 +53,7 @@ export class AlertSrv {
       title: title || '',
       text: text || '',
       severity: severity || 'info',
+      icon: this.getIconForSeverity(severity)
     };
 
     var newAlertJson = angular.toJson(newAlert);

+ 8 - 2
public/app/core/services/backend_srv.ts

@@ -64,7 +64,13 @@ export class BackendSrv {
     }
 
     if (data.message) {
-      this.alertSrv.set("Problem!", data.message, data.severity, 10000);
+      let description = "";
+      let message = data.message;
+      if (message.length > 80) {
+        description = message;
+        message = "Error";
+      }
+      this.alertSrv.set(message, description, data.severity, 10000);
     }
 
     throw data;
@@ -97,7 +103,7 @@ export class BackendSrv {
       return results.data;
     }, err => {
       // handle unauthorized
-      if (err.status === 401 && firstAttempt) {
+      if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
         return this.loginPing().then(() => {
           options.retry = 1;
           return this.request(options);

+ 31 - 34
public/app/core/utils/kbn.js

@@ -17,90 +17,87 @@ function($, _) {
   kbn.round_interval = function(interval) {
     switch (true) {
       // 0.015s
-      case (interval <= 15):
+      case (interval < 15):
         return 10;      // 0.01s
       // 0.035s
-      case (interval <= 35):
+      case (interval < 35):
         return 20;      // 0.02s
       // 0.075s
-      case (interval <= 75):
+      case (interval < 75):
         return 50;       // 0.05s
       // 0.15s
-      case (interval <= 150):
+      case (interval < 150):
         return 100;      // 0.1s
       // 0.35s
-      case (interval <= 350):
+      case (interval < 350):
         return 200;      // 0.2s
       // 0.75s
-      case (interval <= 750):
+      case (interval < 750):
         return 500;       // 0.5s
       // 1.5s
-      case (interval <= 1500):
+      case (interval < 1500):
         return 1000;      // 1s
       // 3.5s
-      case (interval <= 3500):
+      case (interval < 3500):
         return 2000;      // 2s
       // 7.5s
-      case (interval <= 7500):
+      case (interval < 7500):
         return 5000;      // 5s
       // 12.5s
-      case (interval <= 12500):
+      case (interval < 12500):
         return 10000;     // 10s
       // 17.5s
-      case (interval <= 17500):
+      case (interval < 17500):
         return 15000;     // 15s
       // 25s
-      case (interval <= 25000):
+      case (interval < 25000):
         return 20000;     // 20s
       // 45s
-      case (interval <= 45000):
+      case (interval < 45000):
         return 30000;     // 30s
       // 1.5m
-      case (interval <= 90000):
+      case (interval < 90000):
         return 60000;     // 1m
       // 3.5m
-      case (interval <= 210000):
+      case (interval < 210000):
         return 120000;    // 2m
       // 7.5m
-      case (interval <= 450000):
+      case (interval < 450000):
         return 300000;    // 5m
       // 12.5m
-      case (interval <= 750000):
+      case (interval < 750000):
         return 600000;    // 10m
       // 12.5m
-      case (interval <= 1050000):
+      case (interval < 1050000):
         return 900000;    // 15m
       // 25m
-      case (interval <= 1500000):
+      case (interval < 1500000):
         return 1200000;   // 20m
       // 45m
-      case (interval <= 2700000):
+      case (interval < 2700000):
         return 1800000;   // 30m
       // 1.5h
-      case (interval <= 5400000):
+      case (interval < 5400000):
         return 3600000;   // 1h
       // 2.5h
-      case (interval <= 9000000):
+      case (interval < 9000000):
         return 7200000;   // 2h
       // 4.5h
-      case (interval <= 16200000):
+      case (interval < 16200000):
         return 10800000;  // 3h
       // 9h
-      case (interval <= 32400000):
+      case (interval < 32400000):
         return 21600000;  // 6h
-      // 24h
-      case (interval <= 86400000):
+      // 1d
+      case (interval < 86400000):
         return 43200000;  // 12h
-      // 48h
-      case (interval <= 172800000):
-        return 86400000;  // 24h
       // 1w
-      case (interval <= 604800000):
-        return 86400000;  // 24h
+      case (interval < 604800000):
+        return 86400000;  // 1d
       // 3w
-      case (interval <= 1814400000):
+      case (interval < 1814400000):
         return 604800000; // 1w
-      // 2y
+      // 6w
       case (interval < 3628800000):
         return 2592000000; // 30d
       default:
@@ -134,7 +131,7 @@ function($, _) {
       return nummilliseconds + 'ms';
     }
 
-    return 'less then a millisecond'; //'just now' //or other string you like;
+    return 'less than a millisecond'; //'just now' //or other string you like;
   };
 
   kbn.to_percent = function(number,outof) {

+ 1 - 1
public/app/features/dashboard/dashboard_srv.ts

@@ -83,7 +83,7 @@ export class DashboardSrv {
     }
 
     this.$rootScope.appEvent('dashboard-saved', this.dash);
-    this.$rootScope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + clone.title]);
+    this.$rootScope.appEvent('alert-success', ['Dashboard saved']);
   }
 
   save(clone, options) {

+ 3 - 5
public/app/features/dashboard/time_srv.ts

@@ -116,16 +116,14 @@ class TimeSrv {
 
   setAutoRefresh(interval) {
     this.dashboard.refresh = interval;
+    this.cancelNextRefresh();
     if (interval) {
       var intervalMs = kbn.interval_to_ms(interval);
 
-      this.$timeout(() => {
+      this.refreshTimer = this.timer.register(this.$timeout(() => {
         this.startNextRefreshTimer(intervalMs);
         this.refreshDashboard();
-      }, intervalMs);
-
-    } else {
-      this.cancelNextRefresh();
+      }, intervalMs));
     }
 
     // update url

+ 10 - 7
public/app/features/panel/metrics_tab.ts

@@ -9,7 +9,7 @@ export class MetricsTabCtrl {
   panel: any;
   panelCtrl: any;
   datasources: any[];
-  current: any;
+  datasourceInstance: any;
   nextRefId: string;
   dashboard: DashboardModel;
   panelDsValue: any;
@@ -29,23 +29,26 @@ export class MetricsTabCtrl {
     this.panel = this.panelCtrl.panel;
     this.dashboard = this.panelCtrl.dashboard;
     this.datasources = datasourceSrv.getMetricSources();
-    this.panelDsValue = this.panelCtrl.panel.datasource || null;
+    this.panelDsValue = this.panelCtrl.panel.datasource;
 
     for (let ds of this.datasources) {
       if (ds.value === this.panelDsValue) {
-        this.current = ds;
+        this.datasourceInstance = ds;
       }
     }
 
     this.addQueryDropdown = {text: 'Add Query', value: null, fake: true};
+
     // update next ref id
     this.panelCtrl.nextRefId = this.dashboard.getNextQueryLetter(this.panel);
     this.updateDatasourceOptions();
   }
 
   updateDatasourceOptions() {
-    this.hasQueryHelp = this.current.meta.hasQueryHelp;
-    this.queryOptions = this.current.meta.queryOptions;
+    if (this.datasourceInstance) {
+      this.hasQueryHelp = this.datasourceInstance.meta.hasQueryHelp;
+      this.queryOptions = this.datasourceInstance.meta.queryOptions;
+    }
   }
 
   getOptions(includeBuiltin) {
@@ -61,7 +64,7 @@ export class MetricsTabCtrl {
       return;
     }
 
-    this.current = option.datasource;
+    this.datasourceInstance = option.datasource;
     this.panelCtrl.setDatasource(option.datasource);
     this.updateDatasourceOptions();
   }
@@ -85,7 +88,7 @@ export class MetricsTabCtrl {
     this.queryTroubleshooterOpen = false;
     this.helpOpen = !this.helpOpen;
 
-    this.backendSrv.get(`/api/plugins/${this.current.meta.id}/markdown/query_help`).then(res => {
+    this.backendSrv.get(`/api/plugins/${this.datasourceInstance.meta.id}/markdown/query_help`).then(res => {
       var md = new Remarkable();
       this.helpHtml = this.$sce.trustAsHtml(md.render(res));
     });

+ 3 - 3
public/app/features/panel/partials/metrics_tab.html

@@ -73,7 +73,7 @@
 	</div>
 </div>
 
-<div class="query-editor-rows gf-form-group">
+<div class="query-editor-rows gf-form-group" ng-if="ctrl.datasourceInstance">
 	<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
 		<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
 			<plugin-component type="query-ctrl">
@@ -89,11 +89,11 @@
 				</span>
 				<span class="gf-form-query-letter-cell-letter">{{ctrl.panelCtrl.nextRefId}}</span>
 			</label>
-			<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.current.meta.mixed">
+			<button class="btn btn-secondary gf-form-btn" ng-click="ctrl.addQuery()" ng-hide="ctrl.datasourceInstance.meta.mixed">
 				Add Query
 			</button>
 
-			<div class="dropdown" ng-if="ctrl.current.meta.mixed">
+			<div class="dropdown" ng-if="ctrl.datasourceInstance.meta.mixed">
 				<gf-form-dropdown model="ctrl.addQueryDropdown" get-options="ctrl.getOptions(false)" on-change="ctrl.addMixedQuery($option)">
 				</gf-form-dropdown>
 			</div>

+ 2 - 0
public/app/features/panel/query_ctrl.ts

@@ -10,9 +10,11 @@ export class QueryCtrl {
   panel: any;
   hasRawMode: boolean;
   error: string;
+  isLastQuery: boolean;
 
   constructor(public $scope, private $injector) {
     this.panel = this.panelCtrl.panel;
+    this.isLastQuery = _.indexOf(this.panel.targets, this.target) === (this.panel.targets.length - 1);
   }
 
   refresh() {

+ 2 - 5
public/app/features/plugins/ds_edit_ctrl.ts

@@ -126,21 +126,18 @@ export class DataSourceEditCtrl {
         return;
       }
 
-      this.testing = {done: false};
+      this.testing = {done: false, status: 'error'};
 
       // make test call in no backend cache context
       this.backendSrv.withNoBackendCache(() => {
         return datasource.testDatasource().then(result => {
           this.testing.message = result.message;
           this.testing.status = result.status;
-          this.testing.title = result.title;
         }).catch(err => {
           if (err.statusText) {
-            this.testing.message = err.statusText;
-            this.testing.title = "HTTP Error";
+            this.testing.message = 'HTTP Error ' + err.statusText;
           } else {
             this.testing.message = err.message;
-            this.testing.title = "Unknown error";
           }
         });
       }).finally(() => {

+ 8 - 3
public/app/features/plugins/partials/ds_edit.html

@@ -59,9 +59,14 @@
 
 			<div ng-if="ctrl.testing" class="gf-form-group">
 				<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
-				<div class="alert-{{ctrl.testing.status}} alert">
-					<div class="alert-title">{{ctrl.testing.title}}</div>
-					<div ng-bind='ctrl.testing.message'></div>
+				<div class="alert-{{ctrl.testing.status}} alert" ng-show="ctrl.testing.done">
+					<div class="alert-icon">
+							<i class="fa fa-exclamation-triangle" ng-show="ctrl.testing.status === 'error'"></i>
+							<i class="fa fa-check" ng-show="ctrl.testing.status !== 'error'"></i>
+					</div>
+					<div class="alert-body">
+						<div class="alert-title">{{ctrl.testing.message}}</div>
+					</div>
 				</div>
 			</div>
 

+ 1 - 1
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -335,7 +335,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
       var dimensions = {};
 
       return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
-        return { status: 'success', message: 'Data source is working', title: 'Success' };
+        return { status: 'success', message: 'Data source is working' };
       });
     };
 

+ 4 - 4
public/app/plugins/datasource/elasticsearch/datasource.js

@@ -175,9 +175,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
       return this.getFields({type: 'date'}).then(function(dateFields) {
         var timeField = _.find(dateFields, {text: this.timeField});
         if (!timeField) {
-          return { status: "error", message: "No date field named " + this.timeField + ' found', title: "Error" };
+          return { status: "error", message: "No date field named " + this.timeField + ' found' };
         }
-        return { status: "success", message: "Index OK. Time field name OK.", title: "Success" };
+        return { status: "success", message: "Index OK. Time field name OK." };
       }.bind(this), function(err) {
         console.log(err);
         if (err.data && err.data.error) {
@@ -185,9 +185,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
           if (err.data.error.reason) {
             message = err.data.error.reason;
           }
-          return { status: "error", message: message, title: "Error" };
+          return { status: "error", message: message };
         } else {
-          return { status: "error", message: err.status, title: "Error" };
+          return { status: "error", message: err.status };
         }
       });
     };

+ 1 - 1
public/app/plugins/datasource/graphite/datasource.ts

@@ -205,7 +205,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
 
   this.testDatasource = function() {
     return this.metricFindQuery('*').then(function () {
-      return { status: "success", message: "Data source is working", title: "Success" };
+      return { status: "success", message: "Data source is working"};
     });
   };
 

+ 5 - 0
public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts

@@ -24,6 +24,11 @@ describe('GraphiteQueryCtrl', function() {
     ctx.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
     ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
     ctx.panelCtrl = {panel: {}};
+    ctx.panelCtrl = {
+      panel: {
+        targets: [ctx.target]
+      }
+    };
     ctx.panelCtrl.refresh = sinon.spy();
 
     ctx.ctrl = $controller(GraphiteQueryCtrl, {$scope: ctx.scope}, {

+ 3 - 3
public/app/plugins/datasource/influxdb/datasource.ts

@@ -196,11 +196,11 @@ export default class InfluxDatasource {
     return this.metricFindQuery('SHOW DATABASES').then(res => {
       let found = _.find(res, {text: this.database});
       if (!found) {
-        return { status: "error", message: "Could not find the specified database name.", title: "DB Not found" };
+        return { status: "error", message: "Could not find the specified database name." };
       }
-      return { status: "success", message: "Data source is working", title: "Success" };
+      return { status: "success", message: "Data source is working" };
     }).catch(err => {
-      return { status: "error", message: err.message, title: "Test Failed" };
+      return { status: "error", message: err.message };
     });
   }
 

+ 1 - 1
public/app/plugins/datasource/influxdb/partials/query.editor.html

@@ -92,7 +92,7 @@
     <div class="gf-form-inline" ng-if="ctrl.target.orderByTime === 'DESC'">
       <div class="gf-form">
         <label class="gf-form-label query-keyword width-7">ORDER BY</label>
-        <label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()">time <span classs="query-keyword">DESC</span> <i class="fa fa-remove"></i></label>
+        <label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()">time <span class="query-keyword">DESC</span> <i class="fa fa-remove"></i></label>
       </div>
       <div class="gf-form gf-form--grow">
 				<div class="gf-form-label gf-form-label--grow"></div>

+ 6 - 2
public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts

@@ -19,9 +19,13 @@ describe('InfluxDBQueryCtrl', function() {
     ctx.$q = $q;
     ctx.scope = $rootScope.$new();
     ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
-    ctx.panelCtrl = {panel: {}};
-    ctx.panelCtrl.refresh = sinon.spy();
     ctx.target = {target: {}};
+    ctx.panelCtrl = {
+      panel: {
+        targets: [ctx.target]
+      }
+    };
+    ctx.panelCtrl.refresh = sinon.spy();
     ctx.ctrl = $controller(InfluxQueryCtrl, {$scope: ctx.scope}, {
       panelCtrl: ctx.panelCtrl,
       target: ctx.target,

+ 3 - 3
public/app/plugins/datasource/mysql/datasource.ts

@@ -118,13 +118,13 @@ export class MysqlDatasource {
         }],
       }
     }).then(res => {
-      return { status: "success", message: "Database Connection OK", title: "Success" };
+      return { status: "success", message: "Database Connection OK"};
     }).catch(err => {
       console.log(err);
       if (err.data && err.data.message) {
-        return { status: "error", message: err.data.message, title: "Error" };
+        return { status: "error", message: err.data.message };
       } else {
-        return { status: "error", message: err.status, title: "Error" };
+        return { status: "error", message: err.status };
       }
     });
   }

+ 1 - 1
public/app/plugins/datasource/opentsdb/datasource.js

@@ -296,7 +296,7 @@ function (angular, _, dateMath) {
 
     this.testDatasource = function() {
       return this._performSuggestQuery('cpu', 'metrics').then(function () {
-        return { status: "success", message: "Data source is working", title: "Success" };
+        return { status: "success", message: "Data source is working" };
       });
     };
 

+ 5 - 1
public/app/plugins/datasource/opentsdb/specs/query-ctrl-specs.ts

@@ -18,7 +18,11 @@ describe('OpenTsQueryCtrl', function() {
     ctx.$q = $q;
     ctx.scope = $rootScope.$new();
     ctx.target = {target: ''};
-    ctx.panelCtrl = {panel: {}};
+    ctx.panelCtrl = {
+      panel: {
+        targets: [ctx.target]
+      }
+    };
     ctx.panelCtrl.refresh = sinon.spy();
     ctx.datasource.getAggregators = sinon.stub().returns(ctx.$q.when([]));
     ctx.datasource.getFilterTypes = sinon.stub().returns(ctx.$q.when([]));

+ 1 - 1
public/app/plugins/datasource/prometheus/datasource.ts

@@ -241,7 +241,7 @@ export class PrometheusDatasource {
 
   testDatasource() {
     return this.metricFindQuery('metrics(.*)').then(function() {
-      return { status: 'success', message: 'Data source is working', title: 'Success' };
+      return { status: 'success', message: 'Data source is working'};
     });
   }
 

+ 6 - 6
public/app/core/components/code_editor/mode-prometheus.js → public/app/plugins/datasource/prometheus/mode-prometheus.js

@@ -65,20 +65,20 @@ var PrometheusHighlightRules = function() {
       regex : "\\s+"
     } ],
     "start-label-matcher" : [ {
-      token : "label.name",
+      token : "keyword",
       regex : '[a-zA-Z_][a-zA-Z0-9_]*'
     }, {
-      token : "label.matching_operator",
-      regex : '=|!=|=~|!~'
+      token : "keyword.operator",
+      regex : '=~|=|!~|!='
     }, {
-      token : "label.value",
+      token : "string",
       regex : '"[^"]*"|\'[^\']*\''
     }, {
-      token : "label.matching_delimiter",
+      token : "punctuation.operator",
       regex : ",",
       push  : 'start-label-matcher'
     }, {
-      token : "label.matching_end",
+      token : "paren.rparen",
       regex : "}",
       next  : "start"
     } ]

+ 2 - 2
public/app/plugins/datasource/prometheus/partials/query.editor.html

@@ -1,8 +1,8 @@
 <query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
 	<div class="gf-form-inline">
 		<div class="gf-form gf-form--grow">
-			<code-editor content="ctrl.target.expr" on-change="ctrl.refreshMetricData()"
-				get-completer="ctrl.getCompleter()" data-mode="prometheus">
+			<code-editor content="ctrl.target.expr" datasource="ctrl.datasource" on-change="ctrl.refreshMetricData()"
+				get-completer="ctrl.getCompleter()" data-mode="prometheus" code-editor-focus="ctrl.isLastQuery">
 			</code-editor>
 		</div>
 	</div>

+ 0 - 0
public/app/core/components/code_editor/snippets/prometheus.js → public/app/plugins/datasource/prometheus/snippets/prometheus.js


+ 27 - 0
public/app/plugins/datasource/prometheus/specs/completer_specs.ts

@@ -0,0 +1,27 @@
+import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
+
+import {PromCompleter} from '../completer';
+import {PrometheusDatasource} from '../datasource';
+
+describe('Prometheus editor completer', function() {
+
+  let editor = {};
+  let session = {
+    getTokenAt: sinon.stub().returns({}),
+    getLine:  sinon.stub().returns(""),
+  };
+
+  let datasourceStub = <PrometheusDatasource>{};
+  let completer = new PromCompleter(datasourceStub);
+
+  describe("When inside brackets", () => {
+
+    it("Should return range vectors", () => {
+      completer.getCompletions(editor, session, 10, "[", (s, res) => {
+        expect(res[0]).to.eql({caption: '1s', value: '[1s', meta: 'range vector'});
+      });
+    });
+
+  });
+
+});

+ 2 - 14
public/app/plugins/panel/graph/axes_editor.html

@@ -48,26 +48,14 @@
 		<div class="gf-form">
 			<label class="gf-form-label width-6">Mode</label>
 			<div class="gf-form-select-wrapper max-width-15">
-				<select class="gf-form-input" ng-model="ctrl.panel.xaxis.mode" ng-options="v as k for (k, v) in ctrl.xAxisModes" ng-change="ctrl.xAxisOptionChanged()"> </select>
+				<select class="gf-form-input" ng-model="ctrl.panel.xaxis.mode" ng-options="v as k for (k, v) in ctrl.xAxisModes" ng-change="ctrl.xAxisModeChanged()"> </select>
 			</div>
 		</div>
 
-    <!-- Table mode -->
-		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
-			<label class="gf-form-label width-6">Name</label>
-      <metric-segment-model property="ctrl.panel.xaxis.name" get-options="ctrl.getDataFieldNames(false)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
-		</div>
-
-    <!-- Series mode -->
-		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
-			<label class="gf-form-label width-6">Value</label>
-      <metric-segment-model property="ctrl.panel.xaxis.values[0]" get-options="ctrl.getDataFieldNames(true)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
-		</div>
-
 		<!-- Series mode -->
 		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
 			<label class="gf-form-label width-6">Value</label>
-      <metric-segment-model property="ctrl.panel.xaxis.values[0]" options="ctrl.xAxisStatOptions" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
+      <metric-segment-model property="ctrl.panel.xaxis.values[0]" options="ctrl.xAxisStatOptions" on-change="ctrl.xAxisValueChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
 		</div>
 
 		<!-- Histogram mode -->

+ 6 - 4
public/app/plugins/panel/graph/axes_editor.ts

@@ -59,10 +59,12 @@ export class AxesEditorCtrl {
     this.panelCtrl.render();
   }
 
-  xAxisOptionChanged()  {
-    if (!this.panel.xaxis.values || !this.panel.xaxis.values[0]){
-      this.panelCtrl.processor.setPanelDefaultsForNewXAxisMode();
-    }
+  xAxisModeChanged()  {
+    this.panelCtrl.processor.setPanelDefaultsForNewXAxisMode();
+    this.panelCtrl.onDataReceived(this.panelCtrl.dataList);
+  }
+
+  xAxisValueChanged() {
     this.panelCtrl.onDataReceived(this.panelCtrl.dataList);
   }
 

+ 1 - 1
public/app/plugins/panel/text/editor.html

@@ -3,7 +3,7 @@
 		<div class="gf-form">
 			<span class="gf-form-label">Mode</span>
 			<span class="gf-form-select-wrapper">
-				<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
+				<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ['html','markdown']"></select>
 			</span>
 		</div>
 	</div>

+ 12 - 12
public/sass/_variables.dark.scss

@@ -103,7 +103,7 @@ $tight-form-func-bg: 		    #333;
 $tight-form-func-highlight-bg:  #444;
 
 $modal-background: $black;
-$code-tag-bg:      $dark-5;
+$code-tag-bg:      $gray-1;
 $code-tag-border:  lighten($code-tag-bg, 2%);
 
 
@@ -238,17 +238,15 @@ $paginationActiveBackground:          $blue;
 
 // Form states and alerts
 // -------------------------
-$state-warning-text:      $warn;
-$state-warning-bg:        $brand-warning;
+$warning-text-color:      $warn;
+$error-text-color:        #E84D4D;
+$success-text-color:      #12D95A;
+$info-text-color:         $blue-dark;
 
-$errorText:               #E84D4D;
-$errorBackground:         $btn-danger-bg;
-
-$successText:             #12D95A;
-$successBackground:       $btn-success-bg;
-
-$infoText:                $blue-dark;
-$infoBackground:          $blue-dark;
+$alert-error-bg:          linear-gradient(90deg, #d44939, #e0603d);
+$alert-success-bg:        linear-gradient(90deg, #3aa655, #47b274);
+$alert-warning-bg:        linear-gradient(90deg, #d44939, #e0603d);
+$alert-info-bg:           linear-gradient(100deg, #1a4552, #00374a);
 
 // popover
 $popover-bg:              $panel-bg;
@@ -258,6 +256,8 @@ $popover-border-color:    $gray-1;
 $popover-help-bg:         $btn-secondary-bg;
 $popover-help-color:      $text-color;
 
+$popover-error-bg:        $btn-danger-bg;
+
 // Tooltips and popovers
 // -------------------------
 $tooltipColor:            $popover-help-color;
@@ -276,7 +276,7 @@ $card-background-hover: linear-gradient(135deg, #343434, #262626);
 $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3);
 
 // info box
-$info-box-background: linear-gradient(100deg, #1a4552, #0b2127);
+$info-box-background: linear-gradient(100deg, #1a4552, #00374a);
 
 // footer
 $footer-link-color:   $gray-1;

+ 9 - 15
public/sass/_variables.light.scss

@@ -259,29 +259,23 @@ $paginationActiveBackground:          $blue;
 
 // Form states and alerts
 // -------------------------
-$state-warning-text:             lighten($orange, 10%);
-$state-warning-bg:       $orange;
-$warningBorder:           transparent;
+$warning-text-color:            lighten($orange, 10%);
+$error-text-color:              lighten($red, 10%);
+$success-text-color:            lighten($green, 10%);
+$info-text-color:                     $blue;
 
-$errorText:               lighten($red, 10%);
-$errorBackground:         $red;
-$errorBorder:             transparent;
-
-$successText:             lighten($green, 10%);
-$successBackground:       $green;
-$successBorder:           transparent;
-
-$infoText:                $blue;
-$infoBackground:          $blue-dark;
-$infoBorder:              transparent;
+$alert-error-bg:          linear-gradient(90deg, #d44939, #e0603d);
+$alert-success-bg:        linear-gradient(90deg, #3aa655, #47b274);
+$alert-warning-bg:        linear-gradient(90deg, #d44939, #e0603d);
+$alert-info-bg:           $blue-dark;
 
 // popover
 $popover-bg:              $gray-5;
 $popover-color:           $text-color;
 $popover-border-color:    $gray-3;
-
 $popover-help-bg:         $blue-dark;
 $popover-help-color:      $gray-6;
+$popover-error-bg:        $btn-danger-bg;
 
 // Tooltips and popovers
 // -------------------------

+ 11 - 11
public/sass/base/_type.scss

@@ -31,21 +31,21 @@ cite    { font-style: normal; }
 a.muted:hover,
 a.muted:focus        { color: darken($text-muted, 10%); }
 
-.text-warning        { color: $state-warning-text; }
+.text-warning        { color: $warning-text-color; }
 a.text-warning:hover,
-a.text-warning:focus { color: darken($state-warning-text, 10%); }
+a.text-warning:focus { color: darken($warning-text-color, 10%); }
 
-.text-error          { color: $errorText; }
+.text-error          { color: $error-text-color; }
 a.text-error:hover,
-a.text-error:focus   { color: darken($errorText, 10%); }
+a.text-error:focus   { color: darken($error-text-color, 10%); }
 
-.text-info           { color: $infoText; }
+.text-info           { color: $info-text-color; }
 a.text-info:hover,
-a.text-info:focus    { color: darken($infoText, 10%); }
+a.text-info:focus    { color: darken($info-text-color, 10%); }
 
-.text-success        { color: $successText; }
+.text-success        { color: $success-text-color; }
 a.text-success:hover,
-a.text-success:focus { color: darken($successText, 10%); }
+a.text-success:focus { color: darken($success-text-color, 10%); }
 a { cursor: pointer; }
 
 a[disabled] {
@@ -130,7 +130,7 @@ small,
 mark,
 .mark {
   padding: .2em;
-  background-color: $state-warning-bg;
+  background: $alert-warning-bg;
 }
 
 
@@ -296,7 +296,7 @@ a.external-link {
     max-width: 100%;
   }
 
-  ul {
+  ul, ol {
     padding-left: $spacer*1.5;
     margin-bottom: $spacer;
   }
@@ -328,7 +328,7 @@ a.external-link {
     margin-bottom: 0;
   }
 
-  ul:last-child {
+  ul:last-child, ol:last-child {
     margin-bottom: 0;
   }
 }

+ 36 - 24
public/sass/components/_alerts.scss

@@ -7,60 +7,57 @@
 // -------------------------
 
 .alert {
-  padding: 0.5rem 2rem 0.5rem 1rem;
+  padding: 1.25rem 2rem 1.25rem 1.5rem;
   margin-bottom: $line-height-base;
-  text-shadow: 0 1px 0 rgba(255,255,255,.5);
-  background-color: $state-warning-bg;
+  text-shadow: 0 2px 0 rgba(255,255,255,.5);
+  background: $alert-error-bg;
   position: relative;
   color: $white;
-  text-shadow: 0 1px 0 rgba(0,0,0,.5);
+  text-shadow: 0 1px 0 rgba(0,0,0,.2);
   border-radius: 2px;
+  display: flex;
+  flex-direction: row;
 }
 
 // Alternate styles
 // -------------------------
 
 .alert-success {
-  background-color: $successBackground;
+  background: $alert-success-bg;
 }
 
 .alert-danger,
 .alert-error {
-  background-color: $errorBackground;
+  background: $alert-error-bg;
 }
 
 .alert-info {
-  background-color: $infoBackground;
+  background: $alert-info-bg;
 }
 
 .alert-warning {
-  background-color: $state-warning-bg;
+  background: $alert-warning-bg;
 }
 
 .page-alert-list {
   z-index: 8000;
-  min-width: 300px;
-  max-width: 300px;
+  min-width: 400px;
+  max-width: 600px;
   position: fixed;
-  right: 20px;
-  top: 56px;
+  right: 10px;
+  top: 60px;
 }
 
 .alert-close {
-  position: absolute;
-  top: -4px;
-  right: -2px;
-  width: 16px;
-  height: 16px;
-  padding: 0;
-  background: $white;
-  border-radius: 50%;
+  padding: 0 0 0 1rem;
   border: none;
-  font-size: 1.1rem;
-  color: $dark-4;
+  background: none;
+  display: flex;
+  align-items: center;
   .fa {
-    position: relative;
-    top: -2px;
+    align-self: flex-end;
+    font-size: 1.5rem;
+    color: rgba(255,255,255,.75)
   }
 }
 
@@ -68,3 +65,18 @@
   font-weight: $font-weight-semi-bold;
   padding-bottom: 2px;
 }
+
+.alert-icon {
+  padding: 0 1rem 0 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 2.5rem;
+  .fa {
+    font-size: 1.5rem;
+  }
+}
+
+.alert-body {
+  flex-grow: 1;
+}

+ 5 - 1
public/sass/components/_code_editor.scss

@@ -28,7 +28,7 @@
   background-color: $dropdownBackground !important;
   color: $dropdownLinkColor !important;
   border: 1px solid $dropdownBorder !important;
-  width: 320px !important;
+  width: 550px !important;
 
   .ace_scroller {
     .ace_selected, .ace_active-line, .ace_line-hover {
@@ -77,3 +77,7 @@ $doc-font-size: $font-size-sm;
 .ace_tooltip {
   border-radius: 3px;
 }
+
+.ace_hidden-cursors .ace_cursor {
+  opacity: 0 !important;
+}

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

@@ -5,7 +5,7 @@ $useDropShadow: false;
 $attachmentOffset: 0%;
 $easing: cubic-bezier(0, 0, 0.265, 1.00);
 
-@include drop-theme("error", $errorBackground, $popover-color);
+@include drop-theme("error", $popover-error-bg, $popover-color);
 @include drop-theme("popover", $popover-bg, $popover-color, $popover-border-color);
 @include drop-theme("help", $popover-help-bg, $popover-help-color);
 

+ 1 - 1
public/sass/pages/_dashboard.scss

@@ -138,7 +138,7 @@ div.flot-text {
   &--error {
     display: block;
     color: $text-color;
-    @include panel-corner-color($errorBackground);
+    @include panel-corner-color($popover-error-bg);
     .fa:before {
       content: "\f12a";
     }

+ 14 - 0
public/test/core/utils/kbn_specs.js

@@ -167,6 +167,20 @@ define([
       var res = kbn.calculateInterval(range, 900, '>15ms');
       expect(res.interval).to.be('15ms');
     });
+
+    it('1d 1 resolution', function() {
+      var range = { from: dateMath.parse('now-1d'), to: dateMath.parse('now') };
+      var res = kbn.calculateInterval(range, 1, null);
+      expect(res.interval).to.be('1d');
+      expect(res.intervalMs).to.be(86400000);
+    });
+
+    it('86399s 1 resolution', function() {
+      var range = { from: dateMath.parse('now-86390s'), to: dateMath.parse('now') };
+      var res = kbn.calculateInterval(range, 1, null);
+      expect(res.interval).to.be('12h');
+      expect(res.intervalMs).to.be(43200000);
+    });
   });
 
   describe('hex', function() {

+ 6 - 3
public/views/index.html

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