mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into alerting_definitions
This commit is contained in:
commit
1686d86c3b
13
.github/ISSUE_TEMPLATE.md
vendored
13
.github/ISSUE_TEMPLATE.md
vendored
@ -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:
|
Please include some basic information:
|
||||||
- What grafana version are you using?
|
- What Grafana version are you using?
|
||||||
- What datasource are you using?
|
- What datasource are you using?
|
||||||
- What OS are you running grafana on?
|
- What OS are you running grafana on?
|
||||||
- What did you do?
|
- What did you do?
|
||||||
- What was the expected result?
|
- What was the expected result?
|
||||||
- What happenend instead?
|
- 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
|
- 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)
|
- The raw query and response from your data source (check this in chrome dev tools network tab)
|
||||||
|
|
||||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,13 +1,33 @@
|
|||||||
# 3.1.0
|
# 3.1.0 (unreleased)
|
||||||
|
|
||||||
### Enhancements
|
### 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)
|
* **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)
|
* **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)
|
* **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)
|
* **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)
|
* **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)
|
# 3.0.2 Patch release (2016-05-16)
|
||||||
|
|
||||||
|
16
docker/blocks/collectd/Dockerfile
Normal file
16
docker/blocks/collectd/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM ubuntu:xenial
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
RUN apt-get -y update
|
||||||
|
RUN apt-get -y install collectd curl python-pip
|
||||||
|
|
||||||
|
# add a fake mtab for host disk stats
|
||||||
|
ADD etc_mtab /etc/mtab
|
||||||
|
|
||||||
|
ADD collectd.conf.tpl /etc/collectd/collectd.conf.tpl
|
||||||
|
|
||||||
|
RUN pip install envtpl
|
||||||
|
ADD start_container /usr/bin/start_container
|
||||||
|
RUN chmod +x /usr/bin/start_container
|
||||||
|
CMD start_container
|
37
docker/blocks/collectd/README.md
Normal file
37
docker/blocks/collectd/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
collectd-write-graphite
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Basic collectd-based server monitoring. Sends stats to Graphite.
|
||||||
|
|
||||||
|
Collectd metrics:
|
||||||
|
|
||||||
|
* CPU used/free/idle/etc
|
||||||
|
* Free disk (via mounting hosts '/' into container, eg: -v /:/hostfs:ro)
|
||||||
|
* Disk performance
|
||||||
|
* Load average
|
||||||
|
* Memory used/free/etc
|
||||||
|
* Uptime
|
||||||
|
* Network interface
|
||||||
|
* Swap
|
||||||
|
|
||||||
|
Environment variables
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
* `HOST_NAME`
|
||||||
|
- Will be sent to Graphite
|
||||||
|
- Required
|
||||||
|
* `GRAPHITE_HOST`
|
||||||
|
- Graphite IP or hostname
|
||||||
|
- Required
|
||||||
|
* `GRAPHITE_PORT`
|
||||||
|
- Graphite port
|
||||||
|
- Optional, defaults to 2003
|
||||||
|
* `GRAPHITE_PREFIX`
|
||||||
|
- Graphite prefix
|
||||||
|
- Optional, defaults to collectd.
|
||||||
|
* `REPORT_BY_CPU`
|
||||||
|
- Report per-CPU metrics if true, global sum of CPU metrics if false (details: [collectd.conf man page](https://collectd.org/documentation/manpages/collectd.conf.5.shtml#plugin_cpu))
|
||||||
|
- Optional, defaults to false.
|
||||||
|
* `COLLECT_INTERVAL`
|
||||||
|
- Collection interval and thus resolution of metrics
|
||||||
|
- Optional, defaults to 10
|
76
docker/blocks/collectd/collectd.conf.tpl
Normal file
76
docker/blocks/collectd/collectd.conf.tpl
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
Hostname "{{ HOST_NAME }}"
|
||||||
|
|
||||||
|
FQDNLookup false
|
||||||
|
Interval {{ COLLECT_INTERVAL | default("10") }}
|
||||||
|
Timeout 2
|
||||||
|
ReadThreads 5
|
||||||
|
|
||||||
|
LoadPlugin cpu
|
||||||
|
LoadPlugin df
|
||||||
|
LoadPlugin load
|
||||||
|
LoadPlugin memory
|
||||||
|
LoadPlugin disk
|
||||||
|
LoadPlugin interface
|
||||||
|
LoadPlugin uptime
|
||||||
|
LoadPlugin swap
|
||||||
|
LoadPlugin write_graphite
|
||||||
|
|
||||||
|
<Plugin cpu>
|
||||||
|
ReportByCpu {{ REPORT_BY_CPU | default("false") }}
|
||||||
|
</Plugin>
|
||||||
|
|
||||||
|
<Plugin df>
|
||||||
|
# expose host's mounts into container using -v /:/host:ro (location inside container does not matter much)
|
||||||
|
# ignore rootfs; else, the root file-system would appear twice, causing
|
||||||
|
# one of the updates to fail and spam the log
|
||||||
|
FSType rootfs
|
||||||
|
# ignore the usual virtual / temporary file-systems
|
||||||
|
FSType sysfs
|
||||||
|
FSType proc
|
||||||
|
FSType devtmpfs
|
||||||
|
FSType devpts
|
||||||
|
FSType tmpfs
|
||||||
|
FSType fusectl
|
||||||
|
FSType cgroup
|
||||||
|
FSType overlay
|
||||||
|
FSType debugfs
|
||||||
|
FSType pstore
|
||||||
|
FSType securityfs
|
||||||
|
FSType hugetlbfs
|
||||||
|
FSType squashfs
|
||||||
|
FSType mqueue
|
||||||
|
MountPoint "/etc/resolv.conf"
|
||||||
|
MountPoint "/etc/hostname"
|
||||||
|
MountPoint "/etc/hosts"
|
||||||
|
IgnoreSelected true
|
||||||
|
ReportByDevice false
|
||||||
|
ReportReserved true
|
||||||
|
ReportInodes true
|
||||||
|
</Plugin>
|
||||||
|
|
||||||
|
<Plugin "disk">
|
||||||
|
Disk "/^[hs]d[a-z]/"
|
||||||
|
IgnoreSelected false
|
||||||
|
</Plugin>
|
||||||
|
|
||||||
|
|
||||||
|
<Plugin interface>
|
||||||
|
Interface "lo"
|
||||||
|
Interface "/^veth.*/"
|
||||||
|
Interface "/^docker.*/"
|
||||||
|
IgnoreSelected true
|
||||||
|
</Plugin>
|
||||||
|
|
||||||
|
|
||||||
|
<Plugin "write_graphite">
|
||||||
|
<Carbon>
|
||||||
|
Host "{{ GRAPHITE_HOST }}"
|
||||||
|
Port "{{ GRAPHITE_PORT | default("2003") }}"
|
||||||
|
Prefix "{{ GRAPHITE_PREFIX | default("collectd.") }}"
|
||||||
|
EscapeCharacter "_"
|
||||||
|
SeparateInstances true
|
||||||
|
StoreRates true
|
||||||
|
AlwaysAppendDS false
|
||||||
|
</Carbon>
|
||||||
|
</Plugin>
|
||||||
|
|
1
docker/blocks/collectd/etc_mtab
Normal file
1
docker/blocks/collectd/etc_mtab
Normal file
@ -0,0 +1 @@
|
|||||||
|
hostfs /.dockerinit ext4 ro,relatime,user_xattr,barrier=1,data=ordered 0 0
|
11
docker/blocks/collectd/fig
Normal file
11
docker/blocks/collectd/fig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
collectd:
|
||||||
|
build: blocks/collectd
|
||||||
|
environment:
|
||||||
|
HOST_NAME: myserver
|
||||||
|
GRAPHITE_HOST: graphite
|
||||||
|
GRAPHITE_PORT: 2003
|
||||||
|
GRAPHITE_PREFIX: collectd.
|
||||||
|
REPORT_BY_CPU: 'false'
|
||||||
|
COLLECT_INTERVAL: 10
|
||||||
|
links:
|
||||||
|
- graphite
|
5
docker/blocks/collectd/start_container
Normal file
5
docker/blocks/collectd/start_container
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
envtpl /etc/collectd/collectd.conf.tpl
|
||||||
|
|
||||||
|
collectd -f
|
@ -1,7 +1,15 @@
|
|||||||
To build the docs locally, you need to have docker installed. The docs are built using a custom [docker](https://www.docker.com/)
|
# Building The Docs
|
||||||
image and [mkdocs](http://www.mkdocs.org/).
|
|
||||||
|
|
||||||
Build the `grafana/docs-base:latest` image:
|
To build the docs locally, you need to have docker installed. The
|
||||||
|
docs are built using a custom [docker](https://www.docker.com/) image
|
||||||
|
and the [mkdocs](http://www.mkdocs.org/) tool.
|
||||||
|
|
||||||
|
**Prepare the Docker Image**:
|
||||||
|
|
||||||
|
Build the `grafana/docs-base:latest` image. Run these commands in the
|
||||||
|
same directory this file is in. **Note** that you may require ``sudo``
|
||||||
|
when running ``make docs-build`` depending on how your system's docker
|
||||||
|
service is configured):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/grafana/docs-base
|
$ git clone https://github.com/grafana/docs-base
|
||||||
@ -9,10 +17,45 @@ $ cd docs-base
|
|||||||
$ make docs-build
|
$ make docs-build
|
||||||
```
|
```
|
||||||
|
|
||||||
To build the docs:
|
**Build the Documentation**:
|
||||||
|
|
||||||
|
Now that the docker image has been prepared we can build the
|
||||||
|
docs. Switch your working directory back to the directory this file
|
||||||
|
(README.md) is in and run (possibly with ``sudo``):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd docs
|
|
||||||
$ make docs
|
$ make docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This command will not return control of the shell to the user. Instead
|
||||||
|
the command is now running a new docker container built from the image
|
||||||
|
we created in the previous step.
|
||||||
|
|
||||||
Open [localhost:8180](http://localhost:8180) to view the docs.
|
Open [localhost:8180](http://localhost:8180) to view the docs.
|
||||||
|
|
||||||
|
**Note** that after running ``make docs`` you may notice a message
|
||||||
|
like this in the console output
|
||||||
|
|
||||||
|
> Running at: http://0.0.0.0:8000/
|
||||||
|
|
||||||
|
This is misleading. That is **not** the port the documentation is
|
||||||
|
served from. You must browse to port **8180** to view the new
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
|
||||||
|
# Adding a New Page
|
||||||
|
|
||||||
|
Adding a new page requires updating the ``mkdocs.yml`` file which is
|
||||||
|
located in this directory.
|
||||||
|
|
||||||
|
For example, if you are adding documentation for a new HTTP API called
|
||||||
|
``preferences`` you would:
|
||||||
|
|
||||||
|
1. Create the file ``docs/sources/http_api/preferences.md``
|
||||||
|
1. Add a reference to it in ``docs/sources/http_api/overview.md``
|
||||||
|
1. Update the list under the **pages** key in the ``docs/mkdocs.yml`` file with a reference to your new page:
|
||||||
|
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- ['http_api/preferences.md', 'API', 'Preferences API']
|
||||||
|
```
|
||||||
|
@ -84,6 +84,7 @@ pages:
|
|||||||
- ['http_api/user.md', 'API', 'User API']
|
- ['http_api/user.md', 'API', 'User API']
|
||||||
- ['http_api/admin.md', 'API', 'Admin API']
|
- ['http_api/admin.md', 'API', 'Admin API']
|
||||||
- ['http_api/snapshot.md', 'API', 'Snapshot API']
|
- ['http_api/snapshot.md', 'API', 'Snapshot API']
|
||||||
|
- ['http_api/preferences.md', 'API', 'Preferences API']
|
||||||
- ['http_api/other.md', 'API', 'Other API']
|
- ['http_api/other.md', 'API', 'Other API']
|
||||||
|
|
||||||
- ['plugins/index.md', 'Plugins', 'Overview']
|
- ['plugins/index.md', 'Plugins', 'Overview']
|
||||||
|
@ -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
|
The role new users will be assigned for the main organization (if the
|
||||||
above setting is set to true). Defaults to `Viewer`, other valid
|
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>
|
<hr>
|
||||||
|
|
||||||
|
@ -10,13 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide
|
|||||||
|
|
||||||
Description | Download
|
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
|
## 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 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
|
## APT Repository
|
||||||
|
|
||||||
|
@ -10,24 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
|
|||||||
|
|
||||||
Description | Download
|
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
|
## Install Stable Release from package file
|
||||||
|
|
||||||
You can install Grafana using Yum directly.
|
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`.
|
Or install manually using `rpm`.
|
||||||
|
|
||||||
#### On CentOS / Fedora / Redhat:
|
#### On CentOS / Fedora / Redhat:
|
||||||
|
|
||||||
$ sudo yum install initscripts fontconfig
|
$ 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:
|
#### 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
|
## Install via YUM Repository
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
|
|||||||
|
|
||||||
Description | Download
|
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
|
## Configure
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"stable": "3.0.2",
|
"stable": "3.0.4",
|
||||||
"testing": "3.0.2"
|
"testing": "3.0.4"
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"grunt-angular-templates": "^0.5.5",
|
"grunt-angular-templates": "^0.5.5",
|
||||||
"grunt-cli": "~0.1.13",
|
"grunt-cli": "~0.1.13",
|
||||||
"grunt-contrib-clean": "~0.7.0",
|
"grunt-contrib-clean": "~0.7.0",
|
||||||
"grunt-contrib-compress": "~0.14.0",
|
"grunt-contrib-compress": "^1.3.0",
|
||||||
"grunt-contrib-concat": "^0.5.1",
|
"grunt-contrib-concat": "^0.5.1",
|
||||||
"grunt-contrib-copy": "~0.8.2",
|
"grunt-contrib-copy": "~0.8.2",
|
||||||
"grunt-contrib-cssmin": "~0.14.0",
|
"grunt-contrib-cssmin": "~0.14.0",
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
deb_ver=3.0.1
|
deb_ver=3.0.4-1464167696
|
||||||
rpm_ver=3.0.1-1
|
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/testing/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/wheezy grafana_${deb_ver}_amd64.deb
|
||||||
|
|
||||||
#package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
|
wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
|
||||||
#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
|
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/7 grafana-${rpm_ver}.x86_64.rpm
|
||||||
package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm
|
package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm
|
||||||
|
@ -117,6 +117,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Get("/:id", wrap(GetUserById))
|
r.Get("/:id", wrap(GetUserById))
|
||||||
r.Get("/:id/orgs", wrap(GetUserOrgList))
|
r.Get("/:id/orgs", wrap(GetUserOrgList))
|
||||||
r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
|
||||||
|
r.Post("/:id/using/:orgId", wrap(UpdateUserActiveOrg))
|
||||||
}, reqGrafanaAdmin)
|
}, reqGrafanaAdmin)
|
||||||
|
|
||||||
// org information available to all users.
|
// org information available to all users.
|
||||||
@ -211,7 +212,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
|
r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
|
||||||
r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
|
r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
|
||||||
r.Get("/file/:file", GetDashboardFromJsonFile)
|
r.Get("/file/:file", GetDashboardFromJsonFile)
|
||||||
r.Get("/home", GetHomeDashboard)
|
r.Get("/home", wrap(GetHomeDashboard))
|
||||||
r.Get("/tags", GetDashboardTags)
|
r.Get("/tags", GetDashboardTags)
|
||||||
r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
|
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
|
var credentialCacheLock sync.RWMutex
|
||||||
|
|
||||||
func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials {
|
func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials {
|
||||||
|
cacheKey := profile + ":" + assumeRoleArn
|
||||||
credentialCacheLock.RLock()
|
credentialCacheLock.RLock()
|
||||||
if _, ok := awsCredentialCache[profile]; ok {
|
if _, ok := awsCredentialCache[cacheKey]; ok {
|
||||||
if awsCredentialCache[profile].expiration != nil &&
|
if awsCredentialCache[cacheKey].expiration != nil &&
|
||||||
(*awsCredentialCache[profile].expiration).After(time.Now().UTC()) {
|
(*awsCredentialCache[cacheKey].expiration).After(time.Now().UTC()) {
|
||||||
result := awsCredentialCache[profile].credential
|
result := awsCredentialCache[cacheKey].credential
|
||||||
credentialCacheLock.RUnlock()
|
credentialCacheLock.RUnlock()
|
||||||
return result
|
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},
|
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
|
||||||
})
|
})
|
||||||
credentialCacheLock.Lock()
|
credentialCacheLock.Lock()
|
||||||
awsCredentialCache[profile] = cache{
|
awsCredentialCache[cacheKey] = cache{
|
||||||
credential: creds,
|
credential: creds,
|
||||||
expiration: expiration,
|
expiration: expiration,
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,15 @@ func init() {
|
|||||||
"AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps"},
|
"AWS/EBS": {"VolumeReadBytes", "VolumeWriteBytes", "VolumeReadOps", "VolumeWriteOps", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeIdleTime", "VolumeQueueLength", "VolumeThroughputPercentage", "VolumeConsumedReadWriteOps"},
|
||||||
"AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
|
"AWS/EC2": {"CPUCreditUsage", "CPUCreditBalance", "CPUUtilization", "DiskReadOps", "DiskWriteOps", "DiskReadBytes", "DiskWriteBytes", "NetworkIn", "NetworkOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
|
||||||
"AWS/ELB": {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount"},
|
"AWS/ELB": {"HealthyHostCount", "UnHealthyHostCount", "RequestCount", "Latency", "HTTPCode_ELB_4XX", "HTTPCode_ELB_5XX", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX", "HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount"},
|
||||||
|
"AWS/ElasticBeanstalk": {
|
||||||
|
"EnvironmentHealth",
|
||||||
|
"ApplicationLatencyP10", "ApplicationLatencyP50", "ApplicationLatencyP75", "ApplicationLatencyP85", "ApplicationLatencyP90", "ApplicationLatencyP95", "ApplicationLatencyP99", "ApplicationLatencyP99.9",
|
||||||
|
"ApplicationRequests2xx", "ApplicationRequests3xx", "ApplicationRequests4xx", "ApplicationRequests5xx", "ApplicationRequestsTotal",
|
||||||
|
"CPUIdle", "CPUIowait", "CPUIrq", "CPUNice", "CPUSoftirq", "CPUSystem", "CPUUser",
|
||||||
|
"InstanceHealth", "InstancesDegraded", "InstancesInfo", "InstancesNoData", "InstancesOk", "InstancesPending", "InstancesSevere", "InstancesUnknown", "InstancesWarning",
|
||||||
|
"LoadAverage1min", "LoadAverage5min",
|
||||||
|
"RootFilesystemUtil",
|
||||||
|
},
|
||||||
"AWS/ElasticMapReduce": {"IsIdle", "JobsRunning", "JobsFailed",
|
"AWS/ElasticMapReduce": {"IsIdle", "JobsRunning", "JobsFailed",
|
||||||
"MapTasksRunning", "MapTasksRemaining", "MapSlotsOpen", "RemainingMapTasksPerSlot", "ReduceTasksRunning", "ReduceTasksRemaining", "ReduceSlotsOpen",
|
"MapTasksRunning", "MapTasksRemaining", "MapSlotsOpen", "RemainingMapTasksPerSlot", "ReduceTasksRunning", "ReduceTasksRemaining", "ReduceSlotsOpen",
|
||||||
"CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "TaskNodesRunning", "TaskNodesPending", "LiveTaskTrackers",
|
"CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "TaskNodesRunning", "TaskNodesPending", "LiveTaskTrackers",
|
||||||
@ -85,6 +94,7 @@ func init() {
|
|||||||
"AWS/EBS": {"VolumeId"},
|
"AWS/EBS": {"VolumeId"},
|
||||||
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
|
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
|
||||||
"AWS/ELB": {"LoadBalancerName", "AvailabilityZone"},
|
"AWS/ELB": {"LoadBalancerName", "AvailabilityZone"},
|
||||||
|
"AWS/ElasticBeanstalk": {"EnvironmentName", "InstanceId"},
|
||||||
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
||||||
"AWS/ES": {"ClientId", "DomainName"},
|
"AWS/ES": {"ClientId", "DomainName"},
|
||||||
"AWS/Events": {"RuleName"},
|
"AWS/Events": {"RuleName"},
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
@ -174,30 +175,27 @@ func canEditDashboard(role m.RoleType) bool {
|
|||||||
return role == m.ROLE_ADMIN || role == m.ROLE_EDITOR || role == m.ROLE_READ_ONLY_EDITOR
|
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}
|
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
|
||||||
if err := bus.Dispatch(&prefsQuery); err != nil {
|
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 {
|
if prefsQuery.Result.HomeDashboardId != 0 {
|
||||||
slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
|
slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId}
|
||||||
err := bus.Dispatch(&slugQuery)
|
err := bus.Dispatch(&slugQuery)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
c.JsonApiErr(500, "Failed to get slug from database", err)
|
dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result}
|
||||||
return
|
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")
|
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JsonApiErr(500, "Failed to load home dashboard", err)
|
return ApiError(500, "Failed to load home dashboard", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dash := dtos.DashboardFullWithMeta{}
|
dash := dtos.DashboardFullWithMeta{}
|
||||||
@ -205,11 +203,10 @@ func GetHomeDashboard(c *middleware.Context) {
|
|||||||
dash.Meta.CanEdit = canEditDashboard(c.OrgRole)
|
dash.Meta.CanEdit = canEditDashboard(c.OrgRole)
|
||||||
jsonParser := json.NewDecoder(file)
|
jsonParser := json.NewDecoder(file)
|
||||||
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
|
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
|
||||||
c.JsonApiErr(500, "Failed to load home dashboard", err)
|
return ApiError(500, "Failed to load home dashboard", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, &dash)
|
return Json(200, &dash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboardFromJsonFile(c *middleware.Context) {
|
func GetDashboardFromJsonFile(c *middleware.Context) {
|
||||||
|
@ -55,6 +55,13 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
|
|||||||
req.Header.Add("Authorization", util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword))
|
req.Header.Add("Authorization", util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dsAuth := req.Header.Get("X-DS-Authorization")
|
||||||
|
if len(dsAuth) > 0 {
|
||||||
|
req.Header.Del("X-DS-Authorization")
|
||||||
|
req.Header.Del("Authorization")
|
||||||
|
req.Header.Add("Authorization", dsAuth)
|
||||||
|
}
|
||||||
|
|
||||||
// clear cookie headers
|
// clear cookie headers
|
||||||
req.Header.Del("Cookie")
|
req.Header.Del("Cookie")
|
||||||
req.Header.Del("Set-Cookie")
|
req.Header.Del("Set-Cookie")
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package dtos
|
package dtos
|
||||||
|
|
||||||
type IndexViewData struct {
|
type IndexViewData struct {
|
||||||
User *CurrentUser
|
User *CurrentUser
|
||||||
Settings map[string]interface{}
|
Settings map[string]interface{}
|
||||||
AppUrl string
|
AppUrl string
|
||||||
AppSubUrl string
|
AppSubUrl string
|
||||||
GoogleAnalyticsId string
|
GoogleAnalyticsId string
|
||||||
GoogleTagManagerId string
|
GoogleTagManagerId string
|
||||||
MainNavLinks []*NavLink
|
MainNavLinks []*NavLink
|
||||||
|
BuildVersion string
|
||||||
|
BuildCommit string
|
||||||
|
NewGrafanaVersionExists bool
|
||||||
|
NewGrafanaVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginCss struct {
|
type PluginCss struct {
|
||||||
|
@ -36,11 +36,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
|||||||
LightTheme: prefs.Theme == "light",
|
LightTheme: prefs.Theme == "light",
|
||||||
Timezone: prefs.Timezone,
|
Timezone: prefs.Timezone,
|
||||||
},
|
},
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
AppUrl: setting.AppUrl,
|
AppUrl: setting.AppUrl,
|
||||||
AppSubUrl: setting.AppSubUrl,
|
AppSubUrl: setting.AppSubUrl,
|
||||||
GoogleAnalyticsId: setting.GoogleAnalyticsId,
|
GoogleAnalyticsId: setting.GoogleAnalyticsId,
|
||||||
GoogleTagManagerId: setting.GoogleTagManagerId,
|
GoogleTagManagerId: setting.GoogleTagManagerId,
|
||||||
|
BuildVersion: setting.BuildVersion,
|
||||||
|
BuildCommit: setting.BuildCommit,
|
||||||
|
NewGrafanaVersion: plugins.GrafanaLatestVersion,
|
||||||
|
NewGrafanaVersionExists: plugins.GrafanaHasUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.DisableGravatar {
|
if setting.DisableGravatar {
|
||||||
|
@ -88,7 +88,7 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range headers {
|
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])
|
req.Header.Set(key, value[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,24 @@ func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) Response {
|
|||||||
return handleUpdateUser(cmd)
|
return handleUpdateUser(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//POST /api/users/:id/using/:orgId
|
||||||
|
func UpdateUserActiveOrg(c *middleware.Context) Response {
|
||||||
|
userId := c.ParamsInt64(":id")
|
||||||
|
orgId := c.ParamsInt64(":orgId")
|
||||||
|
|
||||||
|
if !validateUsingOrg(userId, orgId) {
|
||||||
|
return ApiError(401, "Not a valid organization", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := m.SetUsingOrgCommand{UserId: userId, OrgId: orgId}
|
||||||
|
|
||||||
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
return ApiError(500, "Failed change active organization", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiSuccess("Active organization changed")
|
||||||
|
}
|
||||||
|
|
||||||
func handleUpdateUser(cmd m.UpdateUserCommand) Response {
|
func handleUpdateUser(cmd m.UpdateUserCommand) Response {
|
||||||
if len(cmd.Login) == 0 {
|
if len(cmd.Login) == 0 {
|
||||||
cmd.Login = cmd.Email
|
cmd.Login = cmd.Email
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/social"
|
"github.com/grafana/grafana/pkg/social"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "3.0.0-beta4"
|
var version = "3.1.0"
|
||||||
var commit = "NA"
|
var commit = "NA"
|
||||||
var buildstamp string
|
var buildstamp string
|
||||||
var build_date 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() {
|
$scope.submit = function() {
|
||||||
if ($scope.loginMode) {
|
if ($scope.loginMode) {
|
||||||
$scope.login();
|
$scope.login();
|
||||||
|
@ -209,7 +209,9 @@ function (_, $, coreModule) {
|
|||||||
// needs to call this after digest so
|
// needs to call this after digest so
|
||||||
// property is synced with outerscope
|
// property is synced with outerscope
|
||||||
$scope.$$postDigest(function() {
|
$scope.$$postDigest(function() {
|
||||||
$scope.onChange();
|
$scope.$apply(function() {
|
||||||
|
$scope.onChange();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
|||||||
registerPluginComponent(scope, elem, attrs, componentInfo);
|
registerPluginComponent(scope, elem, attrs, componentInfo);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
$rootScope.appEvent('alert-error', ['Plugin Error', err.message || err]);
|
$rootScope.appEvent('alert-error', ['Plugin Error', err.message || err]);
|
||||||
console.log('Plugin componnet error', err);
|
console.log('Plugin component error', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -96,6 +96,11 @@ function (angular, _, coreModule, config) {
|
|||||||
var requestIsLocal = options.url.indexOf('/') === 0;
|
var requestIsLocal = options.url.indexOf('/') === 0;
|
||||||
var firstAttempt = options.retry === 0;
|
var firstAttempt = options.retry === 0;
|
||||||
|
|
||||||
|
if (requestIsLocal && options.headers && options.headers.Authorization) {
|
||||||
|
options.headers['X-DS-Authorization'] = options.headers.Authorization;
|
||||||
|
delete options.headers.Authorization;
|
||||||
|
}
|
||||||
|
|
||||||
return $http(options).then(null, function(err) {
|
return $http(options).then(null, function(err) {
|
||||||
// handle unauthorized for backend requests
|
// handle unauthorized for backend requests
|
||||||
if (requestIsLocal && firstAttempt && err.status === 401) {
|
if (requestIsLocal && firstAttempt && err.status === 401) {
|
||||||
|
@ -66,14 +66,17 @@ function (angular, _, coreModule, config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.getAnnotationSources = function() {
|
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) {
|
if (value.meta && value.meta.annotations) {
|
||||||
memo.push(value);
|
sources.push(value);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return memo;
|
return sources;
|
||||||
}, []);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getMetricSources = function(options) {
|
this.getMetricSources = function(options) {
|
||||||
@ -90,24 +93,7 @@ function (angular, _, coreModule, config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!options || !options.skipVariables) {
|
if (!options || !options.skipVariables) {
|
||||||
// look for data source variables
|
this.addDataSourceVariables(metricSources);
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metricSources.sort(function(a, b) {
|
metricSources.sort(function(a, b) {
|
||||||
@ -123,6 +109,27 @@ function (angular, _, coreModule, config) {
|
|||||||
return metricSources;
|
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();
|
this.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -173,8 +173,8 @@ export default class TimeSeries {
|
|||||||
|
|
||||||
isMsResolutionNeeded() {
|
isMsResolutionNeeded() {
|
||||||
for (var i = 0; i < this.datapoints.length; i++) {
|
for (var i = 0; i < this.datapoints.length; i++) {
|
||||||
if (this.datapoints[i][0] !== null) {
|
if (this.datapoints[i][1] !== null) {
|
||||||
var timestamp = this.datapoints[i][0].toString();
|
var timestamp = this.datapoints[i][1].toString();
|
||||||
if (timestamp.length === 13 && (timestamp % 1000) !== 0) {
|
if (timestamp.length === 13 && (timestamp % 1000) !== 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,21 @@ function($, _) {
|
|||||||
|
|
||||||
kbn.round_interval = function(interval) {
|
kbn.round_interval = function(interval) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
// 0.3s
|
// 0.015s
|
||||||
case (interval <= 300):
|
case (interval <= 15):
|
||||||
return 100; // 0.1s
|
return 10; // 0.01s
|
||||||
|
// 0.035s
|
||||||
|
case (interval <= 35):
|
||||||
|
return 20; // 0.02s
|
||||||
|
// 0.075s
|
||||||
|
case (interval <= 75):
|
||||||
|
return 50; // 0.05s
|
||||||
|
// 0.15s
|
||||||
|
case (interval <= 150):
|
||||||
|
return 100; // 0.1s
|
||||||
|
// 0.35s
|
||||||
|
case (interval <= 350):
|
||||||
|
return 200; // 0.2s
|
||||||
// 0.75s
|
// 0.75s
|
||||||
case (interval <= 750):
|
case (interval <= 750):
|
||||||
return 500; // 0.5s
|
return 500; // 0.5s
|
||||||
@ -133,7 +145,7 @@ function($, _) {
|
|||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
kbn.interval_regex = /(\d+(?:\.\d+)?)([Mwdhmsy])/;
|
kbn.interval_regex = /(\d+(?:\.\d+)?)(ms|[Mwdhmsy])/;
|
||||||
|
|
||||||
// histogram & trends
|
// histogram & trends
|
||||||
kbn.intervals_in_seconds = {
|
kbn.intervals_in_seconds = {
|
||||||
@ -143,7 +155,8 @@ function($, _) {
|
|||||||
d: 86400,
|
d: 86400,
|
||||||
h: 3600,
|
h: 3600,
|
||||||
m: 60,
|
m: 60,
|
||||||
s: 1
|
s: 1,
|
||||||
|
ms: 0.001
|
||||||
};
|
};
|
||||||
|
|
||||||
kbn.calculateInterval = function(range, resolution, userInterval) {
|
kbn.calculateInterval = function(range, resolution, userInterval) {
|
||||||
|
@ -30,7 +30,7 @@ function (angular, _, $) {
|
|||||||
$scope.datasourceChanged = function() {
|
$scope.datasourceChanged = function() {
|
||||||
return datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) {
|
return datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) {
|
||||||
$scope.currentDatasource = 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() {
|
module.directive('panelWidth', function() {
|
||||||
|
|
||||||
return function(scope, element) {
|
return function(scope, element) {
|
||||||
|
var fullscreen = false;
|
||||||
|
|
||||||
function updateWidth() {
|
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) {
|
scope.onAppEvent('panel-fullscreen-enter', function(evt, info) {
|
||||||
|
fullscreen = true;
|
||||||
|
|
||||||
if (scope.panel.id !== info.panelId) {
|
if (scope.panel.id !== info.panelId) {
|
||||||
element.hide();
|
element.hide();
|
||||||
} else {
|
} else {
|
||||||
@ -156,14 +163,20 @@ function (angular, _, config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
scope.onAppEvent('panel-fullscreen-exit', function(evt, info) {
|
scope.onAppEvent('panel-fullscreen-exit', function(evt, info) {
|
||||||
|
fullscreen = false;
|
||||||
|
|
||||||
if (scope.panel.id !== info.panelId) {
|
if (scope.panel.id !== info.panelId) {
|
||||||
element.show();
|
element.show();
|
||||||
} else {
|
|
||||||
updateWidth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateWidth();
|
||||||
});
|
});
|
||||||
|
|
||||||
scope.$watch('panel.span', updateWidth);
|
scope.$watch('panel.span', updateWidth);
|
||||||
|
|
||||||
|
if (fullscreen) {
|
||||||
|
element.hide();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,12 +70,12 @@ function (angular, _, require, config) {
|
|||||||
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
|
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
|
||||||
|
|
||||||
var soloUrl = $scope.shareUrl;
|
var soloUrl = $scope.shareUrl;
|
||||||
soloUrl = soloUrl.replace('/dashboard/', '/dashboard-solo/');
|
soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
|
||||||
soloUrl = soloUrl.replace("&fullscreen", "");
|
soloUrl = soloUrl.replace("&fullscreen", "");
|
||||||
|
|
||||||
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
|
$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 += '&width=1000';
|
||||||
$scope.imageUrl += '&height=500';
|
$scope.imageUrl += '&height=500';
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
///<reference path="../../../headers/common.d.ts" />
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export class SubmenuCtrl {
|
export class SubmenuCtrl {
|
||||||
annotations: any;
|
annotations: any;
|
||||||
@ -8,7 +9,11 @@ export class SubmenuCtrl {
|
|||||||
dashboard: any;
|
dashboard: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $rootScope, private templateValuesSrv, private dynamicDashboardSrv) {
|
constructor(private $rootScope,
|
||||||
|
private templateValuesSrv,
|
||||||
|
private templateSrv,
|
||||||
|
private dynamicDashboardSrv,
|
||||||
|
private $location) {
|
||||||
this.annotations = this.dashboard.templating.list;
|
this.annotations = this.dashboard.templating.list;
|
||||||
this.variables = this.dashboard.templating.list;
|
this.variables = this.dashboard.templating.list;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ function (angular, _, $) {
|
|||||||
|
|
||||||
var module = angular.module('grafana.services');
|
var module = angular.module('grafana.services');
|
||||||
|
|
||||||
module.factory('dashboardViewStateSrv', function($location, $timeout) {
|
module.factory('dashboardViewStateSrv', function($location, $timeout, templateSrv, contextSrv, timeSrv) {
|
||||||
|
|
||||||
// represents the transient view state
|
// represents the transient view state
|
||||||
// like fullscreen panel & edit
|
// like fullscreen panel & edit
|
||||||
@ -25,6 +25,19 @@ function (angular, _, $) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// update url on time range change
|
||||||
|
$scope.onAppEvent('time-range-changed', function() {
|
||||||
|
var urlParams = $location.search();
|
||||||
|
var urlRange = timeSrv.timeRangeForUrl();
|
||||||
|
urlParams.from = urlRange.from;
|
||||||
|
urlParams.to = urlRange.to;
|
||||||
|
$location.search(urlParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.onAppEvent('template-variable-value-updated', function() {
|
||||||
|
self.updateUrlParamsWithCurrentVariables();
|
||||||
|
});
|
||||||
|
|
||||||
$scope.onAppEvent('$routeUpdate', function() {
|
$scope.onAppEvent('$routeUpdate', function() {
|
||||||
var urlState = self.getQueryStringState();
|
var urlState = self.getQueryStringState();
|
||||||
if (self.needsSync(urlState)) {
|
if (self.needsSync(urlState)) {
|
||||||
@ -40,10 +53,26 @@ function (angular, _, $) {
|
|||||||
self.registerPanel(payload.scope);
|
self.registerPanel(payload.scope);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.update(this.getQueryStringState(), true);
|
this.update(this.getQueryStringState());
|
||||||
this.expandRowForPanel();
|
this.expandRowForPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DashboardViewState.prototype.updateUrlParamsWithCurrentVariables = function() {
|
||||||
|
// update url
|
||||||
|
var params = $location.search();
|
||||||
|
// remove variable params
|
||||||
|
_.each(params, function(value, key) {
|
||||||
|
if (key.indexOf('var-') === 0) {
|
||||||
|
delete params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// add new values
|
||||||
|
templateSrv.fillVariableValuesForUrl(params);
|
||||||
|
// update url
|
||||||
|
$location.search(params);
|
||||||
|
};
|
||||||
|
|
||||||
DashboardViewState.prototype.expandRowForPanel = function() {
|
DashboardViewState.prototype.expandRowForPanel = function() {
|
||||||
if (!this.state.panelId) { return; }
|
if (!this.state.panelId) { return; }
|
||||||
|
|
||||||
@ -63,6 +92,7 @@ function (angular, _, $) {
|
|||||||
state.fullscreen = state.fullscreen ? true : null;
|
state.fullscreen = state.fullscreen ? true : null;
|
||||||
state.edit = (state.edit === "true" || state.edit === true) || null;
|
state.edit = (state.edit === "true" || state.edit === true) || null;
|
||||||
state.editview = state.editview || null;
|
state.editview = state.editview || null;
|
||||||
|
state.org = contextSrv.user.orgId;
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,10 +100,11 @@ function (angular, _, $) {
|
|||||||
var urlState = _.clone(this.state);
|
var urlState = _.clone(this.state);
|
||||||
urlState.fullscreen = this.state.fullscreen ? true : null;
|
urlState.fullscreen = this.state.fullscreen ? true : null;
|
||||||
urlState.edit = this.state.edit ? true : null;
|
urlState.edit = this.state.edit ? true : null;
|
||||||
|
urlState.org = contextSrv.user.orgId;
|
||||||
return urlState;
|
return urlState;
|
||||||
};
|
};
|
||||||
|
|
||||||
DashboardViewState.prototype.update = function(state, skipUrlSync) {
|
DashboardViewState.prototype.update = function(state) {
|
||||||
_.extend(this.state, state);
|
_.extend(this.state, state);
|
||||||
this.dashboard.meta.fullscreen = this.state.fullscreen;
|
this.dashboard.meta.fullscreen = this.state.fullscreen;
|
||||||
|
|
||||||
@ -83,10 +114,7 @@ function (angular, _, $) {
|
|||||||
this.state.edit = null;
|
this.state.edit = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipUrlSync) {
|
$location.search(this.serializeToUrl());
|
||||||
$location.search(this.serializeToUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.syncState();
|
this.syncState();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import $ from 'jquery';
|
|||||||
const TITLE_HEIGHT = 25;
|
const TITLE_HEIGHT = 25;
|
||||||
const EMPTY_TITLE_HEIGHT = 9;
|
const EMPTY_TITLE_HEIGHT = 9;
|
||||||
const PANEL_PADDING = 5;
|
const PANEL_PADDING = 5;
|
||||||
|
const PANEL_BORDER = 2;
|
||||||
|
|
||||||
import {Emitter} from 'app/core/core';
|
import {Emitter} from 'app/core/core';
|
||||||
|
|
||||||
@ -158,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?) {
|
render(payload?) {
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
<div class="main">
|
<div class="panel nospace" ng-if="panel" style="width: 100%">
|
||||||
<div class="row-fluid">
|
<plugin-component type="panel">
|
||||||
<div class="span12">
|
</plugin-component>
|
||||||
<div class="panel nospace" ng-if="panel" style="width: 100%">
|
|
||||||
<plugin-component type="panel">
|
|
||||||
</plugin-component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
@ -17,6 +17,10 @@ function (angular, $) {
|
|||||||
var params = $location.search();
|
var params = $location.search();
|
||||||
panelId = parseInt(params.panelId);
|
panelId = parseInt(params.panelId);
|
||||||
|
|
||||||
|
// add fullscreen param;
|
||||||
|
params.fullscreen = true;
|
||||||
|
$location.search(params);
|
||||||
|
|
||||||
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug).then(function(result) {
|
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug).then(function(result) {
|
||||||
$scope.initDashboard(result, $scope);
|
$scope.initDashboard(result, $scope);
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,7 @@ function (angular, _) {
|
|||||||
{value: "interval", text: "Interval"},
|
{value: "interval", text: "Interval"},
|
||||||
{value: "datasource", text: "Data source"},
|
{value: "datasource", text: "Data source"},
|
||||||
{value: "custom", text: "Custom"},
|
{value: "custom", text: "Custom"},
|
||||||
|
{value: "constant", text: "Constant"},
|
||||||
];
|
];
|
||||||
|
|
||||||
$scope.refreshOptions = [
|
$scope.refreshOptions = [
|
||||||
@ -141,15 +142,34 @@ function (angular, _) {
|
|||||||
$scope.current = angular.copy(replacementDefaults);
|
$scope.current = angular.copy(replacementDefaults);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.showSelectionOptions = function() {
|
||||||
|
if ($scope.current) {
|
||||||
|
if ($scope.current.type === 'query') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($scope.current.type === 'custom') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.typeChanged = function () {
|
$scope.typeChanged = function () {
|
||||||
if ($scope.current.type === 'interval') {
|
if ($scope.current.type === 'interval') {
|
||||||
$scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d';
|
$scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d';
|
||||||
|
$scope.current.refresh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($scope.current.type === 'query') {
|
if ($scope.current.type === 'query') {
|
||||||
$scope.current.query = '';
|
$scope.current.query = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($scope.current.type === 'constant') {
|
||||||
|
$scope.current.query = '';
|
||||||
|
$scope.current.refresh = 0;
|
||||||
|
$scope.current.hide = 2;
|
||||||
|
}
|
||||||
|
|
||||||
if ($scope.current.type === 'datasource') {
|
if ($scope.current.type === 'datasource') {
|
||||||
$scope.current.query = $scope.datasourceTypes[0].value;
|
$scope.current.query = $scope.datasourceTypes[0].value;
|
||||||
$scope.current.regex = '';
|
$scope.current.regex = '';
|
||||||
|
@ -152,6 +152,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="current.type === 'constant'" class="gf-form-group">
|
||||||
|
<h5 class="section-heading">Constant options</h5>
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label">Value</span>
|
||||||
|
<input type="text" class="gf-form-input" ng-model='current.query' ng-blur="runQuery()" placeholder="your metric prefix"></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ng-show="current.type === 'query'" class="gf-form-group">
|
<div ng-show="current.type === 'query'" class="gf-form-group">
|
||||||
<h5 class="section-heading">Query Options</h5>
|
<h5 class="section-heading">Query Options</h5>
|
||||||
|
|
||||||
@ -214,7 +222,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section gf-form-group" ng-hide="current.type === 'datasource'">
|
<div class="section gf-form-group" ng-show="showSelectionOptions()">
|
||||||
<h5 class="section-heading">Selection Options</h5>
|
<h5 class="section-heading">Selection Options</h5>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<gf-form-switch class="gf-form"
|
<gf-form-switch class="gf-form"
|
||||||
|
@ -42,6 +42,16 @@ function (angular, _) {
|
|||||||
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1");
|
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) {
|
this.formatValue = function(value, format, variable) {
|
||||||
// for some scopedVars there is no variable
|
// for some scopedVars there is no variable
|
||||||
variable = variable || {};
|
variable = variable || {};
|
||||||
@ -60,13 +70,7 @@ function (angular, _) {
|
|||||||
return '(' + escapedValues.join('|') + ')';
|
return '(' + escapedValues.join('|') + ')';
|
||||||
}
|
}
|
||||||
case "lucene": {
|
case "lucene": {
|
||||||
if (typeof value === 'string') {
|
return this.luceneFormat(value, format, variable);
|
||||||
return luceneEscape(value);
|
|
||||||
}
|
|
||||||
var quotedValues = _.map(value, function(val) {
|
|
||||||
return '\"' + luceneEscape(val) + '\"';
|
|
||||||
});
|
|
||||||
return '(' + quotedValues.join(' OR ') + ')';
|
|
||||||
}
|
}
|
||||||
case "pipe": {
|
case "pipe": {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
@ -97,8 +101,11 @@ function (angular, _) {
|
|||||||
if (!str) {
|
if (!str) {
|
||||||
return false;
|
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) {
|
this.highlightVariablesAsHtml = function(str) {
|
||||||
|
@ -79,7 +79,6 @@ function (angular, _, kbn) {
|
|||||||
else if (variable.refresh === 1 || variable.refresh === 2) {
|
else if (variable.refresh === 1 || variable.refresh === 2) {
|
||||||
return self.updateOptions(variable).then(function() {
|
return self.updateOptions(variable).then(function() {
|
||||||
if (_.isEmpty(variable.current) && variable.options.length) {
|
if (_.isEmpty(variable.current) && variable.options.length) {
|
||||||
console.log("setting current for %s", variable.name);
|
|
||||||
self.setVariableValue(variable, variable.options[0]);
|
self.setVariableValue(variable, variable.options[0]);
|
||||||
}
|
}
|
||||||
lock.resolve();
|
lock.resolve();
|
||||||
@ -102,7 +101,10 @@ function (angular, _, kbn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return promise.then(function() {
|
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 };
|
option = option || { text: urlValue, value: urlValue };
|
||||||
|
|
||||||
self.updateAutoInterval(variable);
|
self.updateAutoInterval(variable);
|
||||||
@ -125,8 +127,8 @@ function (angular, _, kbn) {
|
|||||||
this.setVariableValue = function(variable, option, initPhase) {
|
this.setVariableValue = function(variable, option, initPhase) {
|
||||||
variable.current = angular.copy(option);
|
variable.current = angular.copy(option);
|
||||||
|
|
||||||
if (_.isArray(variable.current.value)) {
|
if (_.isArray(variable.current.text)) {
|
||||||
variable.current.text = variable.current.value.join(' + ');
|
variable.current.text = variable.current.text.join(' + ');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selectOptionsForCurrentValue(variable);
|
self.selectOptionsForCurrentValue(variable);
|
||||||
@ -166,6 +168,11 @@ function (angular, _, kbn) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (variable.type === 'constant') {
|
||||||
|
variable.options = [{text: variable.query, value: variable.query}];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// extract options in comma seperated string
|
// extract options in comma seperated string
|
||||||
variable.options = _.map(variable.query.split(/[,]+/), function(text) {
|
variable.options = _.map(variable.query.split(/[,]+/), function(text) {
|
||||||
return { text: text.trim(), value: text.trim() };
|
return { text: text.trim(), value: text.trim() };
|
||||||
@ -173,6 +180,7 @@ function (angular, _, kbn) {
|
|||||||
|
|
||||||
if (variable.type === 'interval') {
|
if (variable.type === 'interval') {
|
||||||
self.updateAutoInterval(variable);
|
self.updateAutoInterval(variable);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variable.type === 'custom' && variable.includeAll) {
|
if (variable.type === 'custom' && variable.includeAll) {
|
||||||
@ -224,6 +232,7 @@ function (angular, _, kbn) {
|
|||||||
|
|
||||||
this.selectOptionsForCurrentValue = function(variable) {
|
this.selectOptionsForCurrentValue = function(variable) {
|
||||||
var i, y, value, option;
|
var i, y, value, option;
|
||||||
|
var selected = [];
|
||||||
|
|
||||||
for (i = 0; i < variable.options.length; i++) {
|
for (i = 0; i < variable.options.length; i++) {
|
||||||
option = variable.options[i];
|
option = variable.options[i];
|
||||||
@ -233,28 +242,44 @@ function (angular, _, kbn) {
|
|||||||
value = variable.current.value[y];
|
value = variable.current.value[y];
|
||||||
if (option.value === value) {
|
if (option.value === value) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
|
selected.push(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (option.value === variable.current.value) {
|
} else if (option.value === variable.current.value) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
|
selected.push(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return selected;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.validateVariableSelectionState = function(variable) {
|
this.validateVariableSelectionState = function(variable) {
|
||||||
if (!variable.current) {
|
if (!variable.current) {
|
||||||
if (!variable.options.length) { return; }
|
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)) {
|
if (_.isArray(variable.current.value)) {
|
||||||
self.selectOptionsForCurrentValue(variable);
|
var selected = self.selectOptionsForCurrentValue(variable);
|
||||||
|
|
||||||
|
// if none pick first
|
||||||
|
if (selected.length === 0) {
|
||||||
|
selected = variable.options[0];
|
||||||
|
} else {
|
||||||
|
selected = {
|
||||||
|
value: _.map(selected, function(val) {return val.value;}),
|
||||||
|
text: _.map(selected, function(val) {return val.text;}).join(' + '),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.setVariableValue(variable, selected, false);
|
||||||
} else {
|
} else {
|
||||||
var currentOption = _.findWhere(variable.options, {text: variable.current.text});
|
var currentOption = _.findWhere(variable.options, {text: variable.current.text});
|
||||||
if (currentOption) {
|
if (currentOption) {
|
||||||
return self.setVariableValue(variable, currentOption, true);
|
return self.setVariableValue(variable, currentOption, false);
|
||||||
} else {
|
} else {
|
||||||
if (!variable.options.length) { return; }
|
if (!variable.options.length) { return $q.when(null); }
|
||||||
return self.setVariableValue(variable, variable.options[0]);
|
return self.setVariableValue(variable, variable.options[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,6 +338,14 @@ function (angular, _, kbn) {
|
|||||||
var value = item.value || item.text;
|
var value = item.value || item.text;
|
||||||
var text = item.text || item.value;
|
var text = item.text || item.value;
|
||||||
|
|
||||||
|
if (_.isNumber(value)) {
|
||||||
|
value = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isNumber(text)) {
|
||||||
|
text = text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (regex) {
|
if (regex) {
|
||||||
matches = regex.exec(value);
|
matches = regex.exec(value);
|
||||||
if (!matches) { continue; }
|
if (!matches) { continue; }
|
||||||
|
@ -73,14 +73,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,7 +67,6 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
|||||||
range[timeField]["format"] = "epoch_millis";
|
range[timeField]["format"] = "epoch_millis";
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryInterpolated = templateSrv.replace(queryString);
|
var queryInterpolated = templateSrv.replace(queryString, {}, 'lucene');
|
||||||
var filter = { "bool": { "must": [{ "range": range }] } };
|
var filter = { "bool": { "must": [{ "range": range }] } };
|
||||||
var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
|
var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
|
||||||
var data = {
|
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) {
|
this.getFields = function(query) {
|
||||||
return this._get('/_mapping').then(function(res) {
|
return this._get('/_mapping').then(function(res) {
|
||||||
var fields = {};
|
var fields = {};
|
||||||
@ -246,7 +254,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
|||||||
var header = this.getQueryHeader('count', range.from, range.to);
|
var header = this.getQueryHeader('count', range.from, range.to);
|
||||||
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
|
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(/\$timeFrom/g, range.from.valueOf());
|
||||||
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
|
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
|
||||||
esQuery = header + '\n' + esQuery + '\n';
|
esQuery = header + '\n' + esQuery + '\n';
|
||||||
@ -260,7 +268,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.metricFindQuery = function(query) {
|
this.metricFindQuery = function(query) {
|
||||||
query = templateSrv.replace(query);
|
query = templateSrv.replace(query, {}, luceneThenJsonFormat);
|
||||||
query = angular.fromJson(query);
|
query = angular.fromJson(query);
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return $q.when([]);
|
return $q.when([]);
|
||||||
|
@ -70,9 +70,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-if="agg.type === 'filters'">
|
<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">
|
<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()">
|
<input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
@ -88,7 +88,7 @@
|
|||||||
|
|
||||||
<div ng-if="agg.type === 'geohash_grid'">
|
<div ng-if="agg.type === 'geohash_grid'">
|
||||||
<div class="gf-form offset-width-7">
|
<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()">
|
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision" spellcheck='false' placeholder="3" ng-blur="onChangeInternal()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -256,23 +256,14 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
|||||||
return this.renderTemplate(options.legendFormat, labelData) || '{}';
|
return this.renderTemplate(options.legendFormat, labelData) || '{}';
|
||||||
};
|
};
|
||||||
|
|
||||||
this.renderTemplate = function(format, data) {
|
this.renderTemplate = function(aliasPattern, aliasData) {
|
||||||
var originalSettings = _.templateSettings;
|
var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g;
|
||||||
_.templateSettings = {
|
return aliasPattern.replace(aliasRegex, function(match, g1) {
|
||||||
interpolate: /\{\{(.+?)\}\}/g
|
if (aliasData[g1]) {
|
||||||
};
|
return aliasData[g1];
|
||||||
|
}
|
||||||
var template = _.template(templateSrv.replace(format));
|
return g1;
|
||||||
var result;
|
});
|
||||||
try {
|
|
||||||
result = template(data);
|
|
||||||
} catch (e) {
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_.templateSettings = originalSettings;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getOriginalMetricName = function(labelData) {
|
this.getOriginalMetricName = function(labelData) {
|
||||||
|
@ -58,6 +58,10 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
|||||||
|
|
||||||
updateLink() {
|
updateLink() {
|
||||||
var range = this.panelCtrl.range;
|
var range = this.panelCtrl.range;
|
||||||
|
if (!range) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
|
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
|
||||||
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
|
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
|
||||||
var expr = {
|
var expr = {
|
||||||
|
@ -66,7 +66,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
|||||||
|
|
||||||
function getLegendHeight(panelHeight) {
|
function getLegendHeight(panelHeight) {
|
||||||
if (!panel.legend.show || panel.legend.rightSide) {
|
if (!panel.legend.show || panel.legend.rightSide) {
|
||||||
return 2;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panel.legend.alignAsTable) {
|
if (panel.legend.alignAsTable) {
|
||||||
|
@ -22,8 +22,8 @@ describe('GraphCtrl', function() {
|
|||||||
describe('msResolution with second resolution timestamps', function() {
|
describe('msResolution with second resolution timestamps', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[1234567890, 45], [1234567899, 60]]},
|
{ target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[1236547890, 55], [1234456709, 90]]}
|
{ target: 'test.cpu2', datapoints: [[55, 1236547890], [90, 1234456709]]}
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
@ -37,8 +37,8 @@ describe('GraphCtrl', function() {
|
|||||||
describe('msResolution with millisecond resolution timestamps', function() {
|
describe('msResolution with millisecond resolution timestamps', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]},
|
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[1236547890001, 55], [1234456709000, 90]]}
|
{ target: 'test.cpu2', datapoints: [[55, 1236547890001], [90, 1234456709000]]}
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
@ -52,8 +52,8 @@ describe('GraphCtrl', function() {
|
|||||||
describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
|
describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]},
|
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[1236547890000, 55], [1234456709000, 90]]}
|
{ target: 'test.cpu2', datapoints: [[55, 1236547890000], [90, 1234456709000]]}
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
@ -67,9 +67,9 @@ describe('GraphCtrl', function() {
|
|||||||
describe('msResolution with millisecond resolution timestamps in one of the series', function() {
|
describe('msResolution with millisecond resolution timestamps in one of the series', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
var data = [
|
var data = [
|
||||||
{ target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]},
|
{ target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]},
|
||||||
{ target: 'test.cpu2', datapoints: [[1236547890010, 55], [1234456709000, 90]]},
|
{ target: 'test.cpu2', datapoints: [[55, 1236547890010], [90, 1234456709000]]},
|
||||||
{ target: 'test.cpu3', datapoints: [[1236547890000, 65], [1234456709000, 120]]}
|
{ target: 'test.cpu3', datapoints: [[65, 1236547890000], [120, 1234456709000]]}
|
||||||
];
|
];
|
||||||
ctx.ctrl.panel.tooltip.msResolution = false;
|
ctx.ctrl.panel.tooltip.msResolution = false;
|
||||||
ctx.ctrl.onDataReceived(data);
|
ctx.ctrl.onDataReceived(data);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
<div class="singlestat-panel">
|
<div class="singlestat-panel">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
|
@ -325,6 +325,9 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addGauge() {
|
function addGauge() {
|
||||||
|
var width = elem.width();
|
||||||
|
var height = elem.height();
|
||||||
|
|
||||||
ctrl.invalidGaugeRange = false;
|
ctrl.invalidGaugeRange = false;
|
||||||
if (panel.gauge.minValue > panel.gauge.maxValue) {
|
if (panel.gauge.minValue > panel.gauge.maxValue) {
|
||||||
ctrl.invalidGaugeRange = true;
|
ctrl.invalidGaugeRange = true;
|
||||||
@ -332,8 +335,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var plotCanvas = $('<div></div>');
|
var plotCanvas = $('<div></div>');
|
||||||
var width = elem.width();
|
|
||||||
var height = elem.height();
|
|
||||||
var plotCss = {
|
var plotCss = {
|
||||||
top: '10px',
|
top: '10px',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
|
@ -269,3 +269,7 @@ $checkboxImageUrl: '../img/checkbox.png';
|
|||||||
$card-background: linear-gradient(135deg, #2f2f2f, #262626);
|
$card-background: linear-gradient(135deg, #2f2f2f, #262626);
|
||||||
$card-background-hover: linear-gradient(135deg, #343434, #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);
|
$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;
|
||||||
|
@ -293,3 +293,7 @@ $checkboxImageUrl: '../img/checkbox_white.png';
|
|||||||
$card-background: linear-gradient(135deg, $gray-5, $gray-6);
|
$card-background: linear-gradient(135deg, $gray-5, $gray-6);
|
||||||
$card-background-hover: linear-gradient(135deg, $gray-6, $gray-7);
|
$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);
|
$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 {
|
.page-dashboard .footer {
|
||||||
position: absolute;
|
display: none;
|
||||||
bottom: 2px;
|
|
||||||
left: 3px;
|
|
||||||
font-size: 80%;
|
|
||||||
color: darken($gray-1, 25%);
|
|
||||||
a { color: darken($gray-1, 25%); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.singlestat-panel-value-container {
|
||||||
padding: 20px;
|
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-view {
|
.main-view {
|
||||||
height: 100%;
|
// height: 100%; REMOVED FOR FOOTER TRW
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.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;
|
var fakedata;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@ -64,13 +64,13 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('missing datapoint with ms precision', function() {
|
it('missing datapoint with ms precision', function() {
|
||||||
fakedata.datapoints[0] = [1234567890000, 1337];
|
fakedata.datapoints[0] = [1337, 1234567890000];
|
||||||
series = new TimeSeries(fakedata);
|
series = new TimeSeries(fakedata);
|
||||||
expect(series.isMsResolutionNeeded()).to.be(false);
|
expect(series.isMsResolutionNeeded()).to.be(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('contains datapoint with ms precision', function() {
|
it('contains datapoint with ms precision', function() {
|
||||||
fakedata.datapoints[0] = [1236547890001, 1337];
|
fakedata.datapoints[0] = [1337, 1236547890001];
|
||||||
series = new TimeSeries(fakedata);
|
series = new TimeSeries(fakedata);
|
||||||
expect(series.isMsResolutionNeeded()).to.be(true);
|
expect(series.isMsResolutionNeeded()).to.be(true);
|
||||||
});
|
});
|
||||||
|
@ -147,5 +147,11 @@ define([
|
|||||||
var str = kbn.calculateInterval(range, 1000, '>10s');
|
var str = kbn.calculateInterval(range, 1000, '>10s');
|
||||||
expect(str).to.be('20m');
|
expect(str).to.be('20m');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('10s 900 resolution and user low limit in ms', function() {
|
||||||
|
var range = { from: dateMath.parse('now-10s'), to: dateMath.parse('now') };
|
||||||
|
var str = kbn.calculateInterval(range, 900, '>15ms');
|
||||||
|
expect(str).to.be('15ms');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,8 +5,20 @@ define([
|
|||||||
|
|
||||||
describe('when updating view state', function() {
|
describe('when updating view state', function() {
|
||||||
var viewState, location;
|
var viewState, location;
|
||||||
|
var timeSrv = {};
|
||||||
|
var templateSrv = {};
|
||||||
|
var contextSrv = {
|
||||||
|
user: {
|
||||||
|
orgId: 19
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(module('grafana.services'));
|
beforeEach(module('grafana.services'));
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('timeSrv', timeSrv);
|
||||||
|
$provide.value('templateSrv', templateSrv);
|
||||||
|
$provide.value('contextSrv', contextSrv);
|
||||||
|
}));
|
||||||
|
|
||||||
beforeEach(inject(function(dashboardViewStateSrv, $location, $rootScope) {
|
beforeEach(inject(function(dashboardViewStateSrv, $location, $rootScope) {
|
||||||
$rootScope.onAppEvent = function() {};
|
$rootScope.onAppEvent = function() {};
|
||||||
@ -17,9 +29,9 @@ define([
|
|||||||
|
|
||||||
describe('to fullscreen true and edit true', function() {
|
describe('to fullscreen true and edit true', function() {
|
||||||
it('should update querystring and view state', function() {
|
it('should update querystring and view state', function() {
|
||||||
var updateState = { fullscreen: true, edit: true, panelId: 1 };
|
var updateState = {fullscreen: true, edit: true, panelId: 1};
|
||||||
viewState.update(updateState);
|
viewState.update(updateState);
|
||||||
expect(location.search()).to.eql(updateState);
|
expect(location.search()).to.eql({fullscreen: true, edit: true, panelId: 1, org: 19});
|
||||||
expect(viewState.dashboard.meta.fullscreen).to.be(true);
|
expect(viewState.dashboard.meta.fullscreen).to.be(true);
|
||||||
expect(viewState.state.fullscreen).to.be(true);
|
expect(viewState.state.fullscreen).to.be(true);
|
||||||
});
|
});
|
||||||
@ -29,7 +41,7 @@ define([
|
|||||||
it('should remove params from query string', function() {
|
it('should remove params from query string', function() {
|
||||||
viewState.update({fullscreen: true, panelId: 1, edit: true});
|
viewState.update({fullscreen: true, panelId: 1, edit: true});
|
||||||
viewState.update({fullscreen: false});
|
viewState.update({fullscreen: false});
|
||||||
expect(location.search()).to.eql({});
|
expect(location.search()).to.eql({org: 19});
|
||||||
expect(viewState.dashboard.meta.fullscreen).to.be(false);
|
expect(viewState.dashboard.meta.fullscreen).to.be(false);
|
||||||
expect(viewState.state.fullscreen).to.be(null);
|
expect(viewState.state.fullscreen).to.be(null);
|
||||||
});
|
});
|
||||||
|
@ -141,8 +141,8 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('slash should be properly escaped in regex format', function() {
|
it('slash should be properly escaped in regex format', function() {
|
||||||
var result = _templateSrv.formatValue('Gi3/14', 'regex');
|
var result = _templateSrv.formatValue('Gi3/14', 'regex');
|
||||||
expect(result).to.be('Gi3\\/14');
|
expect(result).to.be('Gi3\\/14');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -200,6 +200,15 @@ define([
|
|||||||
expect(contains).to.be(true);
|
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() {
|
describe('updateTemplateData with simple value', function() {
|
||||||
|
@ -126,6 +126,80 @@ 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 multi select and $__all selected', function(scenario) {
|
||||||
|
scenario.setup(function() {
|
||||||
|
scenario.variable = {
|
||||||
|
type: 'query',
|
||||||
|
query: '',
|
||||||
|
name: 'test',
|
||||||
|
includeAll: true,
|
||||||
|
current: {
|
||||||
|
value: ['$__all'],
|
||||||
|
text: 'All'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
scenario.queryResult = [{text: 'val5'}, {text: 'val6'}];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep current All value', function() {
|
||||||
|
expect(scenario.variable.current.value).to.eql(['$__all']);
|
||||||
|
expect(scenario.variable.current.text).to.eql('All');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
describeUpdateVariable('interval variable without auto', function(scenario) {
|
||||||
scenario.setup(function() {
|
scenario.setup(function() {
|
||||||
scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' };
|
scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' };
|
||||||
|
@ -39,6 +39,41 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-view class="main-view"></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>
|
</grafana-app>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
8
vendor/phantomjs/render.js
vendored
8
vendor/phantomjs/render.js
vendored
@ -38,10 +38,11 @@
|
|||||||
function checkIsReady() {
|
function checkIsReady() {
|
||||||
var canvas = page.evaluate(function() {
|
var canvas = page.evaluate(function() {
|
||||||
if (!window.angular) { return false; }
|
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; }
|
if (!body.injector) { return false; }
|
||||||
|
if (!body.injector()) { return false; }
|
||||||
|
|
||||||
var rootScope = body.scope();
|
var rootScope = body.injector().get('$rootScope');
|
||||||
if (!rootScope) {return false;}
|
if (!rootScope) {return false;}
|
||||||
if (!rootScope.performance) { return false; }
|
if (!rootScope.performance) { return false; }
|
||||||
var panelsToLoad = window.angular.element('div.panel').length;
|
var panelsToLoad = window.angular.element('div.panel').length;
|
||||||
@ -59,6 +60,7 @@
|
|||||||
width: bb.width,
|
width: bb.width,
|
||||||
height: bb.height
|
height: bb.height
|
||||||
};
|
};
|
||||||
|
|
||||||
page.render(params.png);
|
page.render(params.png);
|
||||||
phantom.exit();
|
phantom.exit();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user