Browse Source

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 years ago
parent
commit
bc03640d6b
54 changed files with 364 additions and 238 deletions
  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
 - UX changes to nav & side menu
 - New dashboard grid layout system
 - 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)
 * **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)
 # 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
 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.
 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
 ### 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
 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.
 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
 ### 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.
 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
 ### 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.
 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
 ## 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,
 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
 ## 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
 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.
 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
 ### Field & Aggregation functions
 In the `SELECT` row you can specify what fields and functions you want to use. If you have a
 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:
 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
 # 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
 Grafana ships with a built-in MySQL data source plugin that allow you to query any visualize
 data from a MySQL compatible database.
 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:
 Query editor with example query:
 
 
-![](/img/docs/v43/mysql_table_query.png)
-
+{{< docs-imagebox img="/img/docs/v45/mysql_table_query.png" >}}
 
 
 The query:
 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).
 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
 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
 The metrics tab defines what series data and sources to render.  Each datasource provides different
 options.
 options.
 
 
-## Axes & Grid
+## Axes
 
 
 ![](/img/docs/v43/graph_axes_grid_options.png)
 ![](/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
 - ``Unit`` - The display unit for the Y value
 - ``Grid Max`` - The maximum Y value. (default auto)
 - ``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
 # 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
 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.
 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
 ## Options overview
 
 
 The table panel has many ways to manipulate your data for optimal presentation.
 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.
 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
 ## 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
 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
 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.
 you want in the table. Only applicable for some transforms.
 
 
+<div class="clearfix"></div>
+
 ### Time series to rows
 ### 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.
 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
 ### 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.
 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
 ### 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.
 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
 ### 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
 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.
 mode then any queries you have in the metrics tab will be ignored.
 
 
 ### JSON Data
 ### 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
 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.
 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
 ## 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.
 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.
 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.
 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.
 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
 # 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
 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
 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
 ### 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
 ### 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!
 ### 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:
 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
 The default Grafana port is `3000`, this port requires extra permissions
 on windows. Edit `custom.ini` and uncomment the `http_port`
 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.
 That port should not require extra Windows privileges.
 
 
 Start Grafana by executing `grafana-server.exe`, preferably from the
 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
 ## 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.
 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 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 next button to advance to the next Dashboard in the Playlist.
 Click the back button to rewind to the previous 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
 ### 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 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.
 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"
     "company": "Grafana Labs"
   },
   },
   "name": "grafana",
   "name": "grafana",
-  "version": "4.5.0-beta1",
+  "version": "4.5.0",
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"
     "url": "http://github.com/grafana/grafana.git"
@@ -53,10 +53,6 @@
     "systemjs": "0.19.41",
     "systemjs": "0.19.41",
     "zone.js": "^0.7.2"
     "zone.js": "^0.7.2"
   },
   },
-  "engines": {
-    "node": "4.x",
-    "npm": "2.14.x"
-  },
   "scripts": {
   "scripts": {
     "build": "./node_modules/grunt-cli/bin/grunt",
     "build": "./node_modules/grunt-cli/bin/grunt",
     "test": "./node_modules/grunt-cli/bin/grunt test",
     "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)
 		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 {
 	if err := bus.Dispatch(&dsQuery); err != nil {
 		return ApiError(500, "failed to fetch data source", err)
 		return ApiError(500, "failed to fetch data source", err)
 	}
 	}

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

@@ -5,6 +5,8 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	gocontext "context"
+
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/null"
 	"github.com/grafana/grafana/pkg/components/null"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"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)
 	resp, err := c.HandleRequest(context.Ctx, req)
 	if err != nil {
 	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)
 		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]
 	iv := payload[saltLength : saltLength+aes.BlockSize]
 	payload = payload[saltLength+aes.BlockSize:]
 	payload = payload[saltLength+aes.BlockSize:]
+	payloadDst := make([]byte, len(payload))
 
 
 	stream := cipher.NewCFBDecrypter(block, iv)
 	stream := cipher.NewCFBDecrypter(block, iv)
 
 
 	// XORKeyStream can work in-place if the two arguments are the same.
 	// 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) {
 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_TAB_SIZE = 2;
 const DEFAULT_BEHAVIOURS = true;
 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/";
 const GRAFANA_MODULE_BASE = "public/app/core/components/code_editor/";
 
 
 // Trick for loading additional modules
 // Trick for loading additional modules
-function setModuleUrl(moduleType, name) {
+function setModuleUrl(moduleType, name, pluginBaseUrl = null) {
   let baseUrl = ACE_SRC_BASE;
   let baseUrl = ACE_SRC_BASE;
   let aceModeName = `ace/${moduleType}/${name}`;
   let aceModeName = `ace/${moduleType}/${name}`;
   let moduleName = `${moduleType}-${name}`;
   let moduleName = `${moduleType}-${name}`;
@@ -54,6 +54,10 @@ function setModuleUrl(moduleType, name) {
     baseUrl = GRAFANA_MODULE_BASE;
     baseUrl = GRAFANA_MODULE_BASE;
   }
   }
 
 
+  if (pluginBaseUrl) {
+    baseUrl = pluginBaseUrl + '/';
+  }
+
   if (moduleType === 'snippets') {
   if (moduleType === 'snippets') {
     componentName = `${moduleType}/${name}.js`;
     componentName = `${moduleType}/${name}.js`;
   }
   }
@@ -111,6 +115,17 @@ function link(scope, elem, attrs) {
   let textarea = elem.find("textarea");
   let textarea = elem.find("textarea");
   textarea.addClass('gf-form-input');
   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
   // Event handlers
   editorSession.on('change', (e) => {
   editorSession.on('change', (e) => {
     scope.$apply(() => {
     scope.$apply(() => {
@@ -148,8 +163,8 @@ function link(scope, elem, attrs) {
 
 
   function setLangMode(lang) {
   function setLangMode(lang) {
     let aceModeName = `ace/mode/${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);
     editorSession.setMode(aceModeName);
 
 
     ace.config.loadModule("ace/ext/language_tools", (language_tools) => {
     ace.config.loadModule("ace/ext/language_tools", (language_tools) => {
@@ -199,6 +214,8 @@ export function codeEditorDirective() {
     template: editorTemplate,
     template: editorTemplate,
     scope: {
     scope: {
       content: "=",
       content: "=",
+      datasource: "=",
+      codeEditorFocus: "<",
       onChange: "&",
       onChange: "&",
       getCompleter: "&"
       getCompleter: "&"
     },
     },

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

@@ -16,7 +16,7 @@ export class AlertSrv {
 
 
   init() {
   init() {
     this.$rootScope.onAppEvent('alert-error', (e, alert) => {
     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);
 
 
     this.$rootScope.onAppEvent('alert-warning', (e, alert) => {
     this.$rootScope.onAppEvent('alert-warning', (e, alert) => {
@@ -33,6 +33,14 @@ export class AlertSrv {
     appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
     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) {
   set(title, text, severity, timeout) {
     if (_.isObject(text)) {
     if (_.isObject(text)) {
       console.log('alert error', text);
       console.log('alert error', text);
@@ -45,6 +53,7 @@ export class AlertSrv {
       title: title || '',
       title: title || '',
       text: text || '',
       text: text || '',
       severity: severity || 'info',
       severity: severity || 'info',
+      icon: this.getIconForSeverity(severity)
     };
     };
 
 
     var newAlertJson = angular.toJson(newAlert);
     var newAlertJson = angular.toJson(newAlert);

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

@@ -64,7 +64,13 @@ export class BackendSrv {
     }
     }
 
 
     if (data.message) {
     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;
     throw data;
@@ -97,7 +103,7 @@ export class BackendSrv {
       return results.data;
       return results.data;
     }, err => {
     }, err => {
       // handle unauthorized
       // handle unauthorized
-      if (err.status === 401 && firstAttempt) {
+      if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
         return this.loginPing().then(() => {
         return this.loginPing().then(() => {
           options.retry = 1;
           options.retry = 1;
           return this.request(options);
           return this.request(options);

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

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

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

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

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

@@ -9,7 +9,7 @@ export class MetricsTabCtrl {
   panel: any;
   panel: any;
   panelCtrl: any;
   panelCtrl: any;
   datasources: any[];
   datasources: any[];
-  current: any;
+  datasourceInstance: any;
   nextRefId: string;
   nextRefId: string;
   dashboard: DashboardModel;
   dashboard: DashboardModel;
   panelDsValue: any;
   panelDsValue: any;
@@ -29,23 +29,26 @@ export class MetricsTabCtrl {
     this.panel = this.panelCtrl.panel;
     this.panel = this.panelCtrl.panel;
     this.dashboard = this.panelCtrl.dashboard;
     this.dashboard = this.panelCtrl.dashboard;
     this.datasources = datasourceSrv.getMetricSources();
     this.datasources = datasourceSrv.getMetricSources();
-    this.panelDsValue = this.panelCtrl.panel.datasource || null;
+    this.panelDsValue = this.panelCtrl.panel.datasource;
 
 
     for (let ds of this.datasources) {
     for (let ds of this.datasources) {
       if (ds.value === this.panelDsValue) {
       if (ds.value === this.panelDsValue) {
-        this.current = ds;
+        this.datasourceInstance = ds;
       }
       }
     }
     }
 
 
     this.addQueryDropdown = {text: 'Add Query', value: null, fake: true};
     this.addQueryDropdown = {text: 'Add Query', value: null, fake: true};
+
     // update next ref id
     // update next ref id
     this.panelCtrl.nextRefId = this.dashboard.getNextQueryLetter(this.panel);
     this.panelCtrl.nextRefId = this.dashboard.getNextQueryLetter(this.panel);
     this.updateDatasourceOptions();
     this.updateDatasourceOptions();
   }
   }
 
 
   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) {
   getOptions(includeBuiltin) {
@@ -61,7 +64,7 @@ export class MetricsTabCtrl {
       return;
       return;
     }
     }
 
 
-    this.current = option.datasource;
+    this.datasourceInstance = option.datasource;
     this.panelCtrl.setDatasource(option.datasource);
     this.panelCtrl.setDatasource(option.datasource);
     this.updateDatasourceOptions();
     this.updateDatasourceOptions();
   }
   }
@@ -85,7 +88,7 @@ export class MetricsTabCtrl {
     this.queryTroubleshooterOpen = false;
     this.queryTroubleshooterOpen = false;
     this.helpOpen = !this.helpOpen;
     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();
       var md = new Remarkable();
       this.helpHtml = this.$sce.trustAsHtml(md.render(res));
       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>
 </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}">
 	<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">
 		<rebuild-on-change property="ctrl.panel.datasource || target.datasource" show-null="true">
 			<plugin-component type="query-ctrl">
 			<plugin-component type="query-ctrl">
@@ -89,11 +89,11 @@
 				</span>
 				</span>
 				<span class="gf-form-query-letter-cell-letter">{{ctrl.panelCtrl.nextRefId}}</span>
 				<span class="gf-form-query-letter-cell-letter">{{ctrl.panelCtrl.nextRefId}}</span>
 			</label>
 			</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
 				Add Query
 			</button>
 			</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 model="ctrl.addQueryDropdown" get-options="ctrl.getOptions(false)" on-change="ctrl.addMixedQuery($option)">
 				</gf-form-dropdown>
 				</gf-form-dropdown>
 			</div>
 			</div>

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

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

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

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

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

@@ -59,9 +59,14 @@
 
 
 			<div ng-if="ctrl.testing" class="gf-form-group">
 			<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>
 				<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>
 			</div>
 			</div>
 
 

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

@@ -335,7 +335,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
       var dimensions = {};
       var dimensions = {};
 
 
       return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
       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) {
       return this.getFields({type: 'date'}).then(function(dateFields) {
         var timeField = _.find(dateFields, {text: this.timeField});
         var timeField = _.find(dateFields, {text: this.timeField});
         if (!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) {
       }.bind(this), function(err) {
         console.log(err);
         console.log(err);
         if (err.data && err.data.error) {
         if (err.data && err.data.error) {
@@ -185,9 +185,9 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
           if (err.data.error.reason) {
           if (err.data.error.reason) {
             message = err.data.error.reason;
             message = err.data.error.reason;
           }
           }
-          return { status: "error", message: message, title: "Error" };
+          return { status: "error", message: message };
         } else {
         } 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() {
   this.testDatasource = function() {
     return this.metricFindQuery('*').then(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.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
     ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
     ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
     ctx.panelCtrl = {panel: {}};
     ctx.panelCtrl = {panel: {}};
+    ctx.panelCtrl = {
+      panel: {
+        targets: [ctx.target]
+      }
+    };
     ctx.panelCtrl.refresh = sinon.spy();
     ctx.panelCtrl.refresh = sinon.spy();
 
 
     ctx.ctrl = $controller(GraphiteQueryCtrl, {$scope: ctx.scope}, {
     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 => {
     return this.metricFindQuery('SHOW DATABASES').then(res => {
       let found = _.find(res, {text: this.database});
       let found = _.find(res, {text: this.database});
       if (!found) {
       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 => {
     }).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-inline" ng-if="ctrl.target.orderByTime === 'DESC'">
       <div class="gf-form">
       <div class="gf-form">
         <label class="gf-form-label query-keyword width-7">ORDER BY</label>
         <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>
       <div class="gf-form gf-form--grow">
       <div class="gf-form gf-form--grow">
 				<div class="gf-form-label gf-form-label--grow"></div>
 				<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.$q = $q;
     ctx.scope = $rootScope.$new();
     ctx.scope = $rootScope.$new();
     ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
     ctx.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
-    ctx.panelCtrl = {panel: {}};
-    ctx.panelCtrl.refresh = sinon.spy();
     ctx.target = {target: {}};
     ctx.target = {target: {}};
+    ctx.panelCtrl = {
+      panel: {
+        targets: [ctx.target]
+      }
+    };
+    ctx.panelCtrl.refresh = sinon.spy();
     ctx.ctrl = $controller(InfluxQueryCtrl, {$scope: ctx.scope}, {
     ctx.ctrl = $controller(InfluxQueryCtrl, {$scope: ctx.scope}, {
       panelCtrl: ctx.panelCtrl,
       panelCtrl: ctx.panelCtrl,
       target: ctx.target,
       target: ctx.target,

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

@@ -118,13 +118,13 @@ export class MysqlDatasource {
         }],
         }],
       }
       }
     }).then(res => {
     }).then(res => {
-      return { status: "success", message: "Database Connection OK", title: "Success" };
+      return { status: "success", message: "Database Connection OK"};
     }).catch(err => {
     }).catch(err => {
       console.log(err);
       console.log(err);
       if (err.data && err.data.message) {
       if (err.data && err.data.message) {
-        return { status: "error", message: err.data.message, title: "Error" };
+        return { status: "error", message: err.data.message };
       } else {
       } 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() {
     this.testDatasource = function() {
       return this._performSuggestQuery('cpu', 'metrics').then(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.$q = $q;
     ctx.scope = $rootScope.$new();
     ctx.scope = $rootScope.$new();
     ctx.target = {target: ''};
     ctx.target = {target: ''};
-    ctx.panelCtrl = {panel: {}};
+    ctx.panelCtrl = {
+      panel: {
+        targets: [ctx.target]
+      }
+    };
     ctx.panelCtrl.refresh = sinon.spy();
     ctx.panelCtrl.refresh = sinon.spy();
     ctx.datasource.getAggregators = sinon.stub().returns(ctx.$q.when([]));
     ctx.datasource.getAggregators = sinon.stub().returns(ctx.$q.when([]));
     ctx.datasource.getFilterTypes = 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() {
   testDatasource() {
     return this.metricFindQuery('metrics(.*)').then(function() {
     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+"
       regex : "\\s+"
     } ],
     } ],
     "start-label-matcher" : [ {
     "start-label-matcher" : [ {
-      token : "label.name",
+      token : "keyword",
       regex : '[a-zA-Z_][a-zA-Z0-9_]*'
       regex : '[a-zA-Z_][a-zA-Z0-9_]*'
     }, {
     }, {
-      token : "label.matching_operator",
-      regex : '=|!=|=~|!~'
+      token : "keyword.operator",
+      regex : '=~|=|!~|!='
     }, {
     }, {
-      token : "label.value",
+      token : "string",
       regex : '"[^"]*"|\'[^\']*\''
       regex : '"[^"]*"|\'[^\']*\''
     }, {
     }, {
-      token : "label.matching_delimiter",
+      token : "punctuation.operator",
       regex : ",",
       regex : ",",
       push  : 'start-label-matcher'
       push  : 'start-label-matcher'
     }, {
     }, {
-      token : "label.matching_end",
+      token : "paren.rparen",
       regex : "}",
       regex : "}",
       next  : "start"
       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">
 <query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
 	<div class="gf-form-inline">
 	<div class="gf-form-inline">
 		<div class="gf-form gf-form--grow">
 		<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>
 			</code-editor>
 		</div>
 		</div>
 	</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">
 		<div class="gf-form">
 			<label class="gf-form-label width-6">Mode</label>
 			<label class="gf-form-label width-6">Mode</label>
 			<div class="gf-form-select-wrapper max-width-15">
 			<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>
 		</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 -->
 		<!-- Series mode -->
 		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
 		<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
 			<label class="gf-form-label width-6">Value</label>
 			<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>
 		</div>
 
 
 		<!-- Histogram mode -->
 		<!-- Histogram mode -->

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

@@ -59,10 +59,12 @@ export class AxesEditorCtrl {
     this.panelCtrl.render();
     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);
     this.panelCtrl.onDataReceived(this.panelCtrl.dataList);
   }
   }
 
 

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

@@ -3,7 +3,7 @@
 		<div class="gf-form">
 		<div class="gf-form">
 			<span class="gf-form-label">Mode</span>
 			<span class="gf-form-label">Mode</span>
 			<span class="gf-form-select-wrapper">
 			<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>
 			</span>
 		</div>
 		</div>
 	</div>
 	</div>

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

@@ -103,7 +103,7 @@ $tight-form-func-bg: 		    #333;
 $tight-form-func-highlight-bg:  #444;
 $tight-form-func-highlight-bg:  #444;
 
 
 $modal-background: $black;
 $modal-background: $black;
-$code-tag-bg:      $dark-5;
+$code-tag-bg:      $gray-1;
 $code-tag-border:  lighten($code-tag-bg, 2%);
 $code-tag-border:  lighten($code-tag-bg, 2%);
 
 
 
 
@@ -238,17 +238,15 @@ $paginationActiveBackground:          $blue;
 
 
 // Form states and alerts
 // 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
 $popover-bg:              $panel-bg;
 $popover-bg:              $panel-bg;
@@ -258,6 +256,8 @@ $popover-border-color:    $gray-1;
 $popover-help-bg:         $btn-secondary-bg;
 $popover-help-bg:         $btn-secondary-bg;
 $popover-help-color:      $text-color;
 $popover-help-color:      $text-color;
 
 
+$popover-error-bg:        $btn-danger-bg;
+
 // Tooltips and popovers
 // Tooltips and popovers
 // -------------------------
 // -------------------------
 $tooltipColor:            $popover-help-color;
 $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);
 $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3);
 
 
 // info box
 // info box
-$info-box-background: linear-gradient(100deg, #1a4552, #0b2127);
+$info-box-background: linear-gradient(100deg, #1a4552, #00374a);
 
 
 // footer
 // footer
 $footer-link-color:   $gray-1;
 $footer-link-color:   $gray-1;

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

@@ -259,29 +259,23 @@ $paginationActiveBackground:          $blue;
 
 
 // Form states and alerts
 // 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
 $popover-bg:              $gray-5;
 $popover-bg:              $gray-5;
 $popover-color:           $text-color;
 $popover-color:           $text-color;
 $popover-border-color:    $gray-3;
 $popover-border-color:    $gray-3;
-
 $popover-help-bg:         $blue-dark;
 $popover-help-bg:         $blue-dark;
 $popover-help-color:      $gray-6;
 $popover-help-color:      $gray-6;
+$popover-error-bg:        $btn-danger-bg;
 
 
 // Tooltips and popovers
 // Tooltips and popovers
 // -------------------------
 // -------------------------

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

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

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

@@ -7,60 +7,57 @@
 // -------------------------
 // -------------------------
 
 
 .alert {
 .alert {
-  padding: 0.5rem 2rem 0.5rem 1rem;
+  padding: 1.25rem 2rem 1.25rem 1.5rem;
   margin-bottom: $line-height-base;
   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;
   position: relative;
   color: $white;
   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;
   border-radius: 2px;
+  display: flex;
+  flex-direction: row;
 }
 }
 
 
 // Alternate styles
 // Alternate styles
 // -------------------------
 // -------------------------
 
 
 .alert-success {
 .alert-success {
-  background-color: $successBackground;
+  background: $alert-success-bg;
 }
 }
 
 
 .alert-danger,
 .alert-danger,
 .alert-error {
 .alert-error {
-  background-color: $errorBackground;
+  background: $alert-error-bg;
 }
 }
 
 
 .alert-info {
 .alert-info {
-  background-color: $infoBackground;
+  background: $alert-info-bg;
 }
 }
 
 
 .alert-warning {
 .alert-warning {
-  background-color: $state-warning-bg;
+  background: $alert-warning-bg;
 }
 }
 
 
 .page-alert-list {
 .page-alert-list {
   z-index: 8000;
   z-index: 8000;
-  min-width: 300px;
-  max-width: 300px;
+  min-width: 400px;
+  max-width: 600px;
   position: fixed;
   position: fixed;
-  right: 20px;
-  top: 56px;
+  right: 10px;
+  top: 60px;
 }
 }
 
 
 .alert-close {
 .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;
   border: none;
-  font-size: 1.1rem;
-  color: $dark-4;
+  background: none;
+  display: flex;
+  align-items: center;
   .fa {
   .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;
   font-weight: $font-weight-semi-bold;
   padding-bottom: 2px;
   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;
   background-color: $dropdownBackground !important;
   color: $dropdownLinkColor !important;
   color: $dropdownLinkColor !important;
   border: 1px solid $dropdownBorder !important;
   border: 1px solid $dropdownBorder !important;
-  width: 320px !important;
+  width: 550px !important;
 
 
   .ace_scroller {
   .ace_scroller {
     .ace_selected, .ace_active-line, .ace_line-hover {
     .ace_selected, .ace_active-line, .ace_line-hover {
@@ -77,3 +77,7 @@ $doc-font-size: $font-size-sm;
 .ace_tooltip {
 .ace_tooltip {
   border-radius: 3px;
   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%;
 $attachmentOffset: 0%;
 $easing: cubic-bezier(0, 0, 0.265, 1.00);
 $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("popover", $popover-bg, $popover-color, $popover-border-color);
 @include drop-theme("help", $popover-help-bg, $popover-help-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 {
   &--error {
     display: block;
     display: block;
     color: $text-color;
     color: $text-color;
-    @include panel-corner-color($errorBackground);
+    @include panel-corner-color($popover-error-bg);
     .fa:before {
     .fa:before {
       content: "\f12a";
       content: "\f12a";
     }
     }

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

@@ -167,6 +167,20 @@ define([
       var res = kbn.calculateInterval(range, 900, '>15ms');
       var res = kbn.calculateInterval(range, 900, '>15ms');
       expect(res.interval).to.be('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() {
   describe('hex', function() {

+ 6 - 3
public/views/index.html

@@ -31,11 +31,14 @@
 
 
 			<div class="page-alert-list">
 			<div class="page-alert-list">
 				<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
 				<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)">
 					<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>
 					</button>
-					<div class="alert-title">{{alert.title}}</div>
-					<div ng-bind='alert.text'></div>
 				</div>
 				</div>
 			</div>
 			</div>