diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4dd0adba4c5..73d7aad8fb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
* **Graph Panel**: Log base scale on right Y-axis had no effect, max value calc was not applied, [#6534](https://github.com/grafana/grafana/issues/6534)
* **Graph Panel**: Bar width if bars was only used in series override, [#6528](https://github.com/grafana/grafana/issues/6528)
* **UI/Browser**: Fixed issue with page/view header gradient border not showing in Safari, [#6530](https://github.com/grafana/grafana/issues/6530)
+* **UX**: Panel Drop zone visible after duplicating panel, and when entering fullscreen/edit view, [#6598](https://github.com/grafana/grafana/issues/6598)
+
+### Enhancements
+* **Singlestat**: Support repeated template variables in prefix/postfix [#6595](https://github.com/grafana/grafana/issues/6595)
# 4.0-beta1 (2016-11-09)
diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md
index ce9dcdf1b93..dfe84f142ec 100644
--- a/docs/sources/alerting/notifications.md
+++ b/docs/sources/alerting/notifications.md
@@ -98,6 +98,6 @@ Amazon S3 for this and Webdav. So to set that up you need to configure the
[external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini
config file.
-This is not an optional requirement, you can get slack and email notifications without setting this up.
+This is an optional requirement, you can get slack and email notifications without setting this up.
diff --git a/docs/sources/datasources/plugin_api.md b/docs/sources/datasources/plugin_api.md
index cdcaca29460..2e2121ed21a 100644
--- a/docs/sources/datasources/plugin_api.md
+++ b/docs/sources/datasources/plugin_api.md
@@ -30,11 +30,5 @@ Even though the data source type name is with lowercase `g`, the directive uses
that is how angular directives needs to be named in order to match an element with name ``.
You also specify the query controller here instead of in the query.editor.html partial like before.
-### query.editor.html
-
-This partial needs to be updated, remove the `np-repeat` this is done in the outer partial now,m the query.editor.html
-should only render a single query. Take a look at the Graphite or InfluxDB partials for `query.editor.html` for reference.
-You should also add a `tight-form-item` with `{{target.refId}}`, all queries needs to be assigned a letter (`refId`).
-These query reference letters are going to be utilized in a later feature.
diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md
index cf33e69aa12..ca71a2355f9 100644
--- a/docs/sources/installation/rpm.md
+++ b/docs/sources/installation/rpm.md
@@ -141,6 +141,18 @@ those options.
- [OpenTSDB]({{< relref "datasources/opentsdb.md" >}})
- [Prometheus]({{< relref "datasources/prometheus.md" >}})
+### Server side image rendering
+
+Server side image (png) rendering is a feature that is optional but very useful when sharing visualizations,
+for example in alert notifications.
+
+If the image is missing text make sure you have font packages installed.
+
+```
+yum install fontconfig
+yum install freetype*
+yum install urw-fonts
+```
## Installing from binary tar file
diff --git a/pkg/api/api.go b/pkg/api/api.go
index ed73f2dc76d..4fa28f799b0 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -307,4 +307,5 @@ func Register(r *macaron.Macaron) {
InitAppPluginRoutes(r)
+ r.NotFound(NotFoundHandler)
}
diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go
index b174f88d649..2e58d9155fc 100644
--- a/pkg/api/login_oauth.go
+++ b/pkg/api/login_oauth.go
@@ -96,7 +96,7 @@ func OAuthLogin(ctx *middleware.Context) {
}
sslcli := &http.Client{Transport: tr}
- oauthCtx = context.TODO()
+ oauthCtx = context.Background()
oauthCtx = context.WithValue(oauthCtx, oauth2.HTTPClient, sslcli)
}
@@ -106,6 +106,8 @@ func OAuthLogin(ctx *middleware.Context) {
ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
return
}
+ // token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
+ token.TokenType = "Bearer"
ctx.Logger.Debug("OAuthLogin Got token")
diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go
index cb3f4480821..a546d7e76fc 100644
--- a/pkg/middleware/middleware.go
+++ b/pkg/middleware/middleware.go
@@ -187,6 +187,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
}
ctx.Data["Title"] = title
+ ctx.Data["AppSubUrl"] = setting.AppSubUrl
ctx.HTML(status, strconv.Itoa(status))
}
diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go
index 8843f2e55d3..b63bc623549 100644
--- a/pkg/middleware/recovery.go
+++ b/pkg/middleware/recovery.go
@@ -19,53 +19,14 @@ import (
"bytes"
"fmt"
"io/ioutil"
- "net/http"
"runtime"
"gopkg.in/macaron.v1"
- "github.com/go-macaron/inject"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
-const (
- panicHtml = `
-
PANIC: %s
-
-
-
-
PANIC
-
%s
-
%s
-
-`
-)
-
var (
dunno = []byte("???")
centerDot = []byte("ยท")
@@ -151,21 +112,34 @@ func Recovery() macaron.Handler {
panicLogger.Error("Request error", "error", err, "stack", string(stack))
- // Lookup the current responsewriter
- val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
- res := val.Interface().(http.ResponseWriter)
+ c.Data["Title"] = "Server Error"
+ c.Data["AppSubUrl"] = setting.AppSubUrl
+
+ if theErr, ok := err.(error); ok {
+ c.Data["Title"] = theErr.Error()
+ }
- // respond with panic message while in development mode
- var body []byte
if setting.Env == setting.DEV {
- res.Header().Set("Content-Type", "text/html")
- body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
+ c.Data["ErrorMsg"] = string(stack)
}
- res.WriteHeader(http.StatusInternalServerError)
- if nil != body {
- res.Write(body)
- }
+ c.HTML(500, "500")
+
+ // // Lookup the current responsewriter
+ // val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
+ // res := val.Interface().(http.ResponseWriter)
+ //
+ // // respond with panic message while in development mode
+ // var body []byte
+ // if setting.Env == setting.DEV {
+ // res.Header().Set("Content-Type", "text/html")
+ // body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
+ // }
+ //
+ // res.WriteHeader(http.StatusInternalServerError)
+ // if nil != body {
+ // res.Write(body)
+ // }
}
}()
diff --git a/public/app/app.ts b/public/app/app.ts
index c004bac4177..22431a5110c 100644
--- a/public/app/app.ts
+++ b/public/app/app.ts
@@ -40,7 +40,6 @@ export class GrafanaApp {
init() {
var app = angular.module('grafana', []);
- app.constant('grafanaVersion', "@grafanaVersion@");
moment.locale(config.bootData.user.locale);
diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts
index ed3425f670f..40797e9a47c 100644
--- a/public/app/core/components/grafana_app.ts
+++ b/public/app/core/components/grafana_app.ts
@@ -122,7 +122,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
// handle in active view state class
var lastActivity = new Date().getTime();
var activeUser = true;
- var inActiveTimeLimit = 60 * 1000;
+ var inActiveTimeLimit = 10 * 1000;
function checkForInActiveUser() {
if (!activeUser) {
@@ -147,9 +147,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
}
}
+ // mouse and keyboard is user activity
body.mousemove(userActivityDetected);
body.keydown(userActivityDetected);
- setInterval(checkForInActiveUser, 1000);
+ // treat tab change as activity
+ document.addEventListener('visibilitychange', userActivityDetected);
+
+ // check every 2 seconds
+ setInterval(checkForInActiveUser, 2000);
appEvents.on('toggle-view-mode', () => {
lastActivity = 0;
diff --git a/public/app/core/core.ts b/public/app/core/core.ts
index 8e9f64fd17f..4aa2e7eb64a 100644
--- a/public/app/core/core.ts
+++ b/public/app/core/core.ts
@@ -6,7 +6,6 @@ import "./directives/dash_class";
import "./directives/confirm_click";
import "./directives/dash_edit_link";
import "./directives/dropdown_typeahead";
-import "./directives/grafana_version_check";
import "./directives/metric_segment";
import "./directives/misc";
import "./directives/ng_model_on_blur";
diff --git a/public/app/core/directives/grafana_version_check.js b/public/app/core/directives/grafana_version_check.js
deleted file mode 100644
index bee437b8183..00000000000
--- a/public/app/core/directives/grafana_version_check.js
+++ /dev/null
@@ -1,31 +0,0 @@
-define([
- '../core_module',
-],
-function (coreModule) {
- 'use strict';
-
- coreModule.default.directive('grafanaVersionCheck', function($http, contextSrv) {
- return {
- restrict: 'A',
- link: function(scope, elem) {
- if (contextSrv.version === 'master') {
- return;
- }
-
- $http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
- .then(function(response) {
- if (!response.data || !response.data.version) {
- return;
- }
-
- if (contextSrv.version !== response.data.version) {
- elem.append(' ' +
- ' ' +
- 'New version available: ' + response.data.version +
- '');
- }
- });
- }
- };
- });
-});
diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js
index a807a249235..78489dcae58 100644
--- a/public/app/core/utils/kbn.js
+++ b/public/app/core/utils/kbn.js
@@ -420,11 +420,11 @@ function($, _, moment) {
kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps');
kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps');
kbn.valueFormats.KBs = kbn.formatBuilders.decimalSIPrefix('Bs', 1);
- kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bits', 1);
+ kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bps', 1);
kbn.valueFormats.MBs = kbn.formatBuilders.decimalSIPrefix('Bs', 2);
- kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bits', 2);
+ kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bps', 2);
kbn.valueFormats.GBs = kbn.formatBuilders.decimalSIPrefix('Bs', 3);
- kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bits', 3);
+ kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bps', 3);
// Throughput
kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops');
diff --git a/public/app/features/dashboard/dynamic_dashboard_srv.ts b/public/app/features/dashboard/dynamic_dashboard_srv.ts
index 9f8fe7f7ab2..e5f1c6a9fa1 100644
--- a/public/app/features/dashboard/dynamic_dashboard_srv.ts
+++ b/public/app/features/dashboard/dynamic_dashboard_srv.ts
@@ -64,6 +64,8 @@ export class DynamicDashboardSrv {
j = j - 1;
}
}
+
+ row.panelSpanChanged();
}
}
diff --git a/public/app/features/dashboard/model.ts b/public/app/features/dashboard/model.ts
index ad28ce4643c..999c0470fdd 100644
--- a/public/app/features/dashboard/model.ts
+++ b/public/app/features/dashboard/model.ts
@@ -233,7 +233,6 @@ export class DashboardModel {
}
duplicatePanel(panel, row) {
- var rowIndex = _.indexOf(this.rows, row);
var newPanel = angular.copy(panel);
newPanel.id = this.getNextPanelId();
@@ -241,9 +240,9 @@ export class DashboardModel {
delete newPanel.repeatIteration;
delete newPanel.repeatPanelId;
delete newPanel.scopedVars;
+ delete newPanel.alert;
- var currentRow = this.rows[rowIndex];
- currentRow.panels.push(newPanel);
+ row.addPanel(newPanel);
return newPanel;
}
diff --git a/public/app/features/dashboard/partials/globalAlerts.html b/public/app/features/dashboard/partials/globalAlerts.html
deleted file mode 100644
index 2c065c714fb..00000000000
--- a/public/app/features/dashboard/partials/globalAlerts.html
+++ /dev/null
@@ -1,282 +0,0 @@
-
-