Merge branch 'master' into export-dashboard

Conflicts:
	public/app/features/dashboard/submenu/submenu.ts
pull/5379/head
Torkel Ödegaard 9 years ago
commit ba6573af61
  1. 13
      .github/ISSUE_TEMPLATE.md
  2. 24
      CHANGELOG.md
  3. 2
      docs/sources/installation/configuration.md
  4. 6
      docs/sources/installation/debian.md
  5. 8
      docs/sources/installation/rpm.md
  6. 2
      docs/sources/installation/windows.md
  7. 4
      latest.json
  8. 22
      packaging/publish/publish.sh
  9. 2
      pkg/api/api.go
  10. 11
      pkg/api/cloudwatch/cloudwatch.go
  11. 25
      pkg/api/dashboard.go
  12. 18
      pkg/api/dtos/index.go
  13. 14
      pkg/api/index.go
  14. 2
      pkg/api/pluginproxy/pluginproxy.go
  15. 2
      pkg/cmd/grafana-server/main.go
  16. 9
      public/app/core/controllers/login_ctrl.js
  17. 4
      public/app/core/directives/metric_segment.js
  18. 51
      public/app/core/services/datasource_srv.js
  19. 4
      public/app/core/time_series2.ts
  20. 2
      public/app/features/annotations/editor_ctrl.js
  21. 19
      public/app/features/dashboard/rowCtrl.js
  22. 4
      public/app/features/dashboard/shareModalCtrl.js
  23. 25
      public/app/features/dashboard/submenu/submenu.ts
  24. 9
      public/app/features/dashboard/timeSrv.js
  25. 20
      public/app/features/panel/panel_ctrl.ts
  26. 2
      public/app/features/panel/panel_directive.ts
  27. 25
      public/app/features/templating/templateSrv.js
  28. 29
      public/app/features/templating/templateValuesSrv.js
  29. 9
      public/app/partials/login.html
  30. 1
      public/app/partials/signup_step2.html
  31. 14
      public/app/plugins/datasource/elasticsearch/datasource.js
  32. 6
      public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html
  33. 25
      public/app/plugins/datasource/prometheus/datasource.ts
  34. 4
      public/app/plugins/datasource/prometheus/query_ctrl.ts
  35. 2
      public/app/plugins/panel/graph/graph.js
  36. 18
      public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts
  37. 1
      public/app/plugins/panel/singlestat/module.html
  38. 5
      public/app/plugins/panel/singlestat/module.ts
  39. 4
      public/sass/_variables.dark.scss
  40. 4
      public/sass/_variables.light.scss
  41. 43
      public/sass/components/_footer.scss
  42. 1
      public/sass/components/_panel_singlestat.scss
  43. 2
      public/sass/layout/_page.scss
  44. 6
      public/test/core/time_series_specs.js
  45. 13
      public/test/specs/templateSrv-specs.js
  46. 53
      public/test/specs/templateValuesSrv-specs.js
  47. 35
      public/views/index.html
  48. 4
      vendor/phantomjs/render.js

@ -1,20 +1,19 @@
Thank you! For helping us make Grafana even better.
Thank you for helping us make Grafana even better!
To help us respond to your issues faster, please make sure to add as much information as possible.
To help us respond to your issues more quickly, please make sure to add as much information as possible.
If this issue is about a plugin, please open the issue in that repository.
If this issue is about a plugin, please open the issue in that plugin's repository.
Start your issues title with [Feature Request] / [Bug] / [Question] or no tag if your unsure. Also, please be aware that GitHub now supports uploading of screenshots; look at the bottom of this input field.
Start your issue's title with [Feature Request] / [Bug] / [Question] or no tag if you're unsure. Also, please be aware that GitHub now supports uploading of screenshots; look at the bottom of this input field.
Please include some basic information:
- What grafana version are you using?
- What Grafana version are you using?
- What datasource are you using?
- What OS are you running grafana on?
- What did you do?
- What was the expected result?
- What happenend instead?
If you question/bug relates to a metric query / unexpected data visualization, please include:
If your question/bug relates to a metric query / unexpected data visualization, please include:
- An image or text representation of your metric query
- The raw query and response from your data source (check this in chrome dev tools network tab)

@ -1,13 +1,33 @@
# 3.1.0
# 3.1.0 (unreleased)
### Enhancements
* **Dashboard Url**: Time range changes updates url, closes [#458](https://github.com/grafana/grafana/issues/458)
* **Dashboard Url**: Template variable change updates url, closes [#5002](https://github.com/grafana/grafana/issues/5002)
* **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319)
* **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189)
* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011)
* **Page Footer**: Added page footer with links to docs, shows Grafana version and info if new version is available, closes [#4889](https://github.com/grafana/grafana/pull/4889)
# 3.0.3 Patch release (unreleased)
# 3.0.4 Patch release (2016-05-25)
* **Panel**: Fixed blank dashboard issue when switching to other dashboard while in fullscreen edit mode, fixes [#5163](https://github.com/grafana/grafana/pull/5163)
* **Templating**: Fixed issue with nested multi select variables and cascading and updating child variable selection state, fixes [#4861](https://github.com/grafana/grafana/pull/4861)
* **Templating**: Fixed issue with using templated data source in another template variable query, fixes [#5165](https://github.com/grafana/grafana/pull/5165)
* **Singlestat gauge**: Fixed issue with gauge render position, fixes [#5143](https://github.com/grafana/grafana/pull/5143)
* **Home dashboard**: Fixes broken home dashboard api, fixes [#5167](https://github.com/grafana/grafana/issues/5167)
# 3.0.3 Patch release (2016-05-23)
* **Annotations**: Annotations can now use a template variable as data source, closes [#5054](https://github.com/grafana/grafana/issues/5054)
* **Time picker**: Fixed issue timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078)
* **CloudWatch**: Support for Multiple Account by AssumeRole, closes [#3522](https://github.com/grafana/grafana/issues/3522)
* **Singlestat**: Fixed alignment and minium height issue, fixes [#5113](https://github.com/grafana/grafana/issues/5113), fixes [#4679](https://github.com/grafana/grafana/issues/4679)
* **Share modal**: Fixed link when using grafana under dashboard sub url, fixes [#5109](https://github.com/grafana/grafana/issues/5109)
* **Prometheus**: Fixed bug in query editor that caused it not to load when reloading page, fixes [#5107](https://github.com/grafana/grafana/issues/5107)
* **Elasticsearch**: Fixed bug when template variable query returns numeric values, fixes [#5097](https://github.com/grafana/grafana/issues/5097), fixes [#5088](https://github.com/grafana/grafana/issues/5088)
* **Logging**: Fixed issue with reading logging level value, fixes [#5079](https://github.com/grafana/grafana/issues/5079)
* **Timepicker**: Fixed issue with timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078)
* **Docs**: Added docs for org & user preferences HTTP API, closes [#5069](https://github.com/grafana/grafana/issues/5069)
* **Plugin list panel**: Now shows correct enable state for apps when not enabled, fixes [#5068](https://github.com/grafana/grafana/issues/5068)
* **Elasticsearch**: Templating & Annotation queries that use template variables are now formatted correctly, fixes [#5135](https://github.com/grafana/grafana/issues/5135)
# 3.0.2 Patch release (2016-05-16)

@ -226,7 +226,7 @@ organization to be created for that new user.
The role new users will be assigned for the main organization (if the
above setting is set to true). Defaults to `Viewer`, other valid
options are `Admin` and `Editor`.
options are `Admin` and `Editor` and `Read-Only Editor`.
<hr>

@ -10,13 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download
------------ | -------------
Stable .deb for Debian-based Linux | [grafana_3.0.2-1463383025_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb)
Stable .deb for Debian-based Linux | [grafana_3.0.4-1464167696.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.4-1464167696_amd64.deb)
## Install Stable
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.4-1464167696_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_3.0.2-1463383025_amd64.deb
$ sudo dpkg -i grafana_3.0.4-1464167696_amd64.deb
## APT Repository

@ -10,24 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.2-1463383025.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm)
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.4-1464167696.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.4-1464167696.x86_64.rpm)
## Install Stable Release from package file
You can install Grafana using Yum directly.
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.4-1464167696.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-3.0.2-1463383025.x86_64.rpm
$ sudo rpm -Uvh grafana-3.0.4-1464167696.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-3.0.2-1463383025.x86_64.rpm
$ sudo rpm -i --nodeps grafana-3.0.4-1464167696.x86_64.rpm
## Install via YUM Repository

@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
Description | Download
------------ | -------------
Stable Zip package for Windows | [grafana.3.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.2.windows-x64.zip)
Stable Zip package for Windows | [grafana.3.0.4.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.4.windows-x64.zip)
## Configure

@ -1,4 +1,4 @@
{
"stable": "3.0.2",
"testing": "3.0.2"
"stable": "3.0.4",
"testing": "3.0.4"
}

@ -1,22 +1,20 @@
#! /usr/bin/env bash
deb_ver=3.0.1
rpm_ver=3.0.1-1
deb_ver=3.0.4-1464167696
rpm_ver=3.0.4-1464167696
#rpm_ver=3.0.0-1
wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
#wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb
package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb
#package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb
#package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb
package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
#package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
#package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
#wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
#package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm
#package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm
package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
package_cloud push grafana/stable/el/7 grafana-${rpm_ver}.x86_64.rpm
package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm

@ -211,7 +211,7 @@ func Register(r *macaron.Macaron) {
r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
r.Get("/file/:file", GetDashboardFromJsonFile)
r.Get("/home", GetHomeDashboard)
r.Get("/home", wrap(GetHomeDashboard))
r.Get("/tags", GetDashboardTags)
r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
})

@ -57,11 +57,12 @@ var awsCredentialCache map[string]cache = make(map[string]cache)
var credentialCacheLock sync.RWMutex
func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials {
cacheKey := profile + ":" + assumeRoleArn
credentialCacheLock.RLock()
if _, ok := awsCredentialCache[profile]; ok {
if awsCredentialCache[profile].expiration != nil &&
(*awsCredentialCache[profile].expiration).After(time.Now().UTC()) {
result := awsCredentialCache[profile].credential
if _, ok := awsCredentialCache[cacheKey]; ok {
if awsCredentialCache[cacheKey].expiration != nil &&
(*awsCredentialCache[cacheKey].expiration).After(time.Now().UTC()) {
result := awsCredentialCache[cacheKey].credential
credentialCacheLock.RUnlock()
return result
}
@ -118,7 +119,7 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
})
credentialCacheLock.Lock()
awsCredentialCache[profile] = cache{
awsCredentialCache[cacheKey] = cache{
credential: creds,
expiration: expiration,
}

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@ -158,30 +159,27 @@ func canEditDashboard(role m.RoleType) bool {
return role == m.ROLE_ADMIN || role == m.ROLE_EDITOR || role == m.ROLE_READ_ONLY_EDITOR
}
func GetHomeDashboard(c *middleware.Context) {
func GetHomeDashboard(c *middleware.Context) Response {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
c.JsonApiErr(500, "Failed to get preferences", err)
return ApiError(500, "Failed to get preferences", err)
}
if prefsQuery.Result.HomeDashboardId != 0 {
slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
err := bus.Dispatch(&slugQuery)
if err != nil {
c.JsonApiErr(500, "Failed to get slug from database", err)
return
if err == nil {
dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result}
return Json(200, &dashRedirect)
} else {
log.Warn("Failed to get slug from database, %s", err.Error())
}
dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result}
c.JSON(200, &dashRedirect)
return
}
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
file, err := os.Open(filePath)
if err != nil {
c.JsonApiErr(500, "Failed to load home dashboard", err)
return
return ApiError(500, "Failed to load home dashboard", err)
}
dash := dtos.DashboardFullWithMeta{}
@ -189,11 +187,10 @@ func GetHomeDashboard(c *middleware.Context) {
dash.Meta.CanEdit = canEditDashboard(c.OrgRole)
jsonParser := json.NewDecoder(file)
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
c.JsonApiErr(500, "Failed to load home dashboard", err)
return
return ApiError(500, "Failed to load home dashboard", err)
}
c.JSON(200, &dash)
return Json(200, &dash)
}
func GetDashboardFromJsonFile(c *middleware.Context) {

@ -1,13 +1,17 @@
package dtos
type IndexViewData struct {
User *CurrentUser
Settings map[string]interface{}
AppUrl string
AppSubUrl string
GoogleAnalyticsId string
GoogleTagManagerId string
MainNavLinks []*NavLink
User *CurrentUser
Settings map[string]interface{}
AppUrl string
AppSubUrl string
GoogleAnalyticsId string
GoogleTagManagerId string
MainNavLinks []*NavLink
BuildVersion string
BuildCommit string
NewGrafanaVersionExists bool
NewGrafanaVersion string
}
type PluginCss struct {

@ -36,11 +36,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
LightTheme: prefs.Theme == "light",
Timezone: prefs.Timezone,
},
Settings: settings,
AppUrl: setting.AppUrl,
AppSubUrl: setting.AppSubUrl,
GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId,
Settings: settings,
AppUrl: setting.AppUrl,
AppSubUrl: setting.AppSubUrl,
GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion,
BuildCommit: setting.BuildCommit,
NewGrafanaVersion: plugins.GrafanaLatestVersion,
NewGrafanaVersionExists: plugins.GrafanaHasUpdate,
}
if setting.DisableGravatar {

@ -88,7 +88,7 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins
}
for key, value := range headers {
log.Info("setting key %v value %v", key, value[0])
log.Trace("setting key %v value %v", key, value[0])
req.Header.Set(key, value[0])
}
}

@ -24,7 +24,7 @@ import (
"github.com/grafana/grafana/pkg/social"
)
var version = "3.0.0-beta4"
var version = "3.1.0"
var commit = "NA"
var buildstamp string
var build_date string

@ -35,15 +35,6 @@ function (angular, coreModule, config) {
}
};
// build info view model
$scope.buildInfo = {
version: config.buildInfo.version,
commit: config.buildInfo.commit,
buildstamp: new Date(config.buildInfo.buildstamp * 1000),
latestVersion: config.buildInfo.latestVersion,
hasUpdate: config.buildInfo.hasUpdate,
};
$scope.submit = function() {
if ($scope.loginMode) {
$scope.login();

@ -209,7 +209,9 @@ function (_, $, coreModule) {
// needs to call this after digest so
// property is synced with outerscope
$scope.$$postDigest(function() {
$scope.onChange();
$scope.$apply(function() {
$scope.onChange();
});
});
};

@ -66,14 +66,17 @@ function (angular, _, coreModule, config) {
};
this.getAnnotationSources = function() {
return _.reduce(config.datasources, function(memo, value) {
var sources = [];
this.addDataSourceVariables(sources);
_.each(config.datasources, function(value) {
if (value.meta && value.meta.annotations) {
memo.push(value);
sources.push(value);
}
});
return memo;
}, []);
return sources;
};
this.getMetricSources = function(options) {
@ -90,24 +93,7 @@ function (angular, _, coreModule, config) {
});
if (!options || !options.skipVariables) {
// look for data source variables
for (var i = 0; i < templateSrv.variables.length; i++) {
var variable = templateSrv.variables[i];
if (variable.type !== 'datasource') {
continue;
}
var first = variable.current.value;
var ds = config.datasources[first];
if (ds) {
metricSources.push({
name: '$' + variable.name,
value: '$' + variable.name,
meta: ds.meta,
});
}
}
this.addDataSourceVariables(metricSources);
}
metricSources.sort(function(a, b) {
@ -123,6 +109,27 @@ function (angular, _, coreModule, config) {
return metricSources;
};
this.addDataSourceVariables = function(list) {
// look for data source variables
for (var i = 0; i < templateSrv.variables.length; i++) {
var variable = templateSrv.variables[i];
if (variable.type !== 'datasource') {
continue;
}
var first = variable.current.value;
var ds = config.datasources[first];
if (ds) {
list.push({
name: '$' + variable.name,
value: '$' + variable.name,
meta: ds.meta,
});
}
}
};
this.init();
});
});

@ -173,8 +173,8 @@ export default class TimeSeries {
isMsResolutionNeeded() {
for (var i = 0; i < this.datapoints.length; i++) {
if (this.datapoints[i][0] !== null) {
var timestamp = this.datapoints[i][0].toString();
if (this.datapoints[i][1] !== null) {
var timestamp = this.datapoints[i][1].toString();
if (timestamp.length === 13 && (timestamp % 1000) !== 0) {
return true;
}

@ -30,7 +30,7 @@ function (angular, _, $) {
$scope.datasourceChanged = function() {
return datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) {
$scope.currentDatasource = ds;
$scope.currentAnnotation.datasource = ds.name;
$scope.currentAnnotation.datasource = $scope.currentAnnotation.datasource;
});
};

@ -142,12 +142,19 @@ function (angular, _, config) {
});
module.directive('panelWidth', function() {
return function(scope, element) {
var fullscreen = false;
function updateWidth() {
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
if (!fullscreen) {
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
}
}
scope.onAppEvent('panel-fullscreen-enter', function(evt, info) {
fullscreen = true;
if (scope.panel.id !== info.panelId) {
element.hide();
} else {
@ -156,14 +163,20 @@ function (angular, _, config) {
});
scope.onAppEvent('panel-fullscreen-exit', function(evt, info) {
fullscreen = false;
if (scope.panel.id !== info.panelId) {
element.show();
} else {
updateWidth();
}
updateWidth();
});
scope.$watch('panel.span', updateWidth);
if (fullscreen) {
element.hide();
}
};
});

@ -74,12 +74,12 @@ function (angular, _, require, config) {
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
var soloUrl = $scope.shareUrl;
soloUrl = soloUrl.replace('/dashboard/', '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace("&fullscreen", "");
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
$scope.imageUrl = soloUrl.replace('/dashboard-solo/', '/render/dashboard-solo/');
$scope.imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/');
$scope.imageUrl += '&width=1000';
$scope.imageUrl += '&height=500';
};

@ -1,6 +1,7 @@
///<reference path="../../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
export class SubmenuCtrl {
annotations: any;
@ -8,7 +9,11 @@ export class SubmenuCtrl {
dashboard: any;
/** @ngInject */
constructor(private $rootScope, private templateValuesSrv) {
constructor(private $rootScope,
private templateValuesSrv,
private templateSrv,
private dynamicDashboardSrv,
private $location) {
this.annotations = this.dashboard.templating.list;
this.variables = this.dashboard.templating.list;
}
@ -22,8 +27,26 @@ export class SubmenuCtrl {
return this.templateValuesSrv.getValuesForTag(variable, tagKey);
}
updateUrlParamsWithCurrentVariables() {
// update url
var params = this.$location.search();
// remove variable params
_.each(params, function(value, key) {
if (key.indexOf('var-') === 0) {
delete params[key];
}
});
// add new values
this.templateSrv.fillVariableValuesForUrl(params);
// update url
this.$location.search(params);
}
variableUpdated(variable) {
this.templateValuesSrv.variableUpdated(variable).then(() => {
this.dynamicDashboardSrv.update(this.dashboard);
this.updateUrlParamsWithCurrentVariables();
this.$rootScope.$emit('template-variable-value-updated');
this.$rootScope.$broadcast('refresh');
});

@ -10,7 +10,7 @@ define([
var module = angular.module('grafana.services');
module.service('timeSrv', function($rootScope, $timeout, $routeParams, timer) {
module.service('timeSrv', function($rootScope, $timeout, $routeParams, timer, $location) {
var self = this;
this.init = function(dashboard) {
@ -108,6 +108,13 @@ define([
this.old_refresh = null;
}
// update url params
var urlParams = $location.search();
var urlRange = this.timeRangeForUrl();
urlParams.from = urlRange.from;
urlParams.to = urlRange.to;
$location.search(urlParams);
$rootScope.appEvent('time-range-changed', this.time);
$timeout(this.refreshDashboard, 0);
};

@ -8,6 +8,7 @@ import $ from 'jquery';
const TITLE_HEIGHT = 25;
const EMPTY_TITLE_HEIGHT = 9;
const PANEL_PADDING = 5;
const PANEL_BORDER = 2;
import {Emitter} from 'app/core/core';
@ -90,6 +91,23 @@ export class PanelCtrl {
this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
this.editModeInitiated = true;
this.events.emit('init-edit-mode', null);
var routeParams = this.$injector.get('$routeParams');
if (routeParams.editorTab) {
this.editorTabs.forEach((tab, i) => {
if (tab.title === routeParams.editorTab) {
this.editorTabIndex = i;
}
});
}
}
changeTab(newIndex) {
this.editorTabIndex = newIndex;
var route = this.$injector.get('$route');
route.current.params.editorTab = this.editorTabs[newIndex].title;
route.updateParams();
}
addEditorTab(title, directiveFn, index?) {
@ -141,7 +159,7 @@ export class PanelCtrl {
}
}
this.height = this.containerHeight - (PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));
this.height = this.containerHeight - (PANEL_BORDER + PANEL_PADDING + (this.panel.title ? TITLE_HEIGHT : EMPTY_TITLE_HEIGHT));
}
render(payload?) {

@ -36,7 +36,7 @@ var panelTemplate = `
<ul class="gf-tabs">
<li class="gf-tabs-item" ng-repeat="tab in ::ctrl.editorTabs">
<a class="gf-tabs-link" ng-click="ctrl.editorTabIndex = $index" ng-class="{active: ctrl.editorTabIndex === $index}">
<a class="gf-tabs-link" ng-click="ctrl.changeTab($index)" ng-class="{active: ctrl.editorTabIndex === $index}">
{{::tab.title}}
</a>
</li>

@ -42,6 +42,16 @@ function (angular, _) {
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1");
}
this.luceneFormat = function(value) {
if (typeof value === 'string') {
return luceneEscape(value);
}
var quotedValues = _.map(value, function(val) {
return '\"' + luceneEscape(val) + '\"';
});
return '(' + quotedValues.join(' OR ') + ')';
};
this.formatValue = function(value, format, variable) {
// for some scopedVars there is no variable
variable = variable || {};
@ -60,13 +70,7 @@ function (angular, _) {
return '(' + escapedValues.join('|') + ')';
}
case "lucene": {
if (typeof value === 'string') {
return luceneEscape(value);
}
var quotedValues = _.map(value, function(val) {
return '\"' + luceneEscape(val) + '\"';
});
return '(' + quotedValues.join(' OR ') + ')';
return this.luceneFormat(value, format, variable);
}
case "pipe": {
if (typeof value === 'string') {
@ -97,8 +101,11 @@ function (angular, _) {
if (!str) {
return false;
}
var match = this._regex.exec(str);
return match && (match[1] === variableName || match[2] === variableName);
variableName = regexEscape(variableName);
var findVarRegex = new RegExp('\\$(' + variableName + ')(?:\\W|$)|\\[\\[(' + variableName + ')\\]\\]', 'g');
var match = findVarRegex.exec(str);
return match !== null;
};
this.highlightVariablesAsHtml = function(str) {

@ -79,7 +79,6 @@ function (angular, _, kbn) {
else if (variable.refresh === 1 || variable.refresh === 2) {
return self.updateOptions(variable).then(function() {
if (_.isEmpty(variable.current) && variable.options.length) {
console.log("setting current for %s", variable.name);
self.setVariableValue(variable, variable.options[0]);
}
lock.resolve();
@ -102,7 +101,10 @@ function (angular, _, kbn) {
}
return promise.then(function() {
var option = _.findWhere(variable.options, { text: urlValue });
var option = _.find(variable.options, function(op) {
return op.text === urlValue || op.value === urlValue;
});
option = option || { text: urlValue, value: urlValue };
self.updateAutoInterval(variable);
@ -244,15 +246,26 @@ function (angular, _, kbn) {
this.validateVariableSelectionState = function(variable) {
if (!variable.current) {
if (!variable.options.length) { return; }
return self.setVariableValue(variable, variable.options[0], true);
return self.setVariableValue(variable, variable.options[0], false);
}
if (_.isArray(variable.current.value)) {
self.selectOptionsForCurrentValue(variable);
// updated selected value
var selected = {
value: _.map(_.filter(variable.options, {selected: true}), function(op) {
return op.value;
})
};
// if none pick first
if (selected.value.length === 0) {
selected = variable.options[0];
}
return self.setVariableValue(variable, selected, false);
} else {
var currentOption = _.findWhere(variable.options, {text: variable.current.text});
if (currentOption) {
return self.setVariableValue(variable, currentOption, true);
return self.setVariableValue(variable, currentOption, false);
} else {
if (!variable.options.length) { return; }
return self.setVariableValue(variable, variable.options[0]);
@ -313,6 +326,14 @@ function (angular, _, kbn) {
var value = item.value || item.text;
var text = item.text || item.value;
if (_.isNumber(value)) {
value = value.toString();
}
if (_.isNumber(text)) {
text = text.toString();
}
if (regex) {
matches = regex.exec(value);
if (!matches) { continue; }

@ -73,14 +73,5 @@
</div>
</div>
<div class="row" style="margin-top: 50px">
<div class="version-footer text-center small">
Grafana version: {{buildInfo.version}}, commit: {{buildInfo.commit}},
build date: {{buildInfo.buildstamp | date: 'yyyy-MM-dd HH:mm:ss' }}
</div>
<div class="version-footer text-center small" ng-show="buildInfo.hasUpdate">
<a class="external-link" target="_blank" href="http://grafana.org/download">New Grafana Version Available ({{buildInfo.latestVersion}})</a>
</div>
</div>
</div>
</div>

@ -67,7 +67,6 @@
</form>
</div>
</div>
</div>

@ -78,7 +78,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
range[timeField]["format"] = "epoch_millis";
}
var queryInterpolated = templateSrv.replace(queryString);
var queryInterpolated = templateSrv.replace(queryString, {}, 'lucene');
var filter = { "bool": { "must": [{ "range": range }] } };
var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
var data = {
@ -204,6 +204,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
});
};
function escapeForJson(value) {
return value.replace(/\"/g, '\\"');
}
function luceneThenJsonFormat(value) {
return escapeForJson(templateSrv.luceneFormat(value));
}
this.getFields = function(query) {
return this._get('/_mapping').then(function(res) {
var fields = {};
@ -246,7 +254,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
var header = this.getQueryHeader('count', range.from, range.to);
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
esQuery = esQuery.replace("$lucene_query", queryDef.query || '*');
esQuery = esQuery.replace("$lucene_query", escapeForJson(queryDef.query || '*'));
esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf());
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
esQuery = header + '\n' + esQuery + '\n';
@ -260,7 +268,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
};
this.metricFindQuery = function(query) {
query = templateSrv.replace(query);
query = templateSrv.replace(query, {}, luceneThenJsonFormat);
query = angular.fromJson(query);
if (!query) {
return $q.when([]);

@ -70,9 +70,9 @@
</div>
<div ng-if="agg.type === 'filters'">
<div class="gf-form-inline" ng-repeat="filter in agg.settings.filters" ng-class="{last: $last}">
<div class="gf-form-inline offset-width-7" ng-repeat="filter in agg.settings.filters">
<div class="gf-form">
<label class="gf-form-item width-10">Query {{$index + 1}}</label>
<label class="gf-form-label width-10">Query {{$index + 1}}</label>
<input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
</div>
<div class="gf-form">
@ -88,7 +88,7 @@
<div ng-if="agg.type === 'geohash_grid'">
<div class="gf-form offset-width-7">
<label class="gf-form-label">Precision</label>
<label class="gf-form-label width-10">Precision</label>
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision" spellcheck='false' placeholder="3" ng-blur="onChangeInternal()">
</div>
</div>

@ -256,23 +256,14 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
return this.renderTemplate(options.legendFormat, labelData) || '{}';
};
this.renderTemplate = function(format, data) {
var originalSettings = _.templateSettings;
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
var template = _.template(templateSrv.replace(format));
var result;
try {
result = template(data);
} catch (e) {
result = null;
}
_.templateSettings = originalSettings;
return result;
this.renderTemplate = function(aliasPattern, aliasData) {
var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g;
return aliasPattern.replace(aliasRegex, function(match, g1) {
if (aliasData[g1]) {
return aliasData[g1];
}
return g1;
});
};
this.getOriginalMetricName = function(labelData) {

@ -58,6 +58,10 @@ class PrometheusQueryCtrl extends QueryCtrl {
updateLink() {
var range = this.panelCtrl.range;
if (!range) {
return;
}
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
var expr = {

@ -66,7 +66,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function getLegendHeight(panelHeight) {
if (!panel.legend.show || panel.legend.rightSide) {
return 2;
return 0;
}
if (panel.legend.alignAsTable) {

@ -22,8 +22,8 @@ describe('GraphCtrl', function() {
describe('msResolution with second resolution timestamps', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[1234567890, 45], [1234567899, 60]]},
{ target: 'test.cpu2', datapoints: [[1236547890, 55], [1234456709, 90]]}
{ target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890], [90, 1234456709]]}
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.onDataReceived(data);
@ -37,8 +37,8 @@ describe('GraphCtrl', function() {
describe('msResolution with millisecond resolution timestamps', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]},
{ target: 'test.cpu2', datapoints: [[1236547890001, 55], [1234456709000, 90]]}
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890001], [90, 1234456709000]]}
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.onDataReceived(data);
@ -52,8 +52,8 @@ describe('GraphCtrl', function() {
describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]},
{ target: 'test.cpu2', datapoints: [[1236547890000, 55], [1234456709000, 90]]}
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890000], [90, 1234456709000]]}
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.onDataReceived(data);
@ -67,9 +67,9 @@ describe('GraphCtrl', function() {
describe('msResolution with millisecond resolution timestamps in one of the series', function() {
beforeEach(function() {
var data = [
{ target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]},
{ target: 'test.cpu2', datapoints: [[1236547890010, 55], [1234456709000, 90]]},
{ target: 'test.cpu3', datapoints: [[1236547890000, 65], [1234456709000, 120]]}
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
{ target: 'test.cpu2', datapoints: [[55, 1236547890010], [90, 1234456709000]]},
{ target: 'test.cpu3', datapoints: [[65, 1236547890000], [120, 1234456709000]]}
];
ctx.ctrl.panel.tooltip.msResolution = false;
ctx.ctrl.onDataReceived(data);

@ -1,4 +1,3 @@
<div class="singlestat-panel">
</div>
<div class="clearfix"></div>

@ -325,6 +325,9 @@ class SingleStatCtrl extends MetricsPanelCtrl {
}
function addGauge() {
var width = elem.width();
var height = elem.height();
ctrl.invalidGaugeRange = false;
if (panel.gauge.minValue > panel.gauge.maxValue) {
ctrl.invalidGaugeRange = true;
@ -332,8 +335,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
}
var plotCanvas = $('<div></div>');
var width = elem.width();
var height = elem.height();
var plotCss = {
top: '10px',
margin: 'auto',

@ -268,3 +268,7 @@ $checkboxImageUrl: '../img/checkbox.png';
$card-background: linear-gradient(135deg, #2f2f2f, #262626);
$card-background-hover: linear-gradient(135deg, #343434, #262626);
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3);
// footer
$footer-link-color: $gray-1;
$footer-link-hover: $gray-4;

@ -292,3 +292,7 @@ $checkboxImageUrl: '../img/checkbox_white.png';
$card-background: linear-gradient(135deg, $gray-5, $gray-6);
$card-background-hover: linear-gradient(135deg, $gray-6, $gray-7);
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1);
// footer
$footer-link-color: $gray-3;
$footer-link-hover: $dark-5;

@ -1,9 +1,38 @@
.grafana-version-info {
position: absolute;
bottom: 2px;
left: 3px;
font-size: 80%;
color: darken($gray-1, 25%);
a { color: darken($gray-1, 25%); }
.page-dashboard .footer {
display: none;
}
.footer {
color: $footer-link-color;
padding: 5rem 0 1rem 0;
font-size: $font-size-xs;
width: 98%; /* was causing horiz scrollbars - need to examine */
a {
color: $footer-link-color;
&:hover {
color: $footer-link-hover;
}
}
ul {
list-style: none;
}
li {
display: inline-block;
padding-right: 2px;
&:after {
content: ' | ';
padding-left: 2px;
}
}
li:last-child {
&:after {
padding-left: 0;
content: '';
}
}
}

@ -5,7 +5,6 @@
}
.singlestat-panel-value-container {
padding: 20px;
display: table-cell;
vertical-align: middle;
text-align: center;

@ -4,7 +4,7 @@
}
.main-view {
height: 100%;
// height: 100%; REMOVED FOR FOOTER TRW
}
.page-container {

@ -56,7 +56,7 @@ define([
});
});
describe('can detect if serie contains ms precision', function() {
describe('can detect if series contains ms precision', function() {
var fakedata;
beforeEach(function() {
@ -64,13 +64,13 @@ define([
});
it('missing datapoint with ms precision', function() {
fakedata.datapoints[0] = [1234567890000, 1337];
fakedata.datapoints[0] = [1337, 1234567890000];
series = new TimeSeries(fakedata);
expect(series.isMsResolutionNeeded()).to.be(false);
});
it('contains datapoint with ms precision', function() {
fakedata.datapoints[0] = [1236547890001, 1337];
fakedata.datapoints[0] = [1337, 1236547890001];
series = new TimeSeries(fakedata);
expect(series.isMsResolutionNeeded()).to.be(true);
});

@ -141,8 +141,8 @@ define([
});
it('slash should be properly escaped in regex format', function() {
var result = _templateSrv.formatValue('Gi3/14', 'regex');
expect(result).to.be('Gi3\\/14');
var result = _templateSrv.formatValue('Gi3/14', 'regex');
expect(result).to.be('Gi3\\/14');
});
});
@ -200,6 +200,15 @@ define([
expect(contains).to.be(true);
});
it('should find it when part of segment', function() {
var contains = _templateSrv.containsVariable('metrics.$env.$group-*', 'group');
expect(contains).to.be(true);
});
it('should find it its the only thing', function() {
var contains = _templateSrv.containsVariable('$env', 'env');
expect(contains).to.be(true);
});
});
describe('updateTemplateData with simple value', function() {

@ -126,6 +126,59 @@ define([
});
});
describeUpdateVariable('query variable with multi select and new options does not contain some selected values', function(scenario) {
scenario.setup(function() {
scenario.variable = {
type: 'query',
query: '',
name: 'test',
current: {
value: ['val1', 'val2', 'val3'],
text: 'val1 + val2 + val3'
}
};
scenario.queryResult = [{text: 'val2'}, {text: 'val3'}];
});
it('should update current value', function() {
expect(scenario.variable.current.value).to.eql(['val2', 'val3']);
expect(scenario.variable.current.text).to.eql('val2 + val3');
});
});
describeUpdateVariable('query variable with multi select and new options does not contain any selected values', function(scenario) {
scenario.setup(function() {
scenario.variable = {
type: 'query',
query: '',
name: 'test',
current: {
value: ['val1', 'val2', 'val3'],
text: 'val1 + val2 + val3'
}
};
scenario.queryResult = [{text: 'val5'}, {text: 'val6'}];
});
it('should update current value with first one', function() {
expect(scenario.variable.current.value).to.eql('val5');
expect(scenario.variable.current.text).to.eql('val5');
});
});
describeUpdateVariable('query variable with numeric results', function(scenario) {
scenario.setup(function() {
scenario.variable = { type: 'query', query: '', name: 'test', current: {} };
scenario.queryResult = [{text: 12, value: 12}];
});
it('should set current value to first option', function() {
expect(scenario.variable.current.value).to.be('12');
expect(scenario.variable.options[0].value).to.be('12');
expect(scenario.variable.options[0].text).to.be('12');
});
});
describeUpdateVariable('interval variable without auto', function(scenario) {
scenario.setup(function() {
scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' };

@ -39,6 +39,41 @@
</div>
<div ng-view class="main-view"></div>
<footer class="footer">
<div class="row text-center">
<ul>
<li>
<a href="http://docs.grafana.org" target="_blank">
<i class="fa fa-file-code-o"></i>
Docs
</a>
</li>
<li>
<a href="https://grafana.net/support/plans" target="_blank">
<i class="fa fa-support"></i>
Support Plans
</a>
</li>
<li>
<a href="https://grafana.org/community" target="_blank">
<i class="fa fa-comments-o"></i>
Community
</a>
</li>
<li>
<a href="http://grafana.org" target="_blank">Grafana</a>
<span>v[[.BuildVersion]] (commit: [[.BuildCommit]])</span>
</li>
<li>
[[if .NewGrafanaVersionExists]]
<a href="http://grafana.org/download" target="_blank" bs-tooltip="'[[.NewGrafanaVersion]]'">
New version available!
</a>
[[end]]
</li>
</ul>
</div>
</footer>
</grafana-app>
</body>

@ -38,10 +38,10 @@
function checkIsReady() {
var canvas = page.evaluate(function() {
if (!window.angular) { return false; }
var body = window.angular.element(document.body); // 1
var body = window.angular.element(document.body);
if (!body.scope) { return false; }
var rootScope = body.scope();
var rootScope = body.injector().get('$rootScope');
if (!rootScope) {return false;}
if (!rootScope.performance) { return false; }
var panelsToLoad = window.angular.element('div.panel').length;

Loading…
Cancel
Save