diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b7d73592ef4..e53a1159941 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,20 +1,19 @@ -Thank you! For helping us make Grafana even better. +Thank you for helping us make Grafana even better! -To help us respond to your issues faster, please make sure to add as much information as possible. +To help us respond to your issues more quickly, please make sure to add as much information as possible. -If this issue is about a plugin, please open the issue in that repository. +If this issue is about a plugin, please open the issue in that plugin's repository. -Start your issues title with [Feature Request] / [Bug] / [Question] or no tag if your unsure. Also, please be aware that GitHub now supports uploading of screenshots; look at the bottom of this input field. +Start your issue's title with [Feature Request] / [Bug] / [Question] or no tag if you're unsure. Also, please be aware that GitHub now supports uploading of screenshots; look at the bottom of this input field. Please include some basic information: -- What grafana version are you using? +- What Grafana version are you using? - What datasource are you using? - What OS are you running grafana on? - What did you do? - What was the expected result? - What happenend instead? -If you question/bug relates to a metric query / unexpected data visualization, please include: +If your question/bug relates to a metric query / unexpected data visualization, please include: - An image or text representation of your metric query - The raw query and response from your data source (check this in chrome dev tools network tab) - diff --git a/CHANGELOG.md b/CHANGELOG.md index bf074487057..323aaaecac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,33 @@ -# 3.1.0 +# 3.1.0 (unreleased) ### Enhancements +* **Dashboard Url**: Time range changes updates url, closes [#458](https://github.com/grafana/grafana/issues/458) +* **Dashboard Url**: Template variable change updates url, closes [#5002](https://github.com/grafana/grafana/issues/5002) * **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319) * **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189) * **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011) +* **Page Footer**: Added page footer with links to docs, shows Grafana version and info if new version is available, closes [#4889](https://github.com/grafana/grafana/pull/4889) -# 3.0.3 Patch release (unreleased) +# 3.0.4 Patch release (2016-05-25) +* **Panel**: Fixed blank dashboard issue when switching to other dashboard while in fullscreen edit mode, fixes [#5163](https://github.com/grafana/grafana/pull/5163) +* **Templating**: Fixed issue with nested multi select variables and cascading and updating child variable selection state, fixes [#4861](https://github.com/grafana/grafana/pull/4861) +* **Templating**: Fixed issue with using templated data source in another template variable query, fixes [#5165](https://github.com/grafana/grafana/pull/5165) +* **Singlestat gauge**: Fixed issue with gauge render position, fixes [#5143](https://github.com/grafana/grafana/pull/5143) +* **Home dashboard**: Fixes broken home dashboard api, fixes [#5167](https://github.com/grafana/grafana/issues/5167) + +# 3.0.3 Patch release (2016-05-23) +* **Annotations**: Annotations can now use a template variable as data source, closes [#5054](https://github.com/grafana/grafana/issues/5054) * **Time picker**: Fixed issue timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078) * **CloudWatch**: Support for Multiple Account by AssumeRole, closes [#3522](https://github.com/grafana/grafana/issues/3522) +* **Singlestat**: Fixed alignment and minium height issue, fixes [#5113](https://github.com/grafana/grafana/issues/5113), fixes [#4679](https://github.com/grafana/grafana/issues/4679) +* **Share modal**: Fixed link when using grafana under dashboard sub url, fixes [#5109](https://github.com/grafana/grafana/issues/5109) +* **Prometheus**: Fixed bug in query editor that caused it not to load when reloading page, fixes [#5107](https://github.com/grafana/grafana/issues/5107) +* **Elasticsearch**: Fixed bug when template variable query returns numeric values, fixes [#5097](https://github.com/grafana/grafana/issues/5097), fixes [#5088](https://github.com/grafana/grafana/issues/5088) +* **Logging**: Fixed issue with reading logging level value, fixes [#5079](https://github.com/grafana/grafana/issues/5079) +* **Timepicker**: Fixed issue with timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078) +* **Docs**: Added docs for org & user preferences HTTP API, closes [#5069](https://github.com/grafana/grafana/issues/5069) +* **Plugin list panel**: Now shows correct enable state for apps when not enabled, fixes [#5068](https://github.com/grafana/grafana/issues/5068) +* **Elasticsearch**: Templating & Annotation queries that use template variables are now formatted correctly, fixes [#5135](https://github.com/grafana/grafana/issues/5135) # 3.0.2 Patch release (2016-05-16) diff --git a/docker/blocks/collectd/Dockerfile b/docker/blocks/collectd/Dockerfile new file mode 100644 index 00000000000..a08b1f9c1b2 --- /dev/null +++ b/docker/blocks/collectd/Dockerfile @@ -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 diff --git a/docker/blocks/collectd/README.md b/docker/blocks/collectd/README.md new file mode 100644 index 00000000000..2c1a8cb79fc --- /dev/null +++ b/docker/blocks/collectd/README.md @@ -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 diff --git a/docker/blocks/collectd/collectd.conf.tpl b/docker/blocks/collectd/collectd.conf.tpl new file mode 100644 index 00000000000..c19654b39f9 --- /dev/null +++ b/docker/blocks/collectd/collectd.conf.tpl @@ -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 + + + ReportByCpu {{ REPORT_BY_CPU | default("false") }} + + + + # 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 + + + + Disk "/^[hs]d[a-z]/" + IgnoreSelected false + + + + + Interface "lo" + Interface "/^veth.*/" + Interface "/^docker.*/" + IgnoreSelected true + + + + + + Host "{{ GRAPHITE_HOST }}" + Port "{{ GRAPHITE_PORT | default("2003") }}" + Prefix "{{ GRAPHITE_PREFIX | default("collectd.") }}" + EscapeCharacter "_" + SeparateInstances true + StoreRates true + AlwaysAppendDS false + + + diff --git a/docker/blocks/collectd/etc_mtab b/docker/blocks/collectd/etc_mtab new file mode 100644 index 00000000000..749f9789482 --- /dev/null +++ b/docker/blocks/collectd/etc_mtab @@ -0,0 +1 @@ +hostfs /.dockerinit ext4 ro,relatime,user_xattr,barrier=1,data=ordered 0 0 diff --git a/docker/blocks/collectd/fig b/docker/blocks/collectd/fig new file mode 100644 index 00000000000..99f45a66d12 --- /dev/null +++ b/docker/blocks/collectd/fig @@ -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 diff --git a/docker/blocks/collectd/start_container b/docker/blocks/collectd/start_container new file mode 100644 index 00000000000..b01cd0d5ff2 --- /dev/null +++ b/docker/blocks/collectd/start_container @@ -0,0 +1,5 @@ +#!/bin/bash + +envtpl /etc/collectd/collectd.conf.tpl + +collectd -f diff --git a/docs/README.md b/docs/README.md index 36c636fcc72..65bd5714615 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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/) -image and [mkdocs](http://www.mkdocs.org/). +# Building The Docs -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 @@ -9,10 +17,45 @@ $ cd docs-base $ 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 ``` +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. + +**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'] +``` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index ff88133dfdd..c969a568906 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -84,6 +84,7 @@ pages: - ['http_api/user.md', 'API', 'User API'] - ['http_api/admin.md', 'API', 'Admin API'] - ['http_api/snapshot.md', 'API', 'Snapshot API'] +- ['http_api/preferences.md', 'API', 'Preferences API'] - ['http_api/other.md', 'API', 'Other API'] - ['plugins/index.md', 'Plugins', 'Overview'] diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index e7d1d68523a..bbfe381d1f0 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -226,7 +226,7 @@ organization to be created for that new user. The role new users will be assigned for the main organization (if the above setting is set to true). Defaults to `Viewer`, other valid -options are `Admin` and `Editor`. +options are `Admin` and `Editor` and `Read-Only Editor`.
diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 1b3957fa684..6084b9b317d 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -10,13 +10,13 @@ page_keywords: grafana, installation, debian, ubuntu, guide Description | Download ------------ | ------------- -Stable .deb for Debian-based Linux | [grafana_3.0.2-1463383025_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb) +Stable .deb for Debian-based Linux | [grafana_3.0.4-1464167696.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.4-1464167696_amd64.deb) ## Install Stable - $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.2-1463383025_amd64.deb + $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.4-1464167696_amd64.deb $ sudo apt-get install -y adduser libfontconfig - $ sudo dpkg -i grafana_3.0.2-1463383025_amd64.deb + $ sudo dpkg -i grafana_3.0.4-1464167696_amd64.deb ## APT Repository diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index af3ae7f8288..854ba91fcf7 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -10,24 +10,24 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide Description | Download ------------ | ------------- -Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.2-1463383025.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm) +Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-3.0.4-1464167696.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.4-1464167696.x86_64.rpm) ## Install Stable Release from package file You can install Grafana using Yum directly. - $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.2-1463383025.x86_64.rpm + $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.4-1464167696.x86_64.rpm Or install manually using `rpm`. #### On CentOS / Fedora / Redhat: $ sudo yum install initscripts fontconfig - $ sudo rpm -Uvh grafana-3.0.2-1463383025.x86_64.rpm + $ sudo rpm -Uvh grafana-3.0.4-1464167696.x86_64.rpm #### On OpenSuse: - $ sudo rpm -i --nodeps grafana-3.0.2-1463383025.x86_64.rpm + $ sudo rpm -i --nodeps grafana-3.0.4-1464167696.x86_64.rpm ## Install via YUM Repository diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index baa13d0350c..858f8cfea2d 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide Description | Download ------------ | ------------- -Stable Zip package for Windows | [grafana.3.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.2.windows-x64.zip) +Stable Zip package for Windows | [grafana.3.0.4.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-3.0.4.windows-x64.zip) ## Configure diff --git a/latest.json b/latest.json index 443a440a529..7fe27b91983 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "3.0.2", - "testing": "3.0.2" + "stable": "3.0.4", + "testing": "3.0.4" } diff --git a/package.json b/package.json index 9dbff96c2b8..3e3cd2faeac 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "grunt-angular-templates": "^0.5.5", "grunt-cli": "~0.1.13", "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-copy": "~0.8.2", "grunt-contrib-cssmin": "~0.14.0", diff --git a/packaging/publish/publish.sh b/packaging/publish/publish.sh index 79da73d441a..892bfc178cc 100755 --- a/packaging/publish/publish.sh +++ b/packaging/publish/publish.sh @@ -1,22 +1,20 @@ #! /usr/bin/env bash -deb_ver=3.0.1 -rpm_ver=3.0.1-1 +deb_ver=3.0.4-1464167696 +rpm_ver=3.0.4-1464167696 -#rpm_ver=3.0.0-1 +wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb -#wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb +package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb +package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb -#package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb -#package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb +package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb +package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb -#package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb -#package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb +wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm -#wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm - -#package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm -#package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm +package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm +package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm package_cloud push grafana/stable/el/7 grafana-${rpm_ver}.x86_64.rpm package_cloud push grafana/stable/el/6 grafana-${rpm_ver}.x86_64.rpm diff --git a/pkg/api/api.go b/pkg/api/api.go index b7e2ef95a7e..c05cb9af59f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -117,6 +117,7 @@ func Register(r *macaron.Macaron) { r.Get("/:id", wrap(GetUserById)) r.Get("/:id/orgs", wrap(GetUserOrgList)) r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser)) + r.Post("/:id/using/:orgId", wrap(UpdateUserActiveOrg)) }, reqGrafanaAdmin) // org information available to all users. @@ -211,7 +212,7 @@ func Register(r *macaron.Macaron) { r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard) r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard) r.Get("/file/:file", GetDashboardFromJsonFile) - r.Get("/home", GetHomeDashboard) + r.Get("/home", wrap(GetHomeDashboard)) r.Get("/tags", GetDashboardTags) r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard)) }) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 5b4449b5c02..4e9c6cc9064 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -57,11 +57,12 @@ var awsCredentialCache map[string]cache = make(map[string]cache) var credentialCacheLock sync.RWMutex func getCredentials(profile string, region string, assumeRoleArn string) *credentials.Credentials { + cacheKey := profile + ":" + assumeRoleArn credentialCacheLock.RLock() - if _, ok := awsCredentialCache[profile]; ok { - if awsCredentialCache[profile].expiration != nil && - (*awsCredentialCache[profile].expiration).After(time.Now().UTC()) { - result := awsCredentialCache[profile].credential + if _, ok := awsCredentialCache[cacheKey]; ok { + if awsCredentialCache[cacheKey].expiration != nil && + (*awsCredentialCache[cacheKey].expiration).After(time.Now().UTC()) { + result := awsCredentialCache[cacheKey].credential credentialCacheLock.RUnlock() return result } @@ -118,7 +119,7 @@ func getCredentials(profile string, region string, assumeRoleArn string) *creden &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) credentialCacheLock.Lock() - awsCredentialCache[profile] = cache{ + awsCredentialCache[cacheKey] = cache{ credential: creds, expiration: expiration, } diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 5bd318ea3c9..54e60b781cb 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -45,6 +45,15 @@ func init() { "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/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", "MapTasksRunning", "MapTasksRemaining", "MapSlotsOpen", "RemainingMapTasksPerSlot", "ReduceTasksRunning", "ReduceTasksRemaining", "ReduceSlotsOpen", "CoreNodesRunning", "CoreNodesPending", "LiveDataNodes", "TaskNodesRunning", "TaskNodesPending", "LiveTaskTrackers", @@ -85,6 +94,7 @@ func init() { "AWS/EBS": {"VolumeId"}, "AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"}, "AWS/ELB": {"LoadBalancerName", "AvailabilityZone"}, + "AWS/ElasticBeanstalk": {"EnvironmentName", "InstanceId"}, "AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"}, "AWS/ES": {"ClientId", "DomainName"}, "AWS/Events": {"RuleName"}, diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index d8a6057c401..e4a9289a3ed 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" @@ -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 } -func GetHomeDashboard(c *middleware.Context) { +func GetHomeDashboard(c *middleware.Context) Response { prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId} if err := bus.Dispatch(&prefsQuery); err != nil { - c.JsonApiErr(500, "Failed to get preferences", err) + return ApiError(500, "Failed to get preferences", err) } if prefsQuery.Result.HomeDashboardId != 0 { slugQuery := m.GetDashboardSlugByIdQuery{Id: prefsQuery.Result.HomeDashboardId} err := bus.Dispatch(&slugQuery) - if err != nil { - c.JsonApiErr(500, "Failed to get slug from database", err) - return + if err == nil { + dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result} + return Json(200, &dashRedirect) + } else { + log.Warn("Failed to get slug from database, %s", err.Error()) } - - dashRedirect := dtos.DashboardRedirect{RedirectUri: "db/" + slugQuery.Result} - c.JSON(200, &dashRedirect) - return } filePath := path.Join(setting.StaticRootPath, "dashboards/home.json") file, err := os.Open(filePath) if err != nil { - c.JsonApiErr(500, "Failed to load home dashboard", err) - return + return ApiError(500, "Failed to load home dashboard", err) } dash := dtos.DashboardFullWithMeta{} @@ -205,11 +203,10 @@ func GetHomeDashboard(c *middleware.Context) { dash.Meta.CanEdit = canEditDashboard(c.OrgRole) jsonParser := json.NewDecoder(file) if err := jsonParser.Decode(&dash.Dashboard); err != nil { - c.JsonApiErr(500, "Failed to load home dashboard", err) - return + return ApiError(500, "Failed to load home dashboard", err) } - c.JSON(200, &dash) + return Json(200, &dash) } func GetDashboardFromJsonFile(c *middleware.Context) { diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index b00ef595161..9a062ddfd2d 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -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)) } + 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 req.Header.Del("Cookie") req.Header.Del("Set-Cookie") diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 21201d30adf..b813c78f2bb 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -1,13 +1,17 @@ package dtos type IndexViewData struct { - User *CurrentUser - Settings map[string]interface{} - AppUrl string - AppSubUrl string - GoogleAnalyticsId string - GoogleTagManagerId string - MainNavLinks []*NavLink + User *CurrentUser + Settings map[string]interface{} + AppUrl string + AppSubUrl string + GoogleAnalyticsId string + GoogleTagManagerId string + MainNavLinks []*NavLink + BuildVersion string + BuildCommit string + NewGrafanaVersionExists bool + NewGrafanaVersion string } type PluginCss struct { diff --git a/pkg/api/index.go b/pkg/api/index.go index 69dd568a2c6..4c778290b20 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -36,11 +36,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { LightTheme: prefs.Theme == "light", Timezone: prefs.Timezone, }, - Settings: settings, - AppUrl: setting.AppUrl, - AppSubUrl: setting.AppSubUrl, - GoogleAnalyticsId: setting.GoogleAnalyticsId, - GoogleTagManagerId: setting.GoogleTagManagerId, + Settings: settings, + AppUrl: setting.AppUrl, + AppSubUrl: setting.AppSubUrl, + GoogleAnalyticsId: setting.GoogleAnalyticsId, + GoogleTagManagerId: setting.GoogleTagManagerId, + BuildVersion: setting.BuildVersion, + BuildCommit: setting.BuildCommit, + NewGrafanaVersion: plugins.GrafanaLatestVersion, + NewGrafanaVersionExists: plugins.GrafanaHasUpdate, } if setting.DisableGravatar { diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index 92d07988b64..21d40ecb948 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -88,7 +88,7 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins } for key, value := range headers { - log.Info("setting key %v value %v", key, value[0]) + log.Trace("setting key %v value %v", key, value[0]) req.Header.Set(key, value[0]) } } diff --git a/pkg/api/user.go b/pkg/api/user.go index 8f54feaf6a0..f98eec02c40 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -40,6 +40,24 @@ func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) Response { 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 { if len(cmd.Login) == 0 { cmd.Login = cmd.Email diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index 9da0e19c023..d466977cb71 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -25,7 +25,7 @@ import ( "github.com/grafana/grafana/pkg/social" ) -var version = "3.0.0-beta4" +var version = "3.1.0" var commit = "NA" var buildstamp string var build_date string diff --git a/public/app/core/controllers/login_ctrl.js b/public/app/core/controllers/login_ctrl.js index 4249d55a44f..45a47558ed7 100644 --- a/public/app/core/controllers/login_ctrl.js +++ b/public/app/core/controllers/login_ctrl.js @@ -35,15 +35,6 @@ function (angular, coreModule, config) { } }; - // build info view model - $scope.buildInfo = { - version: config.buildInfo.version, - commit: config.buildInfo.commit, - buildstamp: new Date(config.buildInfo.buildstamp * 1000), - latestVersion: config.buildInfo.latestVersion, - hasUpdate: config.buildInfo.hasUpdate, - }; - $scope.submit = function() { if ($scope.loginMode) { $scope.login(); diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js index 61415a660b2..4b3cd2e8de3 100644 --- a/public/app/core/directives/metric_segment.js +++ b/public/app/core/directives/metric_segment.js @@ -209,7 +209,9 @@ function (_, $, coreModule) { // needs to call this after digest so // property is synced with outerscope $scope.$$postDigest(function() { - $scope.onChange(); + $scope.$apply(function() { + $scope.onChange(); + }); }); }; diff --git a/public/app/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts index 6f693a13d68..c8fb5c9ba91 100644 --- a/public/app/core/directives/plugin_component.ts +++ b/public/app/core/directives/plugin_component.ts @@ -244,7 +244,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ registerPluginComponent(scope, elem, attrs, componentInfo); }).catch(err => { $rootScope.appEvent('alert-error', ['Plugin Error', err.message || err]); - console.log('Plugin componnet error', err); + console.log('Plugin component error', err); }); } }; diff --git a/public/app/core/services/backend_srv.js b/public/app/core/services/backend_srv.js index ff3784ab45e..f27e427c70b 100644 --- a/public/app/core/services/backend_srv.js +++ b/public/app/core/services/backend_srv.js @@ -96,6 +96,11 @@ function (angular, _, coreModule, config) { var requestIsLocal = options.url.indexOf('/') === 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) { // handle unauthorized for backend requests if (requestIsLocal && firstAttempt && err.status === 401) { diff --git a/public/app/core/services/datasource_srv.js b/public/app/core/services/datasource_srv.js index 32bc9a39725..378cfa2d9d5 100644 --- a/public/app/core/services/datasource_srv.js +++ b/public/app/core/services/datasource_srv.js @@ -66,14 +66,17 @@ function (angular, _, coreModule, config) { }; this.getAnnotationSources = function() { - return _.reduce(config.datasources, function(memo, value) { + var sources = []; + this.addDataSourceVariables(sources); + + _.each(config.datasources, function(value) { if (value.meta && value.meta.annotations) { - memo.push(value); + sources.push(value); } + }); - return memo; - }, []); + return sources; }; this.getMetricSources = function(options) { @@ -90,24 +93,7 @@ function (angular, _, coreModule, config) { }); if (!options || !options.skipVariables) { - // look for data source variables - for (var i = 0; i < templateSrv.variables.length; i++) { - var variable = templateSrv.variables[i]; - if (variable.type !== 'datasource') { - continue; - } - - var first = variable.current.value; - var ds = config.datasources[first]; - - if (ds) { - metricSources.push({ - name: '$' + variable.name, - value: '$' + variable.name, - meta: ds.meta, - }); - } - } + this.addDataSourceVariables(metricSources); } metricSources.sort(function(a, b) { @@ -123,6 +109,27 @@ function (angular, _, coreModule, config) { return metricSources; }; + this.addDataSourceVariables = function(list) { + // look for data source variables + for (var i = 0; i < templateSrv.variables.length; i++) { + var variable = templateSrv.variables[i]; + if (variable.type !== 'datasource') { + continue; + } + + var first = variable.current.value; + var ds = config.datasources[first]; + + if (ds) { + list.push({ + name: '$' + variable.name, + value: '$' + variable.name, + meta: ds.meta, + }); + } + } + }; + this.init(); }); }); diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index cbceff7afd8..dfae26fb48b 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -173,8 +173,8 @@ export default class TimeSeries { isMsResolutionNeeded() { for (var i = 0; i < this.datapoints.length; i++) { - if (this.datapoints[i][0] !== null) { - var timestamp = this.datapoints[i][0].toString(); + if (this.datapoints[i][1] !== null) { + var timestamp = this.datapoints[i][1].toString(); if (timestamp.length === 13 && (timestamp % 1000) !== 0) { return true; } diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index 9d714c34fb3..f54979b0e97 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -12,9 +12,21 @@ function($, _) { kbn.round_interval = function(interval) { switch (true) { - // 0.3s - case (interval <= 300): - return 100; // 0.1s + // 0.015s + case (interval <= 15): + 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 case (interval <= 750): return 500; // 0.5s @@ -133,7 +145,7 @@ function($, _) { return str; }; - kbn.interval_regex = /(\d+(?:\.\d+)?)([Mwdhmsy])/; + kbn.interval_regex = /(\d+(?:\.\d+)?)(ms|[Mwdhmsy])/; // histogram & trends kbn.intervals_in_seconds = { @@ -143,7 +155,8 @@ function($, _) { d: 86400, h: 3600, m: 60, - s: 1 + s: 1, + ms: 0.001 }; kbn.calculateInterval = function(range, resolution, userInterval) { diff --git a/public/app/features/annotations/editor_ctrl.js b/public/app/features/annotations/editor_ctrl.js index ca754b094a8..c37592cc905 100644 --- a/public/app/features/annotations/editor_ctrl.js +++ b/public/app/features/annotations/editor_ctrl.js @@ -30,7 +30,7 @@ function (angular, _, $) { $scope.datasourceChanged = function() { return datasourceSrv.get($scope.currentAnnotation.datasource).then(function(ds) { $scope.currentDatasource = ds; - $scope.currentAnnotation.datasource = ds.name; + $scope.currentAnnotation.datasource = $scope.currentAnnotation.datasource; }); }; diff --git a/public/app/features/dashboard/rowCtrl.js b/public/app/features/dashboard/rowCtrl.js index 5607f1eecac..4534b9bc510 100644 --- a/public/app/features/dashboard/rowCtrl.js +++ b/public/app/features/dashboard/rowCtrl.js @@ -142,12 +142,19 @@ function (angular, _, config) { }); module.directive('panelWidth', function() { + return function(scope, element) { + var fullscreen = false; + function updateWidth() { - element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%'; + if (!fullscreen) { + element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%'; + } } scope.onAppEvent('panel-fullscreen-enter', function(evt, info) { + fullscreen = true; + if (scope.panel.id !== info.panelId) { element.hide(); } else { @@ -156,14 +163,20 @@ function (angular, _, config) { }); scope.onAppEvent('panel-fullscreen-exit', function(evt, info) { + fullscreen = false; + if (scope.panel.id !== info.panelId) { element.show(); - } else { - updateWidth(); } + + updateWidth(); }); scope.$watch('panel.span', updateWidth); + + if (fullscreen) { + element.hide(); + } }; }); diff --git a/public/app/features/dashboard/shareModalCtrl.js b/public/app/features/dashboard/shareModalCtrl.js index 2944cc79e42..6f778ea27c2 100644 --- a/public/app/features/dashboard/shareModalCtrl.js +++ b/public/app/features/dashboard/shareModalCtrl.js @@ -70,12 +70,12 @@ function (angular, _, require, config) { $scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params); var soloUrl = $scope.shareUrl; - soloUrl = soloUrl.replace('/dashboard/', '/dashboard-solo/'); + soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/'); soloUrl = soloUrl.replace("&fullscreen", ""); $scope.iframeHtml = ''; - $scope.imageUrl = soloUrl.replace('/dashboard-solo/', '/render/dashboard-solo/'); + $scope.imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/'); $scope.imageUrl += '&width=1000'; $scope.imageUrl += '&height=500'; }; diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/submenu/submenu.ts index a9899c3a4b8..75e53e668b0 100644 --- a/public/app/features/dashboard/submenu/submenu.ts +++ b/public/app/features/dashboard/submenu/submenu.ts @@ -1,6 +1,7 @@ /// import angular from 'angular'; +import _ from 'lodash'; export class SubmenuCtrl { annotations: any; @@ -8,7 +9,11 @@ export class SubmenuCtrl { dashboard: any; /** @ngInject */ - constructor(private $rootScope, private templateValuesSrv, private dynamicDashboardSrv) { + constructor(private $rootScope, + private templateValuesSrv, + private templateSrv, + private dynamicDashboardSrv, + private $location) { this.annotations = this.dashboard.templating.list; this.variables = this.dashboard.templating.list; } diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index cda5e69f006..2138dd37438 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -8,7 +8,7 @@ function (angular, _, $) { 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 // 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() { var urlState = self.getQueryStringState(); if (self.needsSync(urlState)) { @@ -40,10 +53,26 @@ function (angular, _, $) { self.registerPanel(payload.scope); }); - this.update(this.getQueryStringState(), true); + this.update(this.getQueryStringState()); 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() { if (!this.state.panelId) { return; } @@ -63,6 +92,7 @@ function (angular, _, $) { state.fullscreen = state.fullscreen ? true : null; state.edit = (state.edit === "true" || state.edit === true) || null; state.editview = state.editview || null; + state.org = contextSrv.user.orgId; return state; }; @@ -70,10 +100,11 @@ function (angular, _, $) { var urlState = _.clone(this.state); urlState.fullscreen = this.state.fullscreen ? true : null; urlState.edit = this.state.edit ? true : null; + urlState.org = contextSrv.user.orgId; return urlState; }; - DashboardViewState.prototype.update = function(state, skipUrlSync) { + DashboardViewState.prototype.update = function(state) { _.extend(this.state, state); this.dashboard.meta.fullscreen = this.state.fullscreen; @@ -83,10 +114,7 @@ function (angular, _, $) { this.state.edit = null; } - if (!skipUrlSync) { - $location.search(this.serializeToUrl()); - } - + $location.search(this.serializeToUrl()); this.syncState(); }; diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 994eaa5fd5b..e47e3444564 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -8,6 +8,7 @@ import $ from 'jquery'; const TITLE_HEIGHT = 25; const EMPTY_TITLE_HEIGHT = 9; const PANEL_PADDING = 5; +const PANEL_BORDER = 2; import {Emitter} from 'app/core/core'; @@ -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?) { diff --git a/public/app/features/panel/partials/soloPanel.html b/public/app/features/panel/partials/soloPanel.html index e76ad724343..131d99d4389 100644 --- a/public/app/features/panel/partials/soloPanel.html +++ b/public/app/features/panel/partials/soloPanel.html @@ -1,9 +1,5 @@ -
-
-
-
- - -
-
+
+ +
+
diff --git a/public/app/features/panel/solo_panel_ctrl.js b/public/app/features/panel/solo_panel_ctrl.js index 313c2014de2..355d5e8b265 100644 --- a/public/app/features/panel/solo_panel_ctrl.js +++ b/public/app/features/panel/solo_panel_ctrl.js @@ -17,6 +17,10 @@ function (angular, $) { var params = $location.search(); panelId = parseInt(params.panelId); + // add fullscreen param; + params.fullscreen = true; + $location.search(params); + dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug).then(function(result) { $scope.initDashboard(result, $scope); }); diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index 43bda1db05f..5efebc21e30 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -25,6 +25,7 @@ function (angular, _) { {value: "interval", text: "Interval"}, {value: "datasource", text: "Data source"}, {value: "custom", text: "Custom"}, + {value: "constant", text: "Constant"}, ]; $scope.refreshOptions = [ @@ -141,15 +142,34 @@ function (angular, _) { $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 () { if ($scope.current.type === 'interval') { $scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d'; + $scope.current.refresh = 0; } if ($scope.current.type === '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') { $scope.current.query = $scope.datasourceTypes[0].value; $scope.current.regex = ''; diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index fc387ff44cf..19f9c25715b 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -152,6 +152,14 @@
+
+
Constant options
+
+ Value + +
+
+
Query Options
@@ -214,7 +222,7 @@
-
+
Selection Options
\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1"); } + this.luceneFormat = function(value) { + if (typeof value === 'string') { + return luceneEscape(value); + } + var quotedValues = _.map(value, function(val) { + return '\"' + luceneEscape(val) + '\"'; + }); + return '(' + quotedValues.join(' OR ') + ')'; + }; + this.formatValue = function(value, format, variable) { // for some scopedVars there is no variable variable = variable || {}; @@ -60,13 +70,7 @@ function (angular, _) { return '(' + escapedValues.join('|') + ')'; } case "lucene": { - if (typeof value === 'string') { - return luceneEscape(value); - } - var quotedValues = _.map(value, function(val) { - return '\"' + luceneEscape(val) + '\"'; - }); - return '(' + quotedValues.join(' OR ') + ')'; + return this.luceneFormat(value, format, variable); } case "pipe": { if (typeof value === 'string') { @@ -97,8 +101,11 @@ function (angular, _) { if (!str) { return false; } - var match = this._regex.exec(str); - return match && (match[1] === variableName || match[2] === variableName); + + variableName = regexEscape(variableName); + var findVarRegex = new RegExp('\\$(' + variableName + ')(?:\\W|$)|\\[\\[(' + variableName + ')\\]\\]', 'g'); + var match = findVarRegex.exec(str); + return match !== null; }; this.highlightVariablesAsHtml = function(str) { diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index d44f07ed366..7bbb921735a 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -79,7 +79,6 @@ function (angular, _, kbn) { else if (variable.refresh === 1 || variable.refresh === 2) { return self.updateOptions(variable).then(function() { if (_.isEmpty(variable.current) && variable.options.length) { - console.log("setting current for %s", variable.name); self.setVariableValue(variable, variable.options[0]); } lock.resolve(); @@ -102,7 +101,10 @@ function (angular, _, kbn) { } return promise.then(function() { - var option = _.findWhere(variable.options, { text: urlValue }); + var option = _.find(variable.options, function(op) { + return op.text === urlValue || op.value === urlValue; + }); + option = option || { text: urlValue, value: urlValue }; self.updateAutoInterval(variable); @@ -125,8 +127,8 @@ function (angular, _, kbn) { this.setVariableValue = function(variable, option, initPhase) { variable.current = angular.copy(option); - if (_.isArray(variable.current.value)) { - variable.current.text = variable.current.value.join(' + '); + if (_.isArray(variable.current.text)) { + variable.current.text = variable.current.text.join(' + '); } self.selectOptionsForCurrentValue(variable); @@ -166,6 +168,11 @@ function (angular, _, kbn) { return; } + if (variable.type === 'constant') { + variable.options = [{text: variable.query, value: variable.query}]; + return; + } + // extract options in comma seperated string variable.options = _.map(variable.query.split(/[,]+/), function(text) { return { text: text.trim(), value: text.trim() }; @@ -173,6 +180,7 @@ function (angular, _, kbn) { if (variable.type === 'interval') { self.updateAutoInterval(variable); + return; } if (variable.type === 'custom' && variable.includeAll) { @@ -224,6 +232,7 @@ function (angular, _, kbn) { this.selectOptionsForCurrentValue = function(variable) { var i, y, value, option; + var selected = []; for (i = 0; i < variable.options.length; i++) { option = variable.options[i]; @@ -233,28 +242,44 @@ function (angular, _, kbn) { value = variable.current.value[y]; if (option.value === value) { option.selected = true; + selected.push(option); } } } else if (option.value === variable.current.value) { option.selected = true; + selected.push(option); } } + + return selected; }; this.validateVariableSelectionState = function(variable) { if (!variable.current) { if (!variable.options.length) { return; } - return self.setVariableValue(variable, variable.options[0], true); + return self.setVariableValue(variable, variable.options[0], false); } if (_.isArray(variable.current.value)) { - self.selectOptionsForCurrentValue(variable); + 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 { var currentOption = _.findWhere(variable.options, {text: variable.current.text}); if (currentOption) { - return self.setVariableValue(variable, currentOption, true); + return self.setVariableValue(variable, currentOption, false); } else { - if (!variable.options.length) { return; } + if (!variable.options.length) { return $q.when(null); } return self.setVariableValue(variable, variable.options[0]); } } @@ -313,6 +338,14 @@ function (angular, _, kbn) { var value = item.value || item.text; var text = item.text || item.value; + if (_.isNumber(value)) { + value = value.toString(); + } + + if (_.isNumber(text)) { + text = text.toString(); + } + if (regex) { matches = regex.exec(value); if (!matches) { continue; } diff --git a/public/app/partials/login.html b/public/app/partials/login.html index ff8d22451a8..8143ff35125 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -73,14 +73,5 @@
-
- - -
diff --git a/public/app/partials/signup_step2.html b/public/app/partials/signup_step2.html index 7b72c2357b9..afb6d0d4fc4 100644 --- a/public/app/partials/signup_step2.html +++ b/public/app/partials/signup_step2.html @@ -67,7 +67,6 @@ - diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index c7c2351d0cc..d5926be3f81 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -78,7 +78,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes range[timeField]["format"] = "epoch_millis"; } - var queryInterpolated = templateSrv.replace(queryString); + var queryInterpolated = templateSrv.replace(queryString, {}, 'lucene'); var filter = { "bool": { "must": [{ "range": range }] } }; var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } }; var data = { @@ -204,6 +204,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; + function escapeForJson(value) { + return value.replace(/\"/g, '\\"'); + } + + function luceneThenJsonFormat(value) { + return escapeForJson(templateSrv.luceneFormat(value)); + } + this.getFields = function(query) { return this._get('/_mapping').then(function(res) { var fields = {}; @@ -246,7 +254,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var header = this.getQueryHeader('count', range.from, range.to); var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef)); - esQuery = esQuery.replace("$lucene_query", queryDef.query || '*'); + esQuery = esQuery.replace("$lucene_query", escapeForJson(queryDef.query || '*')); esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf()); esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf()); esQuery = header + '\n' + esQuery + '\n'; @@ -260,7 +268,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }; this.metricFindQuery = function(query) { - query = templateSrv.replace(query); + query = templateSrv.replace(query, {}, luceneThenJsonFormat); query = angular.fromJson(query); if (!query) { return $q.when([]); diff --git a/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html b/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html index 75979226963..36e914d06e0 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html @@ -70,9 +70,9 @@
-
+
- +
@@ -88,7 +88,7 @@
- +
diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index f1c58f6a61d..cbd7b1ba24b 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -256,23 +256,14 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return this.renderTemplate(options.legendFormat, labelData) || '{}'; }; - this.renderTemplate = function(format, data) { - var originalSettings = _.templateSettings; - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; - - var template = _.template(templateSrv.replace(format)); - var result; - try { - result = template(data); - } catch (e) { - result = null; - } - - _.templateSettings = originalSettings; - - return result; + this.renderTemplate = function(aliasPattern, aliasData) { + var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; + return aliasPattern.replace(aliasRegex, function(match, g1) { + if (aliasData[g1]) { + return aliasData[g1]; + } + return g1; + }); }; this.getOriginalMetricName = function(labelData) { diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 94340804988..92cec328d87 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -58,6 +58,10 @@ class PrometheusQueryCtrl extends QueryCtrl { updateLink() { var range = this.panelCtrl.range; + if (!range) { + return; + } + var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000); var endTime = range.to.utc().format('YYYY-MM-DD HH:mm'); var expr = { diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 562bf71c947..86ad3b5f025 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -66,7 +66,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) { function getLegendHeight(panelHeight) { if (!panel.legend.show || panel.legend.rightSide) { - return 2; + return 0; } if (panel.legend.alignAsTable) { diff --git a/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts b/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts index dfce4031772..d00c90ae6a1 100644 --- a/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_ctrl_specs.ts @@ -22,8 +22,8 @@ describe('GraphCtrl', function() { describe('msResolution with second resolution timestamps', function() { beforeEach(function() { var data = [ - { target: 'test.cpu1', datapoints: [[1234567890, 45], [1234567899, 60]]}, - { target: 'test.cpu2', datapoints: [[1236547890, 55], [1234456709, 90]]} + { target: 'test.cpu1', datapoints: [[45, 1234567890], [60, 1234567899]]}, + { target: 'test.cpu2', datapoints: [[55, 1236547890], [90, 1234456709]]} ]; ctx.ctrl.panel.tooltip.msResolution = false; ctx.ctrl.onDataReceived(data); @@ -37,8 +37,8 @@ describe('GraphCtrl', function() { describe('msResolution with millisecond resolution timestamps', function() { beforeEach(function() { var data = [ - { target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]}, - { target: 'test.cpu2', datapoints: [[1236547890001, 55], [1234456709000, 90]]} + { target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]}, + { target: 'test.cpu2', datapoints: [[55, 1236547890001], [90, 1234456709000]]} ]; ctx.ctrl.panel.tooltip.msResolution = false; ctx.ctrl.onDataReceived(data); @@ -52,8 +52,8 @@ describe('GraphCtrl', function() { describe('msResolution with millisecond resolution timestamps but with trailing zeroes', function() { beforeEach(function() { var data = [ - { target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]}, - { target: 'test.cpu2', datapoints: [[1236547890000, 55], [1234456709000, 90]]} + { target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]}, + { target: 'test.cpu2', datapoints: [[55, 1236547890000], [90, 1234456709000]]} ]; ctx.ctrl.panel.tooltip.msResolution = false; ctx.ctrl.onDataReceived(data); @@ -67,9 +67,9 @@ describe('GraphCtrl', function() { describe('msResolution with millisecond resolution timestamps in one of the series', function() { beforeEach(function() { var data = [ - { target: 'test.cpu1', datapoints: [[1234567890000, 45], [1234567899000, 60]]}, - { target: 'test.cpu2', datapoints: [[1236547890010, 55], [1234456709000, 90]]}, - { target: 'test.cpu3', datapoints: [[1236547890000, 65], [1234456709000, 120]]} + { target: 'test.cpu1', datapoints: [[45, 1234567890000], [60, 1234567899000]]}, + { target: 'test.cpu2', datapoints: [[55, 1236547890010], [90, 1234456709000]]}, + { target: 'test.cpu3', datapoints: [[65, 1236547890000], [120, 1234456709000]]} ]; ctx.ctrl.panel.tooltip.msResolution = false; ctx.ctrl.onDataReceived(data); diff --git a/public/app/plugins/panel/singlestat/module.html b/public/app/plugins/panel/singlestat/module.html index 599e8724159..75a35374566 100644 --- a/public/app/plugins/panel/singlestat/module.html +++ b/public/app/plugins/panel/singlestat/module.html @@ -1,4 +1,3 @@
-
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index de272478fbc..59da49ca3d5 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -325,6 +325,9 @@ class SingleStatCtrl extends MetricsPanelCtrl { } function addGauge() { + var width = elem.width(); + var height = elem.height(); + ctrl.invalidGaugeRange = false; if (panel.gauge.minValue > panel.gauge.maxValue) { ctrl.invalidGaugeRange = true; @@ -332,8 +335,6 @@ class SingleStatCtrl extends MetricsPanelCtrl { } var plotCanvas = $('
'); - var width = elem.width(); - var height = elem.height(); var plotCss = { top: '10px', margin: 'auto', diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index bf78bd496a3..047e50faa73 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -269,3 +269,7 @@ $checkboxImageUrl: '../img/checkbox.png'; $card-background: linear-gradient(135deg, #2f2f2f, #262626); $card-background-hover: linear-gradient(135deg, #343434, #262626); $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3); + +// footer +$footer-link-color: $gray-1; +$footer-link-hover: $gray-4; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 1494f59a2fd..4e389166cef 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -293,3 +293,7 @@ $checkboxImageUrl: '../img/checkbox_white.png'; $card-background: linear-gradient(135deg, $gray-5, $gray-6); $card-background-hover: linear-gradient(135deg, $gray-6, $gray-7); $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1); + +// footer +$footer-link-color: $gray-3; +$footer-link-hover: $dark-5; diff --git a/public/sass/components/_footer.scss b/public/sass/components/_footer.scss index 9f2674980b0..8f3b9186d15 100644 --- a/public/sass/components/_footer.scss +++ b/public/sass/components/_footer.scss @@ -1,9 +1,38 @@ -.grafana-version-info { - position: absolute; - bottom: 2px; - left: 3px; - font-size: 80%; - color: darken($gray-1, 25%); - a { color: darken($gray-1, 25%); } +.page-dashboard .footer { + display: none; } +.footer { + color: $footer-link-color; + padding: 5rem 0 1rem 0; + font-size: $font-size-xs; + width: 98%; /* was causing horiz scrollbars - need to examine */ + + a { + color: $footer-link-color; + + &:hover { + color: $footer-link-hover; + } + } + + ul { + list-style: none; + } + + li { + display: inline-block; + padding-right: 2px; + &:after { + content: ' | '; + padding-left: 2px; + } + } + + li:last-child { + &:after { + padding-left: 0; + content: ''; + } + } +} diff --git a/public/sass/components/_panel_singlestat.scss b/public/sass/components/_panel_singlestat.scss index fac2f716674..48d0a29f4b8 100644 --- a/public/sass/components/_panel_singlestat.scss +++ b/public/sass/components/_panel_singlestat.scss @@ -5,7 +5,6 @@ } .singlestat-panel-value-container { - padding: 20px; display: table-cell; vertical-align: middle; text-align: center; diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index 8de65053888..6203f4be083 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -4,7 +4,7 @@ } .main-view { - height: 100%; + // height: 100%; REMOVED FOR FOOTER TRW } .page-container { diff --git a/public/test/core/time_series_specs.js b/public/test/core/time_series_specs.js index 9d273c86b94..034e872e2f1 100644 --- a/public/test/core/time_series_specs.js +++ b/public/test/core/time_series_specs.js @@ -56,7 +56,7 @@ define([ }); }); - describe('can detect if serie contains ms precision', function() { + describe('can detect if series contains ms precision', function() { var fakedata; beforeEach(function() { @@ -64,13 +64,13 @@ define([ }); it('missing datapoint with ms precision', function() { - fakedata.datapoints[0] = [1234567890000, 1337]; + fakedata.datapoints[0] = [1337, 1234567890000]; series = new TimeSeries(fakedata); expect(series.isMsResolutionNeeded()).to.be(false); }); it('contains datapoint with ms precision', function() { - fakedata.datapoints[0] = [1236547890001, 1337]; + fakedata.datapoints[0] = [1337, 1236547890001]; series = new TimeSeries(fakedata); expect(series.isMsResolutionNeeded()).to.be(true); }); diff --git a/public/test/core/utils/kbn_specs.js b/public/test/core/utils/kbn_specs.js index 7e75e880546..0a2ad165253 100644 --- a/public/test/core/utils/kbn_specs.js +++ b/public/test/core/utils/kbn_specs.js @@ -147,5 +147,11 @@ define([ var str = kbn.calculateInterval(range, 1000, '>10s'); 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'); + }); }); }); diff --git a/public/test/specs/dashboardViewStateSrv-specs.js b/public/test/specs/dashboardViewStateSrv-specs.js index b33c7156392..202a43670b1 100644 --- a/public/test/specs/dashboardViewStateSrv-specs.js +++ b/public/test/specs/dashboardViewStateSrv-specs.js @@ -5,8 +5,20 @@ define([ describe('when updating view state', function() { var viewState, location; + var timeSrv = {}; + var templateSrv = {}; + var contextSrv = { + user: { + orgId: 19 + } + }; 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) { $rootScope.onAppEvent = function() {}; @@ -17,9 +29,9 @@ define([ describe('to fullscreen true and edit true', 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); - 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.state.fullscreen).to.be(true); }); @@ -29,7 +41,7 @@ define([ it('should remove params from query string', function() { viewState.update({fullscreen: true, panelId: 1, edit: true}); 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.state.fullscreen).to.be(null); }); diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js index fd7247cbd81..a592cc7a152 100644 --- a/public/test/specs/templateSrv-specs.js +++ b/public/test/specs/templateSrv-specs.js @@ -141,8 +141,8 @@ define([ }); it('slash should be properly escaped in regex format', function() { - var result = _templateSrv.formatValue('Gi3/14', 'regex'); - expect(result).to.be('Gi3\\/14'); + var result = _templateSrv.formatValue('Gi3/14', 'regex'); + expect(result).to.be('Gi3\\/14'); }); }); @@ -200,6 +200,15 @@ define([ expect(contains).to.be(true); }); + it('should find it when part of segment', function() { + var contains = _templateSrv.containsVariable('metrics.$env.$group-*', 'group'); + expect(contains).to.be(true); + }); + + it('should find it its the only thing', function() { + var contains = _templateSrv.containsVariable('$env', 'env'); + expect(contains).to.be(true); + }); }); describe('updateTemplateData with simple value', function() { diff --git a/public/test/specs/templateValuesSrv-specs.js b/public/test/specs/templateValuesSrv-specs.js index 2edcd03da9a..7a8d8c1fccb 100644 --- a/public/test/specs/templateValuesSrv-specs.js +++ b/public/test/specs/templateValuesSrv-specs.js @@ -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) { scenario.setup(function() { scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' }; diff --git a/public/views/index.html b/public/views/index.html index c4bffae5019..70981fb7784 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -39,6 +39,41 @@
+ diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index e4bb83054a7..f40a42380ad 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -38,10 +38,11 @@ function checkIsReady() { var canvas = page.evaluate(function() { if (!window.angular) { return false; } - var body = window.angular.element(document.body); // 1 - if (!body.scope) { return false; } + var body = window.angular.element(document.body); + 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.performance) { return false; } var panelsToLoad = window.angular.element('div.panel').length; @@ -59,6 +60,7 @@ width: bb.width, height: bb.height }; + page.render(params.png); phantom.exit(); }