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**: 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)

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
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 />`.
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" >}})
- [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

View File

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

View File

@ -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")

View File

@ -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))
}

View File

@ -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 = `<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 (
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)
// }
}
}()

View File

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

View File

@ -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;

View File

@ -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";

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.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');

View File

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

View File

@ -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;
}

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() {
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]);
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() {
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]);
expect(dashboard.rows[0].panels[1].repeat).to.be(undefined);

View File

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

View File

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

View File

@ -57,59 +57,3 @@
</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="span2"></div>
<div class="page-container">
<div class="grafana-info-box span8 text-center">
<h3>Page not found (404)</h3>
</div>
<div class="span2"></div>
<div class="page-header">
<h1>
Page not found (404)
</h1>
</div>
</div>

View File

@ -1,5 +1,5 @@
{
"revision": 5,
"revision": 6,
"title": "TestData - Graph Panel Last 1h",
"tags": [
"grafana-test"
@ -7,8 +7,48 @@
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": 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": [
{
"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,
@ -332,7 +378,13 @@
"type": "text"
}
],
"title": "New row"
"title": "New row",
"showTitle": false,
"titleSize": "h6",
"isNew": false,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null
},
{
"collapse": false,
@ -371,7 +423,7 @@
"yaxis": 2
}
],
"span": 7.99561403508772,
"span": 8,
"stack": false,
"steppedLine": false,
"targets": [
@ -432,12 +484,18 @@
"isNew": true,
"links": [],
"mode": "markdown",
"span": 4.00438596491228,
"span": 4,
"title": "",
"type": "text"
}
],
"title": "New row"
"title": "New row",
"showTitle": false,
"titleSize": "h6",
"isNew": false,
"repeat": null,
"repeatRowId": null,
"repeatIteration": null
},
{
"collapse": false,
@ -545,7 +603,7 @@
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 3,
"span": 4,
"stack": false,
"steppedLine": false,
"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": {},
"bars": false,
@ -624,7 +707,7 @@
"zindex": -3
}
],
"span": 5,
"span": 8,
"stack": true,
"steppedLine": false,
"targets": [
@ -687,49 +770,149 @@
"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",
"url": "http://grafana.org"
},
"version": "1.0.14",
"version": "1.0.15",
"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.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;
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 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;
} else if (/^\d+$/.test(target.period)) {
period = parseInt(target.period, 10);
@ -82,7 +91,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
period = 60;
}
if (range / period >= 1440) {
period = Math.ceil(range / 1440 / 60) * 60;
period = Math.ceil(range / 1440 / periodUnit) * periodUnit;
}
return period;

View File

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

View File

@ -50,11 +50,9 @@
@import "components/tagsinput";
@import "components/tables_lists";
@import "components/search";
@import "components/tightform";
@import "components/gf-form";
@import "components/sidemenu";
@import "components/navbar";
@import "components/gfbox";
@import "components/timepicker";
@import "components/filter-controls";
@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
.modal-body, .gf-box {
.modal-body {
.nav-tabs {
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 {
width: 100%;
overflow: auto;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none
}
-ms-overflow-style: none;
}
.add-panel-panels {

View File

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

View File

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

View File

@ -38,10 +38,10 @@
background-color: transparent;
border: none;
padding: ($tabs-padding-top + $tabs-top-margin) $spacer $tabs-padding-bottom;
color: $text-color;
i {
font-size: 120%;
}
color: $text-color;
&:hover {
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 {
margin-bottom: 10px;
padding: $spacer/2 0 $spacer 0;
td {
padding: 1px;
}
button.btn-sm {
@include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl);
font-size: $font-size-sm;
background-image: none;
border: none;
padding: 6px 10px;
padding: 5px 11px;
color: $text-color;
&.active span {
color: $blue;

View File

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

View File

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

View File

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

View File

@ -5,8 +5,8 @@
* Version: 0.13.4 - 2015-09-03
* 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.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", ["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"]);
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) {
$templateCache.put("template/datepicker/datepicker.html",
"<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 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">
<base href="[[.AppSubUrl]]/" />
</head>
<body>
<div class="gf-box" style="margin: 200px auto 0 auto; width: 500px;">
<div class="gf-box-header">
<span class="gf-box-title">
<div class="page-container">
<div class="page-header">
<h1>
Server side error :(
</span>
</h1>
</div>
<div class="gf-box-body">
<h4>[[.Title]]</h4>
[[.ErrorMsg]]
</div>
<h4>[[.Title]]</h4>
<pre>[[.ErrorMsg]]</pre>
</div>
</body>
</body>
</html>