mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into export-dashboard
Conflicts: .floo .flooignore
This commit is contained in:
4
.floo
4
.floo
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"url": "https://floobits.com/raintank/grafana"
|
||||
}
|
||||
"url": "https://floobits.com/raintank/grafana"
|
||||
}
|
||||
|
@@ -5,5 +5,8 @@
|
||||
*~
|
||||
extern/
|
||||
node_modules/
|
||||
tmp
|
||||
vendor/
|
||||
tmp/
|
||||
data/
|
||||
vendor/
|
||||
public_gen/
|
||||
dist/
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,10 +1,22 @@
|
||||
# 3.0.2 Stable (unreleased)
|
||||
# 3.1.0
|
||||
|
||||
### Enhancements
|
||||
* **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)
|
||||
|
||||
# 3.0.2 Stable (2016-05-16)
|
||||
|
||||
* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988)
|
||||
* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986)
|
||||
* **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025)
|
||||
* **Graph**: Fixed broken xaxis on graph panel, fixes [#5024](https://github.com/grafana/grafana/issues/5024)
|
||||
|
||||
* **Influxdb**: Fixes crash when hiding middle serie, fixes [#5005](https://github.com/grafana/grafana/issues/5005)
|
||||
|
||||
# 3.0.1 Stable (2016-05-11)
|
||||
|
||||
### Bug fixes
|
||||
* **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934)
|
||||
|
||||
# 3.0.0-beta7 (2016-05-02)
|
||||
|
@@ -16,6 +16,7 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB.
|
||||
- [What's New in Grafana 2.0](http://docs.grafana.org/guides/whats-new-in-v2/)
|
||||
- [What's New in Grafana 2.1](http://docs.grafana.org/guides/whats-new-in-v2-1/)
|
||||
- [What's New in Grafana 2.5](http://docs.grafana.org/guides/whats-new-in-v2-5/)
|
||||
- [What's New in Grafana 3.0](http://docs.grafana.org/guides/whats-new-in-v3/)
|
||||
|
||||
## Features
|
||||
### Graphite Target Editor
|
||||
|
@@ -172,6 +172,9 @@ verify_email_enabled = false
|
||||
# Background text for the user field on the login page
|
||||
login_hint = email or username
|
||||
|
||||
# Default UI theme ("dark" or "light")
|
||||
default_theme = dark
|
||||
|
||||
#################################### Anonymous Auth ##########################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
@@ -155,6 +155,9 @@ check_for_updates = true
|
||||
# Background text for the user field on the login page
|
||||
;login_hint = email or username
|
||||
|
||||
# Default UI theme ("dark" or "light")
|
||||
;default_theme = dark
|
||||
|
||||
#################################### Anonymous Auth ##########################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
@@ -8,3 +8,10 @@ graphite:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
|
||||
fake-data-gen:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: graphite
|
||||
FD_PORT: 2003
|
||||
|
||||
|
@@ -4,3 +4,11 @@ influxdb:
|
||||
- "2004:2004"
|
||||
- "8083:8083"
|
||||
- "8086:8086"
|
||||
|
||||
fake-data-gen:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: influxdb
|
||||
FD_PORT: 8086
|
||||
|
||||
|
@@ -2,4 +2,10 @@ opentsdb:
|
||||
image: opower/opentsdb:latest
|
||||
ports:
|
||||
- "4242:4242"
|
||||
|
||||
|
||||
fake-data-gen:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
environment:
|
||||
FD_DATASOURCE: opentsdb
|
||||
|
||||
|
@@ -1,6 +1,16 @@
|
||||
prometheus:
|
||||
build: blocks/prometheus
|
||||
net: bridge
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- /var/docker/prometheus:/prometheus-data
|
||||
|
||||
fake-data-gen:
|
||||
image: grafana/fake-data-gen
|
||||
net: bridge
|
||||
ports:
|
||||
- "9091:9091"
|
||||
environment:
|
||||
FD_DATASOURCE: prom
|
||||
|
||||
|
@@ -7,10 +7,10 @@ page_keywords: grafana, opentsdb, documentation
|
||||
# OpenTSDB Guide
|
||||
The newest release of Grafana adds additional functionality when using an OpenTSDB Data source.
|
||||
|
||||

|
||||

|
||||
|
||||
1. Open the side menu by clicking the the Grafana icon in the top header.
|
||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||
1. Open the side menu by clicking the the Grafana icon in the top header.
|
||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||
|
||||
> NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization.
|
||||
|
||||
|
@@ -10,13 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable .deb for Debian-based Linux | [grafana_3.0.1_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb)
|
||||
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)
|
||||
|
||||
## Install Stable
|
||||
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.1_amd64.deb
|
||||
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb
|
||||
$ sudo apt-get install -y adduser libfontconfig
|
||||
$ sudo dpkg -i grafana_3.0.1_amd64.deb
|
||||
$ sudo dpkg -i grafana_3.0.2-1463383025_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.1-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.1-1.x86_64.rpm)
|
||||
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)
|
||||
|
||||
## 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.1-1.x86_64.rpm
|
||||
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm
|
||||
|
||||
Or install manually using `rpm`.
|
||||
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-3.0.1-1.x86_64.rpm
|
||||
$ sudo rpm -Uvh grafana-3.0.2-1463383025.x86_64.rpm
|
||||
|
||||
#### On OpenSuse:
|
||||
|
||||
$ sudo rpm -i --nodeps grafana-3.0.1-1.x86_64.rpm
|
||||
$ sudo rpm -i --nodeps grafana-3.0.2-1463383025.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.2.6.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
|
||||
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)
|
||||
|
||||
## Configure
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "3.0.1",
|
||||
"testing": "3.0.1"
|
||||
"stable": "3.0.2",
|
||||
"testing": "3.0.2"
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "3.0.2",
|
||||
"version": "3.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
|
@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
|
||||
|
||||
CONF_FILE=/etc/grafana/grafana.ini
|
||||
|
||||
RESTART_ON_UPGRADE=true
|
||||
RESTART_ON_UPGRADE=false
|
||||
|
||||
PLUGINS_DIR=/var/lib/grafana/plugins
|
||||
|
@@ -14,6 +14,6 @@ CONF_DIR=/etc/grafana
|
||||
|
||||
CONF_FILE=/etc/grafana/grafana.ini
|
||||
|
||||
RESTART_ON_UPGRADE=true
|
||||
RESTART_ON_UPGRADE=false
|
||||
|
||||
PLUGINS_DIR=/var/lib/grafana/plugins
|
||||
|
@@ -142,6 +142,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
"buildstamp": setting.BuildStamp,
|
||||
"latestVersion": plugins.GrafanaLatestVersion,
|
||||
"hasUpdate": plugins.GrafanaHasUpdate,
|
||||
"env": setting.Env,
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||
)
|
||||
|
||||
@@ -14,20 +16,17 @@ func upgradeCommand(c CommandLine) error {
|
||||
return err
|
||||
}
|
||||
|
||||
remotePlugins, err2 := s.ListAllPlugins(c.GlobalString("repo"))
|
||||
v, err2 := s.GetPlugin(localPlugin.Id, c.GlobalString("repo"))
|
||||
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
|
||||
for _, v := range remotePlugins.Plugins {
|
||||
if localPlugin.Id == v.Id {
|
||||
if ShouldUpgrade(localPlugin.Info.Version, v) {
|
||||
s.RemoveInstalledPlugin(pluginsDir, pluginName)
|
||||
return InstallPlugin(localPlugin.Id, "", c)
|
||||
}
|
||||
}
|
||||
if ShouldUpgrade(localPlugin.Info.Version, v) {
|
||||
s.RemoveInstalledPlugin(pluginsDir, pluginName)
|
||||
return InstallPlugin(localPlugin.Id, "", c)
|
||||
}
|
||||
|
||||
log.Infof("%s %s is up to date \n", color.GreenString("✔"), localPlugin.Id)
|
||||
return nil
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ func ReadPlugin(pluginDir, pluginName string) (m.InstalledPlugin, error) {
|
||||
}
|
||||
|
||||
if res.Id == "" {
|
||||
return m.InstalledPlugin{}, errors.New("could not read find plugin " + pluginName)
|
||||
return m.InstalledPlugin{}, errors.New("could not find plugin " + pluginName + " in " + pluginDir)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
@@ -69,13 +69,21 @@ func RemoveInstalledPlugin(pluginPath, id string) error {
|
||||
}
|
||||
|
||||
func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
|
||||
resp, _ := ListAllPlugins(repoUrl)
|
||||
fullUrl := repoUrl + "/repo/" + pluginId
|
||||
|
||||
for _, i := range resp.Plugins {
|
||||
if i.Id == pluginId {
|
||||
return i, nil
|
||||
}
|
||||
res, err := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do()
|
||||
if err != nil {
|
||||
return m.Plugin{}, err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return m.Plugin{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode)
|
||||
}
|
||||
|
||||
return m.Plugin{}, errors.New("could not find plugin named \"" + pluginId + "\"")
|
||||
var resp m.Plugin
|
||||
err = res.Body.FromJsonTo(&resp)
|
||||
if err != nil {
|
||||
return m.Plugin{}, errors.New("Could not load plugin data")
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
@@ -24,7 +24,16 @@ func GetPluginSettings(orgId int64) (map[string]*m.PluginSettingInfoDTO, error)
|
||||
}
|
||||
|
||||
// default to enabled true
|
||||
opt := &m.PluginSettingInfoDTO{Enabled: true}
|
||||
opt := &m.PluginSettingInfoDTO{
|
||||
PluginId: pluginDef.Id,
|
||||
OrgId: orgId,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// apps are disabled by default
|
||||
if pluginDef.Type == PluginTypeApp {
|
||||
opt.Enabled = false
|
||||
}
|
||||
|
||||
// if it's included in app check app settings
|
||||
if pluginDef.IncludedInAppId != "" {
|
||||
|
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -26,7 +28,7 @@ func GetPreferencesWithDefaults(query *m.GetPreferencesWithDefaultsQuery) error
|
||||
}
|
||||
|
||||
res := &m.Preferences{
|
||||
Theme: "dark",
|
||||
Theme: setting.DefaultTheme,
|
||||
Timezone: "browser",
|
||||
HomeDashboardId: 0,
|
||||
}
|
||||
|
@@ -88,6 +88,7 @@ var (
|
||||
AutoAssignOrgRole string
|
||||
VerifyEmailEnabled bool
|
||||
LoginHint string
|
||||
DefaultTheme string
|
||||
|
||||
// Http auth
|
||||
AdminUser string
|
||||
@@ -454,6 +455,7 @@ func NewConfigContext(args *CommandLineArgs) error {
|
||||
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
|
||||
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
|
||||
LoginHint = users.Key("login_hint").String()
|
||||
DefaultTheme = users.Key("default_theme").String()
|
||||
|
||||
// anonymous access
|
||||
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
|
||||
|
@@ -42,7 +42,9 @@ export class GrafanaApp {
|
||||
app.constant('grafanaVersion', "@grafanaVersion@");
|
||||
|
||||
app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) => {
|
||||
//$compileProvider.debugInfoEnabled(false);
|
||||
if (config.buildInfo.env !== 'development') {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
|
||||
this.registerFunctions.controller = $controllerProvider.register;
|
||||
this.registerFunctions.directive = $compileProvider.directive;
|
||||
|
@@ -396,6 +396,7 @@ function($, _) {
|
||||
kbn.valueFormats.ev = kbn.formatBuilders.decimalSIPrefix('eV');
|
||||
kbn.valueFormats.amp = kbn.formatBuilders.decimalSIPrefix('A');
|
||||
kbn.valueFormats.volt = kbn.formatBuilders.decimalSIPrefix('V');
|
||||
kbn.valueFormats.dBm = kbn.formatBuilders.decimalSIPrefix('dBm');
|
||||
|
||||
// Temperature
|
||||
kbn.valueFormats.celsius = kbn.formatBuilders.fixedUnit('°C');
|
||||
@@ -677,6 +678,7 @@ function($, _) {
|
||||
{text: 'electron volt (eV)', value: 'ev' },
|
||||
{text: 'Ampere (A)', value: 'amp' },
|
||||
{text: 'Volt (V)', value: 'volt' },
|
||||
{text: 'Decibel-milliwatt (dBm)', value: 'dBm' },
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@@ -10,7 +10,7 @@ var template = `
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<i class="icon-gf icon-gf-datasource"></i>
|
||||
<i class="icon-gf icon-gf-datasources"></i>
|
||||
</label>
|
||||
<label class="gf-form-label">
|
||||
Panel data source
|
||||
|
@@ -45,7 +45,7 @@ export default class InfluxDatasource {
|
||||
var i, y;
|
||||
|
||||
var allQueries = _.map(options.targets, (target) => {
|
||||
if (target.hide) { return []; }
|
||||
if (target.hide) { return ""; }
|
||||
|
||||
queryTargets.push(target);
|
||||
|
||||
@@ -54,8 +54,12 @@ export default class InfluxDatasource {
|
||||
var query = queryModel.render(true);
|
||||
query = query.replace(/\$interval/g, (target.interval || options.interval));
|
||||
return query;
|
||||
|
||||
}).join(";");
|
||||
}).reduce((acc, current) => {
|
||||
if (current !== "") {
|
||||
acc += ";" + current;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
|
||||
// replace grafana variables
|
||||
allQueries = allQueries.replace(/\$timeFilter/g, timeFilter);
|
||||
|
@@ -45,7 +45,7 @@
|
||||
<ul>
|
||||
<li>$m = replaced with measurement name</li>
|
||||
<li>$measurement = replaced with measurement name</li>
|
||||
<li>$1 - $9 = replaced with part of measurement name (if you seperate your measurement name with dots)</li>
|
||||
<li>$1 - $9 = replaced with part of measurement name (if you separate your measurement name with dots)</li>
|
||||
<li>$col = replaced with column name</li>
|
||||
<li>$tag_hostname = replaced with the value of the hostname tag</li>
|
||||
<li>You can also use [[tag_hostname]] pattern replacement syntax</li>
|
||||
|
@@ -16,7 +16,8 @@ export class OpenTsConfigCtrl {
|
||||
|
||||
tsdbVersions = [
|
||||
{name: '<=2.1', value: 1},
|
||||
{name: '>=2.2', value: 2},
|
||||
{name: '==2.2', value: 2},
|
||||
{name: '==2.3', value: 3},
|
||||
];
|
||||
|
||||
tsdbResolutions = [
|
||||
|
@@ -54,13 +54,12 @@ function (angular, _, dateMath) {
|
||||
});
|
||||
|
||||
return this.performTimeSeriesQuery(queries, start, end).then(function(response) {
|
||||
var metricToTargetMapping = mapMetricsToTargets(response.data, options);
|
||||
var metricToTargetMapping = mapMetricsToTargets(response.data, options, this.tsdbVersion);
|
||||
var result = _.map(response.data, function(metricData, index) {
|
||||
index = metricToTargetMapping[index];
|
||||
if (index === -1) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
this._saveTagKeys(metricData);
|
||||
|
||||
return transformMetricData(metricData, groupByTags, options.targets[index], options, this.tsdbResolution);
|
||||
@@ -114,6 +113,9 @@ function (angular, _, dateMath) {
|
||||
msResolution: msResolution,
|
||||
globalAnnotations: true
|
||||
};
|
||||
if (this.tsdbVersion === 3) {
|
||||
reqBody.showQuery = true;
|
||||
}
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (end) {
|
||||
@@ -393,23 +395,27 @@ function (angular, _, dateMath) {
|
||||
return query;
|
||||
}
|
||||
|
||||
function mapMetricsToTargets(metrics, options) {
|
||||
function mapMetricsToTargets(metrics, options, tsdbVersion) {
|
||||
var interpolatedTagValue;
|
||||
return _.map(metrics, function(metricData) {
|
||||
return _.findIndex(options.targets, function(target) {
|
||||
if (target.filters && target.filters.length > 0) {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.filters, function(filter) {
|
||||
return filter.tagk === interpolatedTagValue === "*";
|
||||
});
|
||||
} else {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.tags, function(tagV, tagK) {
|
||||
interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe');
|
||||
return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
|
||||
});
|
||||
}
|
||||
});
|
||||
if (tsdbVersion === 3) {
|
||||
return metricData.query.index;
|
||||
} else {
|
||||
return _.findIndex(options.targets, function(target) {
|
||||
if (target.filters && target.filters.length > 0) {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.filters, function(filter) {
|
||||
return filter.tagk === interpolatedTagValue === "*";
|
||||
});
|
||||
} else {
|
||||
return target.metric === metricData.metric &&
|
||||
_.all(target.tags, function(tagV, tagK) {
|
||||
interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe');
|
||||
return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.tsdbVersion == 2">
|
||||
<div class="gf-form" ng-if="ctrl.tsdbVersion >= 2">
|
||||
<label class="gf-form-label query-keyword width-6">Fill</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select ng-model="ctrl.target.downsampleFillPolicy" class="gf-form-input"
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion == 2">
|
||||
<div class="gf-form-inline" ng-if="ctrl.tsdbVersion >= 2">
|
||||
<div class="gf-form">
|
||||
|
||||
<label class="gf-form-label query-keyword width-8">
|
||||
@@ -170,7 +170,7 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-8">
|
||||
Tags
|
||||
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion == 2">
|
||||
<info-popover mode="right-normal" ng-if="ctrl.tsdbVersion >= 2">
|
||||
Please use filters, tags are deprecated in opentsdb 2.2
|
||||
</info-popover>
|
||||
</label>
|
||||
|
@@ -43,7 +43,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
|
||||
}
|
||||
|
||||
function interpolateQueryExpr(value, variable, defaultFormatFn) {
|
||||
this.interpolateQueryExpr = function(value, variable, defaultFormatFn) {
|
||||
// if no multi or include all do not regexEscape
|
||||
if (!variable.multi && !variable.includeAll) {
|
||||
return value;
|
||||
@@ -59,6 +59,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
|
||||
// Called once per panel (graph)
|
||||
this.query = function(options) {
|
||||
var self = this;
|
||||
var start = getPrometheusTime(options.range.from, false);
|
||||
var end = getPrometheusTime(options.range.to, true);
|
||||
|
||||
@@ -73,7 +74,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
activeTargets.push(target);
|
||||
|
||||
var query: any = {};
|
||||
query.expr = templateSrv.replace(target.expr, options.scopedVars, interpolateQueryExpr);
|
||||
query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
|
||||
|
||||
var interval = target.interval || options.interval;
|
||||
var intervalFactor = target.intervalFactor || 1;
|
||||
@@ -99,7 +100,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return this.performTimeSeriesQuery(query, start, end);
|
||||
}, this));
|
||||
|
||||
var self = this;
|
||||
return $q.all(allQueryPromise)
|
||||
.then(function(allResponse) {
|
||||
var result = [];
|
||||
@@ -160,7 +160,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = templateSrv.replace(expr, {}, interpolateQueryExpr);
|
||||
interpolated = templateSrv.replace(expr, {}, this.interpolateQueryExpr);
|
||||
} catch (err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
||||
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
|
||||
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
|
||||
var expr = {
|
||||
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars),
|
||||
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars, this.datasource.interpolateQueryExpr),
|
||||
range_input: rangeDiff + 's',
|
||||
end_input: endTime,
|
||||
step_input: '',
|
||||
|
@@ -282,7 +282,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel['x-axis'],
|
||||
show: panel.xaxis.show,
|
||||
mode: "time",
|
||||
min: min,
|
||||
max: max,
|
||||
@@ -452,12 +452,21 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : '';
|
||||
url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : '';
|
||||
url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
|
||||
url += panel.grid.leftMin !== null ? '&yMin=' + panel.grid.leftMin : '';
|
||||
url += panel.grid.leftMax !== null ? '&yMax=' + panel.grid.leftMax : '';
|
||||
url += panel.grid.rightMin !== null ? '&yMin=' + panel.grid.rightMin : '';
|
||||
url += panel.grid.rightMax !== null ? '&yMax=' + panel.grid.rightMax : '';
|
||||
url += panel['x-axis'] ? '' : '&hideAxes=true';
|
||||
url += panel['y-axis'] ? '' : '&hideYAxis=true';
|
||||
|
||||
if (panel.yaxes && panel.yaxes.length > 0) {
|
||||
var showYaxis = false;
|
||||
for(var i = 0; panel.yaxes.length > i; i++) {
|
||||
if (panel.yaxes[i].show) {
|
||||
url += (panel.yaxes[i].min !== null && panel.yaxes[i].min !== undefined) ? '&yMin=' + panel.yaxes[i].min : '';
|
||||
url += (panel.yaxes[i].max !== null && panel.yaxes[i].max !== undefined) ? '&yMax=' + panel.yaxes[i].max : '';
|
||||
showYaxis = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
url += showYaxis ? '' : '&hideYAxis=true';
|
||||
}
|
||||
|
||||
url += panel.xaxis.show ? '' : '&hideAxes=true';
|
||||
|
||||
switch(panel.yaxes[0].format) {
|
||||
case 'bytes':
|
||||
|
@@ -81,9 +81,9 @@ function ($) {
|
||||
// Stacked series can increase its length on each new stacked serie if null points found,
|
||||
// to speed the index search we begin always on the last found hoverIndex.
|
||||
var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
|
||||
results.push({ value: value, hoverIndex: newhoverIndex });
|
||||
results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label });
|
||||
} else {
|
||||
results.push({ value: value, hoverIndex: hoverIndex });
|
||||
results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,18 @@ function ($) {
|
||||
|
||||
absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);
|
||||
|
||||
// Dynamically reorder the hovercard for the current time point if the
|
||||
// option is enabled.
|
||||
if (panel.tooltip.ordering === 'decreasing') {
|
||||
seriesHoverInfo.sort(function(a, b) {
|
||||
return parseFloat(b.value) - parseFloat(a.value);
|
||||
});
|
||||
} else if (panel.tooltip.ordering === 'increasing') {
|
||||
seriesHoverInfo.sort(function(a, b) {
|
||||
return parseFloat(a.value) - parseFloat(b.value);
|
||||
});
|
||||
}
|
||||
|
||||
for (i = 0; i < seriesHoverInfo.length; i++) {
|
||||
hoverInfo = seriesHoverInfo[i];
|
||||
|
||||
@@ -150,7 +162,7 @@ function ($) {
|
||||
value = series.formatValue(hoverInfo.value);
|
||||
|
||||
seriesHtml += '<div class="graph-tooltip-list-item ' + highlightClass + '"><div class="graph-tooltip-series-name">';
|
||||
seriesHtml += '<i class="fa fa-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
|
||||
seriesHtml += '<i class="fa fa-minus" style="color:' + hoverInfo.color +';"></i> ' + hoverInfo.label + ':</div>';
|
||||
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
|
||||
plot.highlight(i, hoverInfo.hoverIndex);
|
||||
}
|
||||
|
@@ -92,6 +92,7 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
shared: true,
|
||||
ordering: 'alphabetical',
|
||||
msResolution: false,
|
||||
},
|
||||
// time overrides
|
||||
|
@@ -42,23 +42,29 @@
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Misc options</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Null value</label>
|
||||
<label class="gf-form-label width-9">Null value</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input max-width-8" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Renderer</label>
|
||||
<label class="gf-form-label width-9">Renderer</label>
|
||||
<div class="gf-form-select-wrapper max-width-8">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.renderer" ng-options="f for f in ['flot', 'png']" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-7">Tooltip mode</label>
|
||||
<label class="gf-form-label width-9">Tooltip mode</label>
|
||||
<div class="gf-form-select-wrapper max-width-8">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.shared" ng-options="f.value as f.text for f in [{text: 'All series', value: true}, {text: 'Single', value: false}]" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Tooltip ordering<tip>The ordering from top to bottom</tip></label>
|
||||
<div class="gf-form-select-wrapper max-width-8">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.ordering" ng-options="f.value as f.text for f in [{text: 'Alphabetical', value: 'alphabetical'}, {text: 'Increasing', value: 'increasing'}, {text: 'Decreasing', value: 'decreasing'}]" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
|
@@ -204,35 +204,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section" style="margin-bottom: 20px">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
<strong>Value to text mapping</strong>
|
||||
</li>
|
||||
<li class="tight-form-item" ng-repeat-start="map in ctrl.panel.valueMaps">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="ctrl.render()">
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</li>
|
||||
<li ng-repeat-end>
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="input-mini tight-form-input" ng-blur="ctrl.render()">
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="pointer tight-form-item last" ng-click="ctrl.addValueMap();">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
58
public/app/plugins/panel/singlestat/mappings.html
Normal file
58
public/app/plugins/panel/singlestat/mappings.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<div class="editor-row">
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">
|
||||
Type
|
||||
</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.mappingType"
|
||||
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" ng-if="ctrl.panel.mappingType==1">
|
||||
<h5 class="page-heading">Set value mappings</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
|
||||
</span>
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</span>
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.addValueMap();">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add a value mapping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" ng-if="ctrl.panel.mappingType==2">
|
||||
<h5 class="page-heading">Set range mappings</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form" ng-repeat="rangeMap in ctrl.panel.rangeMaps">
|
||||
<span class="gf-form-label">
|
||||
<i class="fa fa-remove pointer" ng-click="ctrl.removeRangeMap(rangeMap)"></i>
|
||||
</span>
|
||||
<span class="gf-form-label">From</span>
|
||||
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<span class="gf-form-label">To</span>
|
||||
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.render()">
|
||||
<span class="gf-form-label">Text</span>
|
||||
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.render()">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.addRangeMap()">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add a range mapping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -35,6 +35,14 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
valueMaps: [
|
||||
{ value: 'null', op: '=', text: 'N/A' }
|
||||
],
|
||||
mappingTypes: [
|
||||
{name: 'value to text', value: 1},
|
||||
{name: 'range to text', value: 2},
|
||||
],
|
||||
rangeMaps: [
|
||||
{ from: 'null', to: 'null', text: 'N/A' }
|
||||
],
|
||||
mappingType: 1,
|
||||
nullPointMode: 'connected',
|
||||
valueName: 'avg',
|
||||
prefixFontSize: '50%',
|
||||
@@ -73,6 +81,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
onInitEditMode() {
|
||||
this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
this.addEditorTab('Options', 'public/app/plugins/panel/singlestat/editor.html', 2);
|
||||
this.addEditorTab('Value Mappings', 'public/app/plugins/panel/singlestat/mappings.html', 3);
|
||||
this.unitFormats = kbn.getUnitFormats();
|
||||
}
|
||||
|
||||
@@ -192,23 +201,45 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
// check value to text mappings
|
||||
for (var i = 0; i < this.panel.valueMaps.length; i++) {
|
||||
var map = this.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
// check value to text mappings if its enabled
|
||||
if (this.panel.mappingType === 1) {
|
||||
for (var i = 0; i < this.panel.valueMaps.length; i++) {
|
||||
var map = this.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === data.valueRounded) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if (this.panel.mappingType === 2) {
|
||||
for (var i = 0; i < this.panel.rangeMaps.length; i++) {
|
||||
var map = this.panel.rangeMaps[i];
|
||||
// special null case
|
||||
if (map.from === 'null' && map.to === 'null') {
|
||||
if (data.value === null || data.value === void 0) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === data.valueRounded) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
// value/number to range mapping
|
||||
var from = parseFloat(map.from);
|
||||
var to = parseFloat(map.to);
|
||||
if (to >= data.valueRounded && from <= data.valueRounded) {
|
||||
data.valueFormated = map.text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +258,16 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
this.panel.valueMaps.push({value: '', op: '=', text: '' });
|
||||
}
|
||||
|
||||
removeRangeMap(rangeMap) {
|
||||
var index = _.indexOf(this.panel.rangeMaps, rangeMap);
|
||||
this.panel.rangeMaps.splice(index, 1);
|
||||
this.render();
|
||||
};
|
||||
|
||||
addRangeMap() {
|
||||
this.panel.rangeMaps.push({from: '', to: '', text: ''});
|
||||
}
|
||||
|
||||
link(scope, elem, attrs, ctrl) {
|
||||
var $location = this.$location;
|
||||
var linkSrv = this.linkSrv;
|
||||
|
@@ -84,4 +84,29 @@ describe('SingleStatCtrl', function() {
|
||||
expect(ctx.data.valueFormated).to.be('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.datapoints = [[41,50]];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
|
||||
});
|
||||
|
||||
it('Should replace value with text OK', function() {
|
||||
expect(ctx.data.valueFormated).to.be('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) {
|
||||
ctx.setup(function() {
|
||||
ctx.datapoints = [[65,75]];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', function() {
|
||||
expect(ctx.data.valueFormated).to.be('NOT OK');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
Reference in New Issue
Block a user