Merge branch 'master' into alerting_mqe

This commit is contained in:
bergquist 2016-11-17 09:57:56 +01:00
commit 5c153f0383
42 changed files with 454 additions and 1182 deletions

View File

@ -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**: 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) * **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) * **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) # 4.0-beta1 (2016-11-09)

View File

@ -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 [external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini
config file. 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.

View File

@ -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 `<metric-query-editor-graphite />`. that is how angular directives needs to be named in order to match an element with name `<metric-query-editor-graphite />`.
You also specify the query controller here instead of in the query.editor.html partial like before. 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.

View File

@ -141,6 +141,18 @@ those options.
- [OpenTSDB]({{< relref "datasources/opentsdb.md" >}}) - [OpenTSDB]({{< relref "datasources/opentsdb.md" >}})
- [Prometheus]({{< relref "datasources/prometheus.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 ## Installing from binary tar file

View File

@ -307,4 +307,5 @@ func Register(r *macaron.Macaron) {
InitAppPluginRoutes(r) InitAppPluginRoutes(r)
r.NotFound(NotFoundHandler)
} }

View File

@ -96,7 +96,7 @@ func OAuthLogin(ctx *middleware.Context) {
} }
sslcli := &http.Client{Transport: tr} sslcli := &http.Client{Transport: tr}
oauthCtx = context.TODO() oauthCtx = context.Background()
oauthCtx = context.WithValue(oauthCtx, oauth2.HTTPClient, sslcli) oauthCtx = context.WithValue(oauthCtx, oauth2.HTTPClient, sslcli)
} }
@ -106,6 +106,8 @@ func OAuthLogin(ctx *middleware.Context) {
ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err) ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
return 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") ctx.Logger.Debug("OAuthLogin Got token")

View File

@ -187,6 +187,7 @@ func (ctx *Context) Handle(status int, title string, err error) {
} }
ctx.Data["Title"] = title ctx.Data["Title"] = title
ctx.Data["AppSubUrl"] = setting.AppSubUrl
ctx.HTML(status, strconv.Itoa(status)) ctx.HTML(status, strconv.Itoa(status))
} }

View File

@ -19,53 +19,14 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"runtime" "runtime"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
"github.com/go-macaron/inject"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
const (
panicHtml = `<html>
<head><title>PANIC: %s</title>
<meta charset="utf-8" />
<style type="text/css">
html, body {
font-family: "Roboto", sans-serif;
color: #333333;
background-color: #ea5343;
margin: 0px;
}
h1 {
color: #d04526;
background-color: #ffffff;
padding: 20px;
border-bottom: 1px dashed #2b3848;
}
pre {
margin: 20px;
padding: 20px;
border: 2px solid #2b3848;
background-color: #ffffff;
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
</style>
</head><body>
<h1>PANIC</h1>
<pre style="font-weight: bold;">%s</pre>
<pre>%s</pre>
</body>
</html>`
)
var ( var (
dunno = []byte("???") dunno = []byte("???")
centerDot = []byte("·") centerDot = []byte("·")
@ -151,21 +112,34 @@ func Recovery() macaron.Handler {
panicLogger.Error("Request error", "error", err, "stack", string(stack)) panicLogger.Error("Request error", "error", err, "stack", string(stack))
// Lookup the current responsewriter c.Data["Title"] = "Server Error"
val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) c.Data["AppSubUrl"] = setting.AppSubUrl
res := val.Interface().(http.ResponseWriter)
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 { if setting.Env == setting.DEV {
res.Header().Set("Content-Type", "text/html") c.Data["ErrorMsg"] = string(stack)
body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
} }
res.WriteHeader(http.StatusInternalServerError) c.HTML(500, "500")
if nil != body {
res.Write(body) // // 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)
// }
} }
}() }()

View File

@ -40,7 +40,6 @@ export class GrafanaApp {
init() { init() {
var app = angular.module('grafana', []); var app = angular.module('grafana', []);
app.constant('grafanaVersion', "@grafanaVersion@");
moment.locale(config.bootData.user.locale); moment.locale(config.bootData.user.locale);

View File

@ -122,7 +122,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
// handle in active view state class // handle in active view state class
var lastActivity = new Date().getTime(); var lastActivity = new Date().getTime();
var activeUser = true; var activeUser = true;
var inActiveTimeLimit = 60 * 1000; var inActiveTimeLimit = 10 * 1000;
function checkForInActiveUser() { function checkForInActiveUser() {
if (!activeUser) { if (!activeUser) {
@ -147,9 +147,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) {
} }
} }
// mouse and keyboard is user activity
body.mousemove(userActivityDetected); body.mousemove(userActivityDetected);
body.keydown(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', () => { appEvents.on('toggle-view-mode', () => {
lastActivity = 0; lastActivity = 0;

View File

@ -6,7 +6,6 @@ import "./directives/dash_class";
import "./directives/confirm_click"; import "./directives/confirm_click";
import "./directives/dash_edit_link"; import "./directives/dash_edit_link";
import "./directives/dropdown_typeahead"; import "./directives/dropdown_typeahead";
import "./directives/grafana_version_check";
import "./directives/metric_segment"; import "./directives/metric_segment";
import "./directives/misc"; import "./directives/misc";
import "./directives/ng_model_on_blur"; import "./directives/ng_model_on_blur";

View File

@ -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('<i class="icon-info-sign"></i> ' +
'<a href="http://grafana.org/download" target="_blank"> ' +
'New version available: ' + response.data.version +
'</a>');
}
});
}
};
});
});

View File

@ -420,11 +420,11 @@ function($, _, moment) {
kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps'); kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps');
kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps'); kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps');
kbn.valueFormats.KBs = kbn.formatBuilders.decimalSIPrefix('Bs', 1); 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.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.GBs = kbn.formatBuilders.decimalSIPrefix('Bs', 3);
kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bits', 3); kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bps', 3);
// Throughput // Throughput
kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops'); kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops');

View File

@ -64,6 +64,8 @@ export class DynamicDashboardSrv {
j = j - 1; j = j - 1;
} }
} }
row.panelSpanChanged();
} }
} }

View File

@ -233,7 +233,6 @@ export class DashboardModel {
} }
duplicatePanel(panel, row) { duplicatePanel(panel, row) {
var rowIndex = _.indexOf(this.rows, row);
var newPanel = angular.copy(panel); var newPanel = angular.copy(panel);
newPanel.id = this.getNextPanelId(); newPanel.id = this.getNextPanelId();
@ -241,9 +240,9 @@ export class DashboardModel {
delete newPanel.repeatIteration; delete newPanel.repeatIteration;
delete newPanel.repeatPanelId; delete newPanel.repeatPanelId;
delete newPanel.scopedVars; delete newPanel.scopedVars;
delete newPanel.alert;
var currentRow = this.rows[rowIndex]; row.addPanel(newPanel);
currentRow.panels.push(newPanel);
return newPanel; return newPanel;
} }

View File

@ -1,282 +0,0 @@
<topnav title="Alerting" subnav="false">
<ul class="nav">
<li class="active" ><a href="global-alerts">Global Alerts</a></li>
</ul>
</topnav>
<div class="page-container">
<div class="page-wide">
<h1>Global alerts</h1>
<div class="filter-controls-filters">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item">Filters:</li>
<li class="tight-form-item">Alert State</li>
<li><!-- <value-select-dropdown></value-select-dropdown> --></li>
<li class="tight-form-item">Dashboards</li>
<li><!-- <value-select-dropdown></value-select-dropdown> --></li>
<li class="tight-form-item">
<a class="pointer">
<i class="fa fa-pencil"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<ul class="filter-controls-actions">
<li>
<div class="dropdown">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
<input class="cr1" id="state-enabled" type="checkbox">
<label for="state-enabled" class="cr1"></label> <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a>All</a></li>
</ul>
</div>
</li>
<li>
<div class="dropdown">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
Bulk Actions &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a>Update notifications</a></li>
</ul>
</div>
</li>
<li>
<button class="btn btn-inverse" data-toggle="dropdown">
<i class="fa fa-fw fa-th-large"></i> New Dashboard from selected
</button>
</li>
<li>
<span class="filter-controls-actions-selected">2 selected, showing 6 of 6 total</span>
</li>
</ul>
<ul class="filter-list">
<li>
<ul class="filter-list-card">
<li class="filter-list-card-select">
<input class="cr1" id="alert1" type="checkbox">
<label for="alert1" class="cr1"></label>
</li>
<li>
<div class="filter-list-card-controls">
<div class="filter-list-card-links">
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Super Sekret</a></span>
<span class="filter-list-card-link">Panel: <a href="">Prod CPU Data Writes</a></span>
</div>
<div class="filter-list-card-config">
<a href="#"><i class="fa fa-cog"></i></a>
</div>
<div class="filter-list-card-expand" ng-click="alert1.expanded = !alert1.expanded">
<i class="fa fa-angle-right" ng-show="!alert1.expanded"></i>
<i class="fa fa-angle-down" ng-show="alert1.expanded"></i>
</div>
</div>
<span class="filter-list-card-title">Prod CPU Data Writes</span>
<span class="filter-list-card-status">
<span class="filter-list-card-state online">Online</span> for 19 hours
</span>
</li>
</ul>
<div class="filter-list-card-details" ng-show="alert1.expanded">
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
<li class="tight-form-item">apps</li>
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
<li class="tight-form-item">fakesite</li>
<li class="tight-form-item">counters</li>
<li class="tight-form-item">requests</li>
<li class="tight-form-item">count</li>
<li class="tight-form-item">scaleToSeconds(1)</li>
<li class="tight-form-item">aliasByNode(2)</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</li>
<li>
<ul class="filter-list-card">
<li class="filter-list-card-select">
<input class="cr1" id="alert2" type="checkbox" checked>
<label for="alert2" class="cr1"></label>
</li>
<li>
<div class="filter-list-card-controls">
<div class="filter-list-card-links">
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Insanely Super Duper Sekret</a></span>
<span class="filter-list-card-link">Panel: <a href="">client side full page load</a></span>
</div>
<div class="filter-list-card-config">
<a href="#"><i class="fa fa-cog"></i></a>
</div>
<div class="filter-list-card-expand" ng-click="alert2.expanded = !alert2.expanded">
<i class="fa fa-angle-right" ng-show="!alert2.expanded"></i>
<i class="fa fa-angle-down" ng-show="alert2.expanded"></i>
</div>
</div>
<span class="filter-list-card-title">Prod DB Reads</span>
<span class="filter-list-card-status">
<span class="filter-list-card-state warn">Warn</span> for 1 hour
</span>
</li>
</ul>
<div class="filter-list-card-details" ng-show="alert2.expanded">
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
<li class="tight-form-item">apps</li>
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
<li class="tight-form-item">fakesite</li>
<li class="tight-form-item">counters</li>
<li class="tight-form-item">requests</li>
<li class="tight-form-item">count</li>
<li class="tight-form-item">scaleToSeconds(1)</li>
<li class="tight-form-item">aliasByNode(2)</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</li>
<li>
<ul class="filter-list-card">
<li class="filter-list-card-select">
<input class="cr1" id="alert3" type="checkbox" checked>
<label for="alert3" class="cr1"></label>
</li>
<li>
<div class="filter-list-card-controls">
<div class="filter-list-card-links">
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Mildly Sekret</a></span>
<span class="filter-list-card-link">Panel: <a href="">Memory/CPU</a></span>
</div>
<div class="filter-list-card-config">
<a href="#"><i class="fa fa-cog"></i></a>
</div>
<div class="filter-list-card-expand" ng-click="alert3.expanded = !alert3.expanded">
<i class="fa fa-angle-right" ng-show="!alert3.expanded"></i>
<i class="fa fa-angle-down" ng-show="alert3.expanded"></i>
</div>
</div>
<span class="filter-list-card-title">Prod CPU Data Writes</span>
<span class="filter-list-card-status">
<span class="filter-list-card-state critical">Online</span> for 10 minutes
</span>
</li>
</ul>
<div class="filter-list-card-details" ng-show="alert3.expanded">
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
<li class="tight-form-item">apps</li>
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
<li class="tight-form-item">fakesite</li>
<li class="tight-form-item">counters</li>
<li class="tight-form-item">requests</li>
<li class="tight-form-item">count</li>
<li class="tight-form-item">scaleToSeconds(1)</li>
<li class="tight-form-item">aliasByNode(2)</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</li>
<li>
<ul class="filter-list-card">
<li class="filter-list-card-select">
<input class="cr1" id="alert4" type="checkbox">
<label for="alert4" class="cr1"></label>
</li>
<li>
<div class="filter-list-card-controls">
<div class="filter-list-card-links">
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Super Sekret</a></span>
<span class="filter-list-card-link">Panel: <a href="">Stacked lines</a></span>
</div>
<div class="filter-list-card-config">
<a href="#"><i class="fa fa-cog"></i></a>
</div>
<div class="filter-list-card-expand" ng-click="alert4.expanded = !alert4.expanded">
<i class="fa fa-angle-right" ng-show="!alert4.expanded"></i>
<i class="fa fa-angle-down" ng-show="alert4.expanded"></i>
</div>
</div>
<span class="filter-list-card-title">Critical Thing</span>
<span class="filter-list-card-status">
<span class="filter-list-card-state online">Online</span> for 5 weeks
</span>
</li>
</ul>
<div class="filter-list-card-details" ng-show="alert4.expanded">
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
<li class="tight-form-item">apps</li>
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
<li class="tight-form-item">fakesite</li>
<li class="tight-form-item">counters</li>
<li class="tight-form-item">requests</li>
<li class="tight-form-item">count</li>
<li class="tight-form-item">scaleToSeconds(1)</li>
<li class="tight-form-item">aliasByNode(2)</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</li>
<li>
<ul class="filter-list-card">
<li class="filter-list-card-select">
<input class="cr1" id="alert5" type="checkbox">
<label for="alert5" class="cr1"></label>
</li>
<li>
<div class="filter-list-card-controls">
<div class="filter-list-card-links">
<span class="filter-list-card-link"><i class="fa fa-fw fa-th-large"></i>: <a href="">OpSec Public</a></span>
<span class="filter-list-card-link">Panel: <a href="">More Critical Thing</a></span>
</div>
<div class="filter-list-card-config">
<a href="#"><i class="fa fa-cog"></i></a>
</div>
<div class="filter-list-card-expand" ng-click="alert5.expanded = !alert5.expanded">
<i class="fa fa-angle-right" ng-show="!alert5.expanded"></i>
<i class="fa fa-angle-down" ng-show="alert5.expanded"></i>
</div>
</div>
<span class="filter-list-card-title">More Critical Thing</span>
<span class="filter-list-card-status">
<span class="filter-list-card-state online">Online</span> for 2 months
</span>
</li>
</ul>
<div class="filter-list-card-details" ng-show="alert5.expanded">
<h5 class="filter-list-card-details-heading">Alert query <a>configure alerting</a></h5>
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">A</li>
<li class="tight-form-item">apps</li>
<li class="tight-form-item"><i class="fa fa-asterisk"><i></i></i></li>
<li class="tight-form-item">fakesite</li>
<li class="tight-form-item">counters</li>
<li class="tight-form-item">requests</li>
<li class="tight-form-item">count</li>
<li class="tight-form-item">scaleToSeconds(1)</li>
<li class="tight-form-item">aliasByNode(2)</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</li>
</ul>
</div>
</div>

View File

@ -62,7 +62,9 @@ describe('dashboardSrv', function() {
it('duplicate panel should try to add it to same row', function() { it('duplicate panel should try to add it to same row', function() {
var panel = { span: 4, attr: '123', id: 10 }; var panel = { span: 4, attr: '123', id: 10 };
dashboard.rows = [{ panels: [panel] }];
dashboard.addEmptyRow();
dashboard.rows[0].addPanel(panel);
dashboard.duplicatePanel(panel, dashboard.rows[0]); dashboard.duplicatePanel(panel, dashboard.rows[0]);
expect(dashboard.rows[0].panels[0].span).to.be(4); expect(dashboard.rows[0].panels[0].span).to.be(4);
@ -73,7 +75,9 @@ describe('dashboardSrv', function() {
it('duplicate panel should remove repeat data', function() { it('duplicate panel should remove repeat data', function() {
var panel = { span: 4, attr: '123', id: 10, repeat: 'asd', scopedVars: { test: 'asd' }}; var panel = { span: 4, attr: '123', id: 10, repeat: 'asd', scopedVars: { test: 'asd' }};
dashboard.rows = [{ panels: [panel] }];
dashboard.addEmptyRow();
dashboard.rows[0].addPanel(panel);
dashboard.duplicatePanel(panel, dashboard.rows[0]); dashboard.duplicatePanel(panel, dashboard.rows[0]);
expect(dashboard.rows[0].panels[1].repeat).to.be(undefined); expect(dashboard.rows[0].panels[1].repeat).to.be(undefined);

View File

@ -188,6 +188,9 @@ export class PanelCtrl {
duplicate() { duplicate() {
this.dashboard.duplicatePanel(this.panel, this.row); this.dashboard.duplicatePanel(this.panel, this.row);
this.$timeout(() => {
this.$scope.$root.$broadcast('render');
});
} }
updateColumnSpan(span) { updateColumnSpan(span) {

View File

@ -68,8 +68,8 @@ module.directive('grafanaPanel', function($rootScope) {
// the reason for handling these classes this way is for performance // the reason for handling these classes this way is for performance
// limit the watchers on panels etc // limit the watchers on panels etc
var transparentLastState; var transparentLastState = false;
var lastHasAlertRule; var lastHasAlertRule = false;
var lastAlertState; var lastAlertState;
var hasAlertRule; var hasAlertRule;
var lastHeight = 0; var lastHeight = 0;
@ -91,6 +91,12 @@ module.directive('grafanaPanel', function($rootScope) {
lastHeight = ctrl.containerHeight; lastHeight = ctrl.containerHeight;
} }
// set initial transparency
if (ctrl.panel.transparent) {
transparentLastState = true;
panelContainer.addClass('panel-transparent', true);
}
ctrl.events.on('render', () => { ctrl.events.on('render', () => {
if (lastHeight !== ctrl.containerHeight) { if (lastHeight !== ctrl.containerHeight) {
panelContainer.css({minHeight: ctrl.containerHeight}); panelContainer.css({minHeight: ctrl.containerHeight});

View File

@ -57,59 +57,3 @@
</div> </div>
</div> </div>
<div class="tight-form" ng-if="false">
<ul class="tight-form-list pull-right">
<li ng-show="ctrl.error" class="tight-form-item">
<a bs-tooltip="ctrl.error" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="fa fa-warning"></i>
</a>
</li>
<li class="tight-form-item small" ng-show="ctrl.target.datasource">
<em>{{ctrl.target.datasource}}</em>
</li>
<li class="tight-form-item" ng-if="ctrl.toggleEditorMode">
<a class="pointer" tabindex="1" ng-click="ctrl.toggleEditorMode()">
<i class="fa fa-pencil"></i>
</a>
</li>
<li class="tight-form-item">
<div class="dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(target)">
<i class="fa fa-trash"></i>
</a>
</li>
</ul>
<ul class="tight-form-list">
<li class="tight-form-item" style="min-width: 15px; text-align: center">
{{ctrl.target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="ctrl.toggleHideQuery()" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<ul class="tight-form-list" ng-transclude>
</ul>
<div class="clearfix"></div>
</div>

View File

@ -1,3 +0,0 @@
<li ng-class="{active: active, disabled: disabled}">
<a href ng-click="select()" tab-heading-transclude>{{heading}}</a>
</li>

View File

@ -1,11 +0,0 @@
<div>
<ul class="nav nav-tabs" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude>
</ul>
<div class="tab-content">
<div class="tab-pane"
ng-repeat="tab in tabs"
ng-class="{active: tab.active}"
tab-content-transclude="tab">
</div>
</div>
</div>

View File

@ -1,11 +1,12 @@
<navbar title="404" icon="fa fa-fw fa-question" title-url="/">
</navbar>
<div class="row-fluid" style="margin-top: 100px;"> <div class="page-container">
<div class="span2"></div>
<div class="grafana-info-box span8 text-center"> <div class="page-header">
<h3>Page not found (404)</h3> <h1>
</div> Page not found (404)
</h1>
<div class="span2"></div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
{ {
"revision": 5, "revision": 6,
"title": "TestData - Graph Panel Last 1h", "title": "TestData - Graph Panel Last 1h",
"tags": [ "tags": [
"grafana-test" "grafana-test"
@ -7,8 +7,48 @@
"style": "dark", "style": "dark",
"timezone": "browser", "timezone": "browser",
"editable": true, "editable": true,
"hideControls": false,
"sharedCrosshair": false, "sharedCrosshair": false,
"hideControls": false,
"time": {
"from": "2016-11-16T16:59:38.294Z",
"to": "2016-11-16T17:09:01.532Z"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": false,
"schemaVersion": 13,
"version": 4,
"links": [],
"gnetId": null,
"rows": [ "rows": [
{ {
"collapse": false, "collapse": false,
@ -238,7 +278,13 @@
] ]
} }
], ],
"title": "New row" "title": "New row",
"showTitle": false,
"titleSize": "h6",
"isNew": false,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null
}, },
{ {
"collapse": false, "collapse": false,
@ -332,7 +378,13 @@
"type": "text" "type": "text"
} }
], ],
"title": "New row" "title": "New row",
"showTitle": false,
"titleSize": "h6",
"isNew": false,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null
}, },
{ {
"collapse": false, "collapse": false,
@ -371,7 +423,7 @@
"yaxis": 2 "yaxis": 2
} }
], ],
"span": 7.99561403508772, "span": 8,
"stack": false, "stack": false,
"steppedLine": false, "steppedLine": false,
"targets": [ "targets": [
@ -432,12 +484,18 @@
"isNew": true, "isNew": true,
"links": [], "links": [],
"mode": "markdown", "mode": "markdown",
"span": 4.00438596491228, "span": 4,
"title": "", "title": "",
"type": "text" "type": "text"
} }
], ],
"title": "New row" "title": "New row",
"showTitle": false,
"titleSize": "h6",
"isNew": false,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null
}, },
{ {
"collapse": false, "collapse": false,
@ -545,7 +603,7 @@
"points": false, "points": false,
"renderer": "flot", "renderer": "flot",
"seriesOverrides": [], "seriesOverrides": [],
"span": 3, "span": 4,
"stack": false, "stack": false,
"steppedLine": false, "steppedLine": false,
"targets": [ "targets": [
@ -592,6 +650,31 @@
} }
] ]
}, },
{
"content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ",
"editable": true,
"error": false,
"id": 13,
"isNew": true,
"links": [],
"mode": "markdown",
"span": 4,
"title": "",
"type": "text"
}
],
"title": "New row",
"showTitle": false,
"titleSize": "h6",
"isNew": false,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null
},
{
"isNew": false,
"title": "Dashboard Row",
"panels": [
{ {
"aliasColors": {}, "aliasColors": {},
"bars": false, "bars": false,
@ -624,7 +707,7 @@
"zindex": -3 "zindex": -3
} }
], ],
"span": 5, "span": 8,
"stack": true, "stack": true,
"steppedLine": false, "steppedLine": false,
"targets": [ "targets": [
@ -687,49 +770,149 @@
"show": true "show": true
} }
] ]
},
{
"content": "Stacking values on top of nulls, should treat the null values as zero. ",
"editable": true,
"error": false,
"id": 14,
"isNew": true,
"links": [],
"mode": "markdown",
"span": 4,
"title": "",
"type": "text"
} }
], ],
"title": "New row" "showTitle": false,
"titleSize": "h6",
"height": 250,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null,
"collapse": false
},
{
"isNew": false,
"title": "Dashboard Row",
"panels": [
{
"aliasColors": {},
"bars": false,
"datasource": "Grafana TestData",
"editable": true,
"error": false,
"fill": 1,
"id": 12,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"alias": "B-series",
"zindex": -3
}
],
"span": 8,
"stack": true,
"steppedLine": false,
"targets": [
{
"hide": false,
"refId": "B",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
"target": "",
"alias": ""
},
{
"alias": "",
"hide": false,
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
"target": ""
},
{
"alias": "",
"hide": false,
"refId": "C",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
"target": ""
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Stacking all series null segment",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"content": "Stacking when all values are null should leave a gap in the graph",
"editable": true,
"error": false,
"id": 15,
"isNew": true,
"links": [],
"mode": "markdown",
"span": 4,
"title": "",
"type": "text"
}
],
"showTitle": false,
"titleSize": "h6",
"height": 250,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null,
"collapse": false
} }
], ]
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"refresh": false,
"schemaVersion": 13,
"version": 13,
"links": [],
"gnetId": null
} }

View File

@ -9,7 +9,7 @@
"name": "Grafana Project", "name": "Grafana Project",
"url": "http://grafana.org" "url": "http://grafana.org"
}, },
"version": "1.0.14", "version": "1.0.15",
"updated": "2016-09-26" "updated": "2016-09-26"
}, },

View File

@ -37,7 +37,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars); query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
query.statistics = target.statistics; query.statistics = target.statistics;
var period = this._getPeriod(target, query, options, start, end); var now = Math.round(Date.now() / 1000);
var period = this._getPeriod(target, query, options, start, end, now);
target.period = period; target.period = period;
query.period = period; query.period = period;
@ -67,11 +68,19 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
}); });
}; };
this._getPeriod = function(target, query, options, start, end) { this._getPeriod = function(target, query, options, start, end, now) {
var period; var period;
var range = end - start; var range = end - start;
if (!target.period) { var daySec = 60 * 60 * 24;
var periodUnit = 60;
if (now - start > (daySec * 15)) { // until 63 days ago
periodUnit = period = 60 * 5;
} else if (now - start > (daySec * 63)) { // until 455 days ago
periodUnit = period = 60 * 60;
} else if (now - start > (daySec * 455)) { // over 455 days, should return error, but try to long period
periodUnit = period = 60 * 60;
} else if (!target.period) {
period = (query.namespace === 'AWS/EC2') ? 300 : 60; period = (query.namespace === 'AWS/EC2') ? 300 : 60;
} else if (/^\d+$/.test(target.period)) { } else if (/^\d+$/.test(target.period)) {
period = parseInt(target.period, 10); period = parseInt(target.period, 10);
@ -82,7 +91,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
period = 60; period = 60;
} }
if (range / period >= 1440) { if (range / period >= 1440) {
period = Math.ceil(range / 1440 / 60) * 60; period = Math.ceil(range / 1440 / periodUnit) * periodUnit;
} }
return period; return period;

View File

@ -208,11 +208,8 @@ class SingleStatCtrl extends MetricsPanelCtrl {
} }
// Add $__name variable for using in prefix or postfix // Add $__name variable for using in prefix or postfix
data.scopedVars = { data.scopedVars = _.extend({}, this.panel.scopedVars);
__name: { data.scopedVars["__name"] = {value: this.series[0].label};
value: this.series[0].label
}
};
} }
// check value to text mappings if its enabled // check value to text mappings if its enabled
@ -526,7 +523,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
elem.toggleClass('pointer', panel.links.length > 0); elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) { if (panel.links.length > 0) {
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], panel.scopedVars); linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], data.scopedVars);
} else { } else {
linkInfo = null; linkInfo = null;
} }

View File

@ -50,11 +50,9 @@
@import "components/tagsinput"; @import "components/tagsinput";
@import "components/tables_lists"; @import "components/tables_lists";
@import "components/search"; @import "components/search";
@import "components/tightform";
@import "components/gf-form"; @import "components/gf-form";
@import "components/sidemenu"; @import "components/sidemenu";
@import "components/navbar"; @import "components/navbar";
@import "components/gfbox";
@import "components/timepicker"; @import "components/timepicker";
@import "components/filter-controls"; @import "components/filter-controls";
@import "components/filter-list"; @import "components/filter-list";

View File

@ -1,69 +0,0 @@
.gf-box {
margin: 10px 5px;
background-color: $page-bg;
position: relative;
border: 1px solid $tight-form-func-bg;
}
.gf-box-no-margin {
margin: 0;
}
.gf-box-header-close-btn {
float: right;
padding: 0;
margin: 0;
background-color: transparent;
border: none;
padding: 8px;
i {
font-size: 120%;
}
color: $text-color;
&:hover {
color: $white;
}
}
.gf-box-header-save-btn {
padding: 7px 0;
float: right;
color: $gray-2;
font-style: italic;
}
.gf-box-body {
padding: 20px;
min-height: 150px;
}
.gf-box-footer {
overflow: hidden;
}
.gf-box-header {
border-bottom: 1px solid $tight-form-func-bg;
overflow: hidden;
background-color: $tight-form-bg;
.tabs {
float: left;
}
.nav {
margin: 0;
}
}
.gf-box-title {
padding-right: 20px;
padding-left: 10px;
float: left;
color: $link-color;
font-size: 18px;
font-weight: normal;
line-height: 38px;
margin: 0;
.fa {
padding: 0 8px 0 5px;
color: $text-color;
}
}

View File

@ -87,7 +87,7 @@
} }
// temp hack // temp hack
.modal-body, .gf-box { .modal-body {
.nav-tabs { .nav-tabs {
border-bottom: none; border-bottom: none;
} }

View File

@ -67,3 +67,82 @@
} }
} }
.grafana-metric-options {
margin-top: 25px;
}
.tight-form-func {
background: $tight-form-func-bg;
&.show-function-controls {
padding-top: 5px;
min-width: 100px;
text-align: center;
}
}
input[type="text"].tight-form-func-param {
background: transparent;
border: none;
margin: 0;
padding: 0;
}
.tight-form-func-controls {
display: none;
text-align: center;
.fa-arrow-left {
float: left;
position: relative;
top: 2px;
}
.fa-arrow-right {
float: right;
position: relative;
top: 2px;
}
.fa-remove {
margin-left: 10px;
}
}
.grafana-metric-options {
margin-top: 25px;
}
.tight-form-func {
background: $tight-form-func-bg;
&.show-function-controls {
padding-top: 5px;
min-width: 100px;
text-align: center;
}
}
input[type="text"].tight-form-func-param {
background: transparent;
border: none;
margin: 0;
padding: 0;
}
.tight-form-func-controls {
display: none;
text-align: center;
.fa-arrow-left {
float: left;
position: relative;
top: 2px;
}
.fa-arrow-right {
float: right;
position: relative;
top: 2px;
}
.fa-remove {
margin-left: 10px;
}
}

View File

@ -74,12 +74,11 @@
.add-panel-panels-scroll { .add-panel-panels-scroll {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
-ms-overflow-style: none;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none display: none
} }
-ms-overflow-style: none;
} }
.add-panel-panels { .add-panel-panels {

View File

@ -6,6 +6,8 @@
} }
.shortcut-table { .shortcut-table {
margin-bottom: $spacer;
.shortcut-table-category-header { .shortcut-table-category-header {
font-weight: normal; font-weight: normal;
font-size: $font-size-h6; font-size: $font-size-h6;
@ -26,8 +28,6 @@
text-align: right; text-align: right;
color: $text-color; color: $text-color;
} }
margin-bottom: $spacer;
} }
.shortcut-table-key { .shortcut-table-key {

View File

@ -7,11 +7,12 @@
} }
.annotation-segment { .annotation-segment {
padding: 8px 7px;
label.cr1 { label.cr1 {
margin-left: 5px; margin-left: 5px;
margin-top: 3px; margin-top: 3px;
} }
padding: 8px 7px;
} }
.submenu-item { .submenu-item {
@ -31,14 +32,14 @@
.variable-value-link { .variable-value-link {
padding-right: 10px; padding-right: 10px;
.label-tag {
margin: 0 5px;
}
padding: 8px 7px; padding: 8px 7px;
box-sizing: content-box; box-sizing: content-box;
display: inline-block; display: inline-block;
color: $text-color; color: $text-color;
.label-tag {
margin: 0 5px;
}
} }
.variable-link-wrapper { .variable-link-wrapper {

View File

@ -38,10 +38,10 @@
background-color: transparent; background-color: transparent;
border: none; border: none;
padding: ($tabs-padding-top + $tabs-top-margin) $spacer $tabs-padding-bottom; padding: ($tabs-padding-top + $tabs-top-margin) $spacer $tabs-padding-bottom;
color: $text-color;
i { i {
font-size: 120%; font-size: 120%;
} }
color: $text-color;
&:hover { &:hover {
color: $white; color: $white;
} }

View File

@ -1,235 +0,0 @@
.tight-form {
border-top: 1px solid $tight-form-border;
border-left: 1px solid $tight-form-border;
border-right: 1px solid $tight-form-border;
background: $tight-form-bg;
&.last {
border-bottom: 1px solid $tight-form-border;
}
&.borderless {
background: transparent;
border: none;
}
.checkbox-label {
display: inline;
padding-right: 4px;
margin-bottom: 0;
cursor: pointer;
}
}
.tight-form-container-no-item-borders {
border: 1px solid $tight-form-border;
border-bottom: none;
.tight-form, .tight-form-item, [type="text"].tight-form-input, [type="text"].tight-form-clear-input {
border: none;
}
}
.spaced-form {
.tight-form {
margin: 7px 0;
}
}
.borderless {
.tight-form-item,
.tight-form-input {
border: none;
}
}
.tight-form-container {
border-bottom: 1px solid $tight-form-border;
}
.tight-form-btn {
padding: 7px 12px;
}
.tight-form-list {
list-style: none;
margin: 0;
>li {
float: left;
}
}
.tight-form-flex-wrapper {
display: flex;
flex-direction: row;
float: none !important;
}
.grafana-metric-options {
margin-top: 25px;
}
.tight-form-item {
padding: 8px 7px;
box-sizing: content-box;
display: inline-block;
font-weight: normal;
border-right: 1px solid $tight-form-border;
display: inline-block;
color: $text-color;
.has-open-function & {
padding-top: 25px;
}
.tight-form-disabled & {
color: $link-color-disabled;
a {
color: $link-color-disabled;
}
}
&:hover, &:focus {
text-decoration: none;
}
&a:hover {
background: $tight-form-func-bg;
}
&.last {
border-right: none;
}
}
.tight-form-item-icon {
i {
width: 15px;
text-align: center;
display: inline-block;
}
}
.tight-form-func {
background: $tight-form-func-bg;
&.show-function-controls {
padding-top: 5px;
min-width: 100px;
text-align: center;
}
}
input[type="text"].tight-form-func-param {
background: transparent;
border: none;
margin: 0;
padding: 0;
}
input[type="text"].tight-form-clear-input {
padding: 8px 7px;
border: none;
margin: 0px;
background: transparent;
border-radius: 0;
border-right: 1px solid $tight-form-border;
}
[type="text"],
[type="email"],
[type="number"],
[type="password"] {
&.tight-form-input {
background-color: $input-bg;
border: none;
border-right: 1px solid $tight-form-border;
margin: 0px;
border-radius: 0;
padding: 8px 6px;
height: 100%;
box-sizing: border-box;
&.last {
border-right: none;
}
}
}
input[type="checkbox"].tight-form-checkbox {
margin: 0;
}
.tight-form-textarea {
height: 200px;
margin: 0;
box-sizing: border-box;
}
select.tight-form-input {
border: none;
border-right: 1px solid $tight-form-border;
background-color: $input-bg;
margin: 0px;
border-radius: 0;
height: 36px;
padding: 9px 3px;
&.last {
border-right: none;
}
}
.tight-form-func-controls {
display: none;
text-align: center;
.fa-arrow-left {
float: left;
position: relative;
top: 2px;
}
.fa-arrow-right {
float: right;
position: relative;
top: 2px;
}
.fa-remove {
margin-left: 10px;
}
}
.tight-form-radio {
input[type="radio"] {
margin: 0;
}
label {
display: inline;
}
}
.tight-form-section {
margin-bottom: 20px;
margin-right: 40px;
vertical-align: top;
display: inline-block;
.tight-form {
margin-left: 20px;
}
}
.tight-form-align {
padding-left: 66px;
}
.tight-form-item-large { width: 115px; }
.tight-form-item-xlarge { width: 150px; }
.tight-form-item-xxlarge { width: 200px; }
.tight-form-input.tight-form-item-xxlarge {
width: 215px;
}
.tight-form-inner-box {
margin: 20px 0 20px 148px;
display: inline-block;
}

View File

@ -65,15 +65,17 @@
} }
.gf-timepicker-component { .gf-timepicker-component {
margin-bottom: 10px; padding: $spacer/2 0 $spacer 0;
td { td {
padding: 1px; padding: 1px;
} }
button.btn-sm { button.btn-sm {
@include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl);
font-size: $font-size-sm;
background-image: none; background-image: none;
border: none; border: none;
padding: 6px 10px; padding: 5px 11px;
color: $text-color; color: $text-color;
&.active span { &.active span {
color: $blue; color: $blue;

View File

@ -62,12 +62,6 @@
.admin-page { .admin-page {
max-width: 800px; max-width: 800px;
margin-left: 10px; margin-left: 10px;
.gf-box {
margin-top: 0;
}
.gf-box-body {
min-height: 0;
}
h2 { h2 {
margin-left: 15px; margin-left: 15px;
margin-bottom: 0px; margin-bottom: 0px;

View File

@ -61,7 +61,6 @@
} }
&--ok { &--ok {
box-shadow: 0 0 5px rgba(0,200,0,10.8);
.panel-alert-icon:before { .panel-alert-icon:before {
color: $online; color: $online;
content: "\e611"; content: "\e611";

View File

@ -172,6 +172,12 @@ div.flot-text {
} }
} }
.panel-in-fullscreen {
.panel-drop-zone {
display: none !important;
}
}
.panel-time-info { .panel-time-info {
font-weight: bold; font-weight: bold;
float: right; float: right;

View File

@ -5,8 +5,8 @@
* Version: 0.13.4 - 2015-09-03 * Version: 0.13.4 - 2015-09-03
* License: MIT * License: MIT
*/ */
angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker","ui.bootstrap.tabs"]); angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker"]);
angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/tabs/tab.html","template/tabs/tabset.html"]); angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html"]);
angular.module('ui.bootstrap.position', []) angular.module('ui.bootstrap.position', [])
/** /**
@ -1180,302 +1180,6 @@ function($compile, $parse, $document, $rootScope, $position, dateFilter, datePar
}); });
/**
* @ngdoc overview
* @name ui.bootstrap.tabs
*
* @description
* AngularJS version of the tabs directive.
*/
angular.module('ui.bootstrap.tabs', [])
.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
var ctrl = this,
tabs = ctrl.tabs = $scope.tabs = [];
ctrl.select = function(selectedTab) {
angular.forEach(tabs, function(tab) {
if (tab.active && tab !== selectedTab) {
tab.active = false;
tab.onDeselect();
selectedTab.selectCalled = false;
}
});
selectedTab.active = true;
// only call select if it has not already been called
if (!selectedTab.selectCalled) {
selectedTab.onSelect();
selectedTab.selectCalled = true;
}
};
ctrl.addTab = function addTab(tab) {
tabs.push(tab);
// we can't run the select function on the first tab
// since that would select it twice
if (tabs.length === 1 && tab.active !== false) {
tab.active = true;
} else if (tab.active) {
ctrl.select(tab);
} else {
tab.active = false;
}
};
ctrl.removeTab = function removeTab(tab) {
var index = tabs.indexOf(tab);
//Select a new tab if the tab to be removed is selected and not destroyed
if (tab.active && tabs.length > 1 && !destroyed) {
//If this is the last tab, select the previous tab. else, the next tab.
var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
ctrl.select(tabs[newActiveIndex]);
}
tabs.splice(index, 1);
};
var destroyed;
$scope.$on('$destroy', function() {
destroyed = true;
});
}])
/**
* @ngdoc directive
* @name ui.bootstrap.tabs.directive:tabset
* @restrict EA
*
* @description
* Tabset is the outer container for the tabs directive
*
* @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
* @param {boolean=} justified Whether or not to use justified styling for the tabs.
*
* @example
<example module="ui.bootstrap">
<file name="index.html">
<tabset>
<tab heading="Tab 1"><b>First</b> Content!</tab>
<tab heading="Tab 2"><i>Second</i> Content!</tab>
</tabset>
<hr />
<tabset vertical="true">
<tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
<tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
</tabset>
<tabset justified="true">
<tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
<tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
</tabset>
</file>
</example>
*/
.directive('tabset', function() {
return {
restrict: 'EA',
transclude: true,
replace: true,
scope: {
type: '@'
},
controller: 'TabsetController',
templateUrl: 'template/tabs/tabset.html',
link: function(scope, element, attrs) {
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
}
};
})
/**
* @ngdoc directive
* @name ui.bootstrap.tabs.directive:tab
* @restrict EA
*
* @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
* @param {string=} select An expression to evaluate when the tab is selected.
* @param {boolean=} active A binding, telling whether or not this tab is selected.
* @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
*
* @description
* Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
*
* @example
<example module="ui.bootstrap">
<file name="index.html">
<div ng-controller="TabsDemoCtrl">
<button class="btn btn-small" ng-click="items[0].active = true">
Select item 1, using active binding
</button>
<button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
Enable/disable item 2, using disabled binding
</button>
<br />
<tabset>
<tab heading="Tab 1">First Tab</tab>
<tab select="alertMe()">
<tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
Second Tab, with alert callback and html heading!
</tab>
<tab ng-repeat="item in items"
heading="{{item.title}}"
disabled="item.disabled"
active="item.active">
{{item.content}}
</tab>
</tabset>
</div>
</file>
<file name="script.js">
function TabsDemoCtrl($scope) {
$scope.items = [
{ title:"Dynamic Title 1", content:"Dynamic Item 0" },
{ title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
];
$scope.alertMe = function() {
setTimeout(function() {
alert("You've selected the alert tab!");
});
};
};
</file>
</example>
*/
/**
* @ngdoc directive
* @name ui.bootstrap.tabs.directive:tabHeading
* @restrict EA
*
* @description
* Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
*
* @example
<example module="ui.bootstrap">
<file name="index.html">
<tabset>
<tab>
<tab-heading><b>HTML</b> in my titles?!</tab-heading>
And some content, too!
</tab>
<tab>
<tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
That's right.
</tab>
</tabset>
</file>
</example>
*/
.directive('tab', ['$parse', '$log', function($parse, $log) {
return {
require: '^tabset',
restrict: 'EA',
replace: true,
templateUrl: 'template/tabs/tab.html',
transclude: true,
scope: {
active: '=?',
heading: '@',
onSelect: '&select', //This callback is called in contentHeadingTransclude
//once it inserts the tab's content into the dom
onDeselect: '&deselect'
},
controller: function() {
//Empty controller so other directives can require being 'under' a tab
},
link: function(scope, elm, attrs, tabsetCtrl, transclude) {
scope.$watch('active', function(active) {
if (active) {
tabsetCtrl.select(scope);
}
});
scope.disabled = false;
if (attrs.disable) {
scope.$parent.$watch($parse(attrs.disable), function(value) {
scope.disabled = !! value;
});
}
// Deprecation support of "disabled" parameter
// fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
// This code is duplicated from the lines above to make it easy to remove once
// the feature has been completely deprecated
if (attrs.disabled) {
$log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
scope.$parent.$watch($parse(attrs.disabled), function(value) {
scope.disabled = !! value;
});
}
scope.select = function() {
if (!scope.disabled) {
scope.active = true;
}
};
tabsetCtrl.addTab(scope);
scope.$on('$destroy', function() {
tabsetCtrl.removeTab(scope);
});
//We need to transclude later, once the content container is ready.
//when this link happens, we're inside a tab heading.
scope.$transcludeFn = transclude;
}
};
}])
.directive('tabHeadingTransclude', function() {
return {
restrict: 'A',
require: '^tab',
link: function(scope, elm, attrs, tabCtrl) {
scope.$watch('headingElement', function updateHeadingElement(heading) {
if (heading) {
elm.html('');
elm.append(heading);
}
});
}
};
})
.directive('tabContentTransclude', function() {
return {
restrict: 'A',
require: '^tabset',
link: function(scope, elm, attrs) {
var tab = scope.$eval(attrs.tabContentTransclude);
//Now our tab is ready to be transcluded: both the tab heading area
//and the tab content area are loaded. Transclude 'em both.
tab.$transcludeFn(tab.$parent, function(contents) {
angular.forEach(contents, function(node) {
if (isTabHeading(node)) {
//Let tabHeadingTransclude know.
tab.headingElement = node;
} else {
elm.append(node);
}
});
});
}
};
function isTabHeading(node) {
return node.tagName && (
node.hasAttribute('tab-heading') ||
node.hasAttribute('data-tab-heading') ||
node.hasAttribute('x-tab-heading') ||
node.tagName.toLowerCase() === 'tab-heading' ||
node.tagName.toLowerCase() === 'data-tab-heading' ||
node.tagName.toLowerCase() === 'x-tab-heading'
);
}
});
angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/datepicker/datepicker.html", $templateCache.put("template/datepicker/datepicker.html",
"<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" + "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
@ -1568,25 +1272,3 @@ angular.module("template/datepicker/year.html", []).run(["$templateCache", funct
""); "");
}]); }]);
angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/tabs/tab.html",
"<li ng-class=\"{active: active, disabled: disabled}\">\n" +
" <a href ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
"</li>\n" +
"");
}]);
angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/tabs/tabset.html",
"<div>\n" +
" <ul class=\"nav nav-{{type || 'tabs'}} nav-tabs-alt\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
" <div class=\"tab-content\">\n" +
" <div class=\"tab-pane\" \n" +
" ng-repeat=\"tab in tabs\" \n" +
" ng-class=\"{active: tab.active}\"\n" +
" tab-content-transclude=\"tab\">\n" +
" </div>\n" +
" </div>\n" +
"</div>\n" +
"");
}]);

View File

@ -5,28 +5,32 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<title>Grafana</title> <title>Grafana - Error</title>
<link href='[[.AppSubUrl]]/public/css/fonts.min.css' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="[[.AppSubUrl]]/public/css/grafana.dark.min.css">
<link rel="stylesheet" href="[[.AppSubUrl]]/public/css/grafana.dark.min.css" title="Dark">
<link rel="icon" type="image/png" href="[[.AppSubUrl]]/public/img/fav32.png"> <link rel="icon" type="image/png" href="[[.AppSubUrl]]/public/img/fav32.png">
<base href="[[.AppSubUrl]]/" />
</head> </head>
<body> <body>
<div class="gf-box" style="margin: 200px auto 0 auto; width: 500px;"> <div class="page-container">
<div class="gf-box-header"> <div class="page-header">
<span class="gf-box-title"> <h1>
Server side error :( Server side error :(
</span> </h1>
</div> </div>
<div class="gf-box-body"> <h4>[[.Title]]</h4>
<h4>[[.Title]]</h4>
[[.ErrorMsg]] <pre>[[.ErrorMsg]]</pre>
</div>
</div> </div>
</body>
</body>
</html> </html>