Merge branch 'master' into query-editor-style

pull/4842/head
Torkel Ödegaard 9 years ago
commit 68bd82f1b6
  1. 35
      CHANGELOG.md
  2. 5
      README.md
  3. 7
      conf/defaults.ini
  4. 7
      conf/sample.ini
  5. 29
      docs/sources/http_api/data_source.md
  6. 6
      docs/sources/installation/debian.md
  7. 8
      docs/sources/installation/rpm.md
  8. 2
      docs/sources/installation/windows.md
  9. 4
      docs/sources/plugins/development.md
  10. 2
      docs/sources/plugins/index.md
  11. 8
      docs/sources/plugins/installation.md
  12. 2
      docs/sources/plugins/panels.md
  13. 5
      karma.conf.js
  14. 3
      latest.json
  15. 6
      package.json
  16. 18
      packaging/publish/publish.sh
  17. 10
      pkg/api/api.go
  18. 1
      pkg/api/avatar/avatar.go
  19. 17
      pkg/api/dtos/plugins.go
  20. 8
      pkg/api/frontendsettings.go
  21. 46
      pkg/api/gnetproxy.go
  22. 18
      pkg/api/plugins.go
  23. 18
      pkg/api/user.go
  24. 25
      pkg/cmd/grafana-cli/commands/commands.go
  25. 19
      pkg/cmd/grafana-cli/commands/install_command.go
  26. 2
      pkg/cmd/grafana-cli/commands/upgrade_all_command.go
  27. 28
      pkg/cmd/grafana-cli/main.go
  28. 2
      pkg/cmd/grafana-server/main.go
  29. 4
      pkg/metrics/report_usage.go
  30. 17
      pkg/models/stats.go
  31. 7
      pkg/plugins/frontend_plugin.go
  32. 4
      pkg/plugins/models.go
  33. 4
      pkg/plugins/plugins.go
  34. 119
      pkg/plugins/update_checker.go
  35. 14
      pkg/services/sqlstore/migrations/preferences_mig.go
  36. 7
      pkg/services/sqlstore/stats.go
  37. 2
      pkg/setting/setting.go
  38. 13
      public/app/core/components/colorpicker.ts
  39. 3
      public/app/core/components/dashboard_selector.ts
  40. 23
      public/app/core/components/info_popover.ts
  41. 4
      public/app/core/components/sidemenu/sidemenu.html
  42. 11
      public/app/core/components/sidemenu/sidemenu.ts
  43. 32
      public/app/core/components/switch.ts
  44. 4
      public/app/core/controllers/login_ctrl.js
  45. 2
      public/app/core/directives/plugin_component.ts
  46. 5
      public/app/core/services/popover_srv.ts
  47. 4
      public/app/core/time_series2.ts
  48. 37
      public/app/core/utils/emitter.ts
  49. 4
      public/app/features/admin/partials/stats.html
  50. 1
      public/app/features/annotations/editor_ctrl.js
  51. 2
      public/app/features/dashboard/dashboardSrv.js
  52. 3
      public/app/features/dashboard/dashnav/dashnav.html
  53. 8
      public/app/features/dashboard/keybindings.js
  54. 11
      public/app/features/dashboard/partials/settings.html
  55. 52
      public/app/features/dashboard/partials/shareModal.html
  56. 12
      public/app/features/dashboard/timepicker/timepicker.html
  57. 26
      public/app/features/dashboard/timepicker/timepicker.ts
  58. 1
      public/app/features/dashboard/unsavedChangesSrv.js
  59. 7
      public/app/features/org/prefs_control.ts
  60. 12
      public/app/features/panel/metrics_panel_ctrl.ts
  61. 1
      public/app/features/panel/panel_ctrl.ts
  62. 2
      public/app/features/panel/partials/panelTime.html
  63. 13
      public/app/features/plugins/ds_edit_ctrl.ts
  64. 20
      public/app/features/plugins/partials/ds_edit.html
  65. 55
      public/app/features/plugins/partials/ds_http_settings.html
  66. 5
      public/app/features/plugins/partials/ds_list.html
  67. 7
      public/app/features/plugins/partials/plugin_edit.html
  68. 9
      public/app/features/plugins/partials/plugin_list.html
  69. 21
      public/app/features/plugins/partials/update_instructions.html
  70. 15
      public/app/features/plugins/plugin_edit_ctrl.ts
  71. 101
      public/app/features/templating/partials/editor.html
  72. 10
      public/app/features/templating/templateValuesSrv.js
  73. 8
      public/app/headers/common.d.ts
  74. 73
      public/app/headers/es6-promise/es6-promise.d.ts
  75. 8
      public/app/headers/es6-shim/es6-shim.d.ts
  76. 24
      public/app/partials/help_modal.html
  77. 6
      public/app/partials/login.html
  78. 3
      public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html
  79. 29
      public/app/plugins/datasource/cloudwatch/partials/config.html
  80. 16
      public/app/plugins/datasource/influxdb/datasource.ts
  81. 2
      public/app/plugins/datasource/opentsdb/config_ctrl.ts
  82. 2
      public/app/plugins/datasource/prometheus/datasource.ts
  83. 58
      public/app/plugins/panel/dashlist/editor.html
  84. 27
      public/app/plugins/panel/dashlist/module.html
  85. 104
      public/app/plugins/panel/dashlist/module.ts
  86. 6
      public/app/plugins/panel/graph/graph.js
  87. 22
      public/app/plugins/panel/graph/graph_tooltip.js
  88. 5
      public/app/plugins/panel/graph/legend.js
  89. 23
      public/app/plugins/panel/graph/module.ts
  90. 4
      public/app/plugins/panel/graph/series_overrides_ctrl.js
  91. 2
      public/app/plugins/panel/graph/tab_display.html
  92. 3
      public/app/plugins/panel/graph/tab_legend.html
  93. 2
      public/app/plugins/panel/pluginlist/README.md
  94. 40
      public/app/plugins/panel/pluginlist/editor.html
  95. 119
      public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg
  96. 30
      public/app/plugins/panel/pluginlist/module.html
  97. 74
      public/app/plugins/panel/pluginlist/module.ts
  98. 16
      public/app/plugins/panel/pluginlist/plugin.json
  99. 6
      public/app/plugins/panel/singlestat/module.ts
  100. 11
      public/app/plugins/panel/table/module.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,4 +1,34 @@
# 3.0.0-beta2 (unreleased)
# 3.0.0-beta5 (2016-04-15)
### Bug fixes
* **grafana-cli**: Fixed issue grafana-cli tool, did not detect the right plugin dir, fixes [#4723](https://github.com/grafana/grafana/issues/4723)
* **Graph**: Fixed issue with light theme text color issue in tooltip, fixes [#4702](https://github.com/grafana/grafana/issues/4702)
* **Snapshot**: Fixed issue with empty snapshots, fixes [#4706](https://github.com/grafana/grafana/issues/4706)
# 3.0.0-beta4 (2016-04-13)
### Bug fixes
* **Home dashboard**: Fixed issue with permission denied error on home dashboard, fixes [#4686](https://github.com/grafana/grafana/issues/4686)
* **Templating**: Fixed issue templating variables that use regex extraction, fixes [#4672](https://github.com/grafana/grafana/issues/4672)
# 3.0.0-beta3 (2016-04-12)
### Enhancements
* **InfluxDB**: Changed multi query encoding to work with InfluxDB 0.11 & 0.12, closes [#4533](https://github.com/grafana/grafana/issues/4533)
* **Timepicker**: Add arrows and shortcuts for moving back and forth in current dashboard, closes [#119](https://github.com/grafana/grafana/issues/119)
### Bug fixes
* **Postgres**: Fixed page render crash when using postgres, fixes [#4558](https://github.com/grafana/grafana/issues/4558)
* **Table panel**: Fixed table panel bug when trying to show annotations in table panel, fixes [#4563](https://github.com/grafana/grafana/issues/4563)
* **App Config**: Fixed app config issue showing content of other app config, fixes [#4575](https://github.com/grafana/grafana/issues/4575)
* **Graph Panel**: Fixed legend option max not updating, fixes [#4601](https://github.com/grafana/grafana/issues/4601)
* **Graph Panel**: Fixed issue where newly added graph panels shared same axes config, fixes [#4582](https://github.com/grafana/grafana/issues/4582)
* **Graph Panel**: Fixed issue with axis labels overlapping Y-axis, fixes [#4626](https://github.com/grafana/grafana/issues/4626)
* **InfluxDB**: Fixed issue with templating query containing template variable, fixes [#4602](https://github.com/grafana/grafana/issues/4602)
* **Graph Panel**: Fixed issue with hiding series and stacking, fixes [#4557](https://github.com/grafana/grafana/issues/4557)
* **Graph Panel**: Fixed issue with legend height in table mode with few series, affected iframe embedding as well, fixes [#4640](https://github.com/grafana/grafana/issues/4640)
# 3.0.0-beta2 (2016-04-04)
### New Features (introduces since 3.0-beta1)
* **Preferences**: Set home dashboard on user and org level, closes [#1678](https://github.com/grafana/grafana/issues/1678)
@ -9,9 +39,8 @@
* **Dashboard**: Fixed dashboard panel layout for mobile devices, fixes [#4529](https://github.com/grafana/grafana/issues/4529)
* **Table Panel**: Fixed issue with table panel sort, fixes [#4532](https://github.com/grafana/grafana/issues/4532)
* **Page Load Crash**: A Datasource with null jsonData would make Grafana fail to load page, fixes [#4536](https://github.com/grafana/grafana/issues/4536)
* **Metrics tab**: Fix for missing datasource name in datasource selector, fixes [#4540](https://github.com/grafana/grafana/issues/4540)
* **Metrics tab**: Fix for missing datasource name in datasource selector, fixes [#4541](https://github.com/grafana/grafana/issues/4540)
* **Graph**: Fix legend in table mode with series on right-y axis, fixes [#4551](https://github.com/grafana/grafana/issues/4551), [#1145](https://github.com/grafana/grafana/issues/1145)
* **Password**: Password reset link/page did not work, fixes [#4542](https://github.com/grafana/grafana/issues/4542)
# 3.0.0-beta1 (2016-03-31)

@ -1,8 +1,10 @@
[Grafana](http://grafana.org) [![Circle CI](https://circleci.com/gh/grafana/grafana.svg?style=svg)](https://circleci.com/gh/grafana/grafana) [![Coverage Status](https://coveralls.io/repos/grafana/grafana/badge.png)](https://coveralls.io/r/grafana/grafana) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/grafana/grafana?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[Grafana](http://grafana.org) [![Circle CI](https://circleci.com/gh/grafana/grafana.svg?style=svg)](https://circleci.com/gh/grafana/grafana) [![Coverage Status](https://coveralls.io/repos/grafana/grafana/badge.png)](https://coveralls.io/r/grafana/grafana)
================
[Website](http://grafana.org) |
[Twitter](https://twitter.com/grafana) |
[IRC](https://webchat.freenode.net/?channels=grafana) |
![](https://brandfolder.com/api/favicon/icon?size=16&domain=www.slack.com)
[Slack](http://slack.raintank.io) |
[Email](mailto:contact@grafana.org)
Grafana is an open source, feature rich metrics dashboard and graph editor for
@ -77,6 +79,7 @@ the latest master builds [here](http://grafana.org/download/builds)
- Go 1.5
- NodeJS
- [Godep](https://github.com/tools/godep)
### Get Code

@ -111,6 +111,13 @@ gc_interval_time = 86400
# Change this option to false to disable reporting.
reporting_enabled = true
# Set to false to disable all checks to https://grafana.net
# for new vesions (grafana itself and plugins), check is used
# in some UI views to notify that grafana or plugin update exists
# This option does not cause any auto updates, nor send any information
# only a GET request to http://grafana.net to get latest versions
check_for_updates = true
# Google Analytics universal tracking code, only enabled if you specify an id here
google_analytics_ua_id =

@ -100,6 +100,13 @@
# Change this option to false to disable reporting.
;reporting_enabled = true
# Set to false to disable all checks to https://grafana.net
# for new vesions (grafana itself and plugins), check is used
# in some UI views to notify that grafana or plugin update exists
# This option does not cause any auto updates, nor send any information
# only a GET request to http://grafana.net to get latest versions
check_for_updates = true
# Google Analytics universal tracking code, only enabled if you specify an id here
;google_analytics_ua_id =

@ -207,35 +207,6 @@ page_keywords: grafana, admin, http, api, documentation, datasource
{"message":"Data source deleted"}
## Available data source types
`GET /api/datasources/plugins`
**Example Request**:
GET /api/datasources/plugins HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
**Example Response**:
HTTP/1.1 200
Content-Type: application/json
{
"grafana":{
"metrics":true,"module":"plugins/datasource/grafana/datasource",
"name":"Grafana (for testing)",
"partials":{
"query":"app/plugins/datasource/grafana/partials/query.editor.html"
},
"pluginType":"datasource",
"serviceName":"GrafanaDatasource",
"type":"grafana"
}
}
## Data source proxy calls
`GET /api/datasources/proxy/:datasourceId/*`

@ -11,7 +11,7 @@ page_keywords: grafana, installation, debian, ubuntu, guide
Description | Download
------------ | -------------
Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta11459429091_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb)
Beta .deb for Debian-based Linux | [grafana_3.0.0-beta51460725904_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta51460725904_amd64.deb)
## Install Stable
@ -21,9 +21,9 @@ Beta .deb for Debian-based Linux | [grafana_3.0.0-beta11459429091_amd64.deb](h
## Install 3.0 Beta
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta11459429091_amd64.deb
$ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta51460725904_amd64.deb
$ sudo apt-get install -y adduser libfontconfig
$ sudo dpkg -i grafana_3.0.0-beta11459429091_amd64.deb
$ sudo dpkg -i grafana_3.0.0-beta51460725904_amd64.deb
## APT Repository

@ -11,7 +11,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
Description | Download
------------ | -------------
Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta11459429091.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm)
Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta51460725904.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta51460725904§.x86_64.rpm)
## Install Stable Release from package file
@ -34,18 +34,18 @@ Or install manually using `rpm`.
You can install Grafana using Yum directly.
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta11459429091.x86_64.rpm
$ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta51460725904.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-3.0.0-beta11459429091.x86_64.rpm
$ sudo rpm -Uvh grafana-3.0.0-beta51460725904.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-3.0.0-beta11459429091.x86_64.rpm
$ sudo rpm -i --nodeps grafana-3.0.0-beta51460725904.x86_64.rpm
## Install via YUM Repository

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

@ -10,7 +10,7 @@ From grafana 3.0 it's very easy to develop your own plugins and share them with
## Short version
1. [Setup grafana](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md)
1. [Setup grafana](http://docs.grafana.org/project/building_from_source/)
2. Clone an example plugin into ```/var/lib/grafana/plugins``` or `data/plugins` (relative to grafana git repo if your running development version from source dir)
3. Code away!
@ -34,7 +34,7 @@ and [apps](./apps.md) plugins in the documentation.
## Start developing your plugin
There are two ways that you can start developing a Grafana plugin.
1. Setup a Grafana development environment. [(described here)](https://github.com/grafana/grafana/blob/master/DEVELOPMENT.md) and place your plugin in the ```data/plugins``` folder.
1. Setup a Grafana development environment. [(described here)](http://docs.grafana.org/project/building_from_source/) and place your plugin in the ```data/plugins``` folder.
2. Install Grafana and place your plugin in the plugins directory which is set in your [config file](../installation/configuration.md). By default this is `/var/lib/grafana/plugins` on Linux systems.
3. Place your plugin directory anywhere you like and specify it grafana.ini.

@ -9,7 +9,7 @@ page_keywords: grafana, plugins, documentation
From Grafana 3.0 not only datasource plugins are supported but also panel plugins and apps.
Having panels as plugins make it easy to create and add any kind of panel, to show your data
or improve your favorite dashboards. Apps is something new in Grafana that enables
bundling of datasources, panels, dashboards and Grafana pages into a cohesive experiance.
bundling of datasources, panels, dashboards and Grafana pages into a cohesive experience.
Grafana already have a strong community of contributors and plugin developers.
By making it easier to develop and install plugins we hope that the community

@ -28,14 +28,14 @@ List installed plugins
grafana-cli plugins ls
```
Upgrade all installed plugins
Update all installed plugins
```
grafana-cli plugins upgrade-all
grafana-cli plugins update-all
```
Upgrade one plugin
Update one plugin
```
grafana-cli plugins upgrade <plugin-id>
grafana-cli plugins update <plugin-id>
```
Remove one plugin

@ -7,7 +7,7 @@ page_keywords: grafana, plugins, documentation
# Panels
Panels are the main bulding block of dashboards.
Panels are the main building blocks of dashboards.
## Panel development

@ -24,9 +24,10 @@ module.exports = function(config) {
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['PhantomJS'],
captureTimeout: 2000,
captureTimeout: 20000,
singleRun: true,
autoWatchBatchDelay: 1000,
autoWatchBatchDelay: 10000,
browserNoActivityTimeout: 60000,
});

@ -1,3 +1,4 @@
{
"version": "2.1.1"
"stable": "2.6.0",
"testing": "3.0.0-beta5"
}

@ -4,13 +4,12 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "3.0.0-beta2",
"version": "3.0.0-beta6",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
},
"devDependencies": {
"angular2": "2.0.0-beta.12",
"zone.js": "^0.6.6",
"autoprefixer": "^6.3.3",
"es6-promise": "^3.0.2",
@ -54,7 +53,7 @@
"mocha": "2.3.4",
"phantomjs-prebuilt": "^2.1.3",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.2",
"rxjs": "5.0.0-beta.4",
"sass-lint": "^1.5.0",
"systemjs": "0.19.24"
},
@ -68,6 +67,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"eventemitter3": "^1.2.0",
"grunt-jscs": "~1.5.x",
"grunt-sass-lint": "^0.1.0",
"grunt-sync": "^0.4.1",

@ -1,21 +1,21 @@
#! /usr/bin/env bash
deb_ver=3.0.0-beta11459429091
rpm_ver=3.0.0-beta11459429091
deb_ver=3.0.0-beta51460725904
rpm_ver=3.0.0-beta51460725904
#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/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-${version}-1.x86_64.rpm

@ -30,6 +30,7 @@ func Register(r *macaron.Macaron) {
// authed views
r.Get("/profile/", reqSignedIn, Index)
r.Get("/profile/password", reqSignedIn, Index)
r.Get("/profile/switch-org/:id", reqSignedIn, ChangeActiveOrgAndRedirectToHome)
r.Get("/org/", reqSignedIn, Index)
r.Get("/org/new", reqSignedIn, Index)
r.Get("/datasources/", reqSignedIn, Index)
@ -190,12 +191,12 @@ func Register(r *macaron.Macaron) {
r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
r.Group("/plugins", func() {
r.Get("/", wrap(GetPluginList))
r.Get("/plugins", wrap(GetPluginList))
r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
r.Group("/plugins", func() {
r.Get("/:pluginId/readme", wrap(GetPluginReadme))
r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
r.Get("/:pluginId/settings", wrap(GetPluginSettingById))
r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
}, reqOrgAdmin)
@ -252,6 +253,9 @@ func Register(r *macaron.Macaron) {
// rendering
r.Get("/render/*", reqSignedIn, RenderToPng)
// grafana.net proxy
r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)
// Gravatar service.
avt := avatar.CacheServer()
r.Get("/avatar/:hash", avt.ServeHTTP)

@ -116,6 +116,7 @@ func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if avatar.Expired() {
if err := avatar.Update(); err != nil {
log.Trace("avatar update error: %v", err)
avatar = this.notFound
}
}

@ -15,15 +15,20 @@ type PluginSetting struct {
Dependencies *plugins.PluginDependencies `json:"dependencies"`
JsonData map[string]interface{} `json:"jsonData"`
DefaultNavUrl string `json:"defaultNavUrl"`
LatestVersion string `json:"latestVersion"`
HasUpdate bool `json:"hasUpdate"`
}
type PluginListItem struct {
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"`
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"`
LatestVersion string `json:"latestVersion"`
HasUpdate bool `json:"hasUpdate"`
}
type PluginList []PluginListItem

@ -137,9 +137,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,
"buildstamp": setting.BuildStamp,
"version": setting.BuildVersion,
"commit": setting.BuildCommit,
"buildstamp": setting.BuildStamp,
"latestVersion": plugins.GrafanaLatestVersion,
"hasUpdate": plugins.GrafanaHasUpdate,
},
}

@ -0,0 +1,46 @@
package api
import (
"crypto/tls"
"net"
"net/http"
"net/http/httputil"
"time"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/util"
)
var gNetProxyTransport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy {
director := func(req *http.Request) {
req.URL.Scheme = "https"
req.URL.Host = "grafana.net"
req.Host = "grafana.net"
req.URL.Path = util.JoinUrlFragments("https://grafana.net/api", proxyPath)
// clear cookie headers
req.Header.Del("Cookie")
req.Header.Del("Set-Cookie")
}
return &httputil.ReverseProxy{Director: director}
}
func ProxyGnetRequest(c *middleware.Context) {
proxyPath := c.Params("*")
proxy := ReverseProxyGnetReq(proxyPath)
proxy.Transport = gNetProxyTransport
proxy.ServeHTTP(c.Resp, c.Req.Request)
c.Resp.Header().Del("Set-Cookie")
}

@ -14,6 +14,7 @@ func GetPluginList(c *middleware.Context) Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
embeddedFilter := c.Query("embedded")
coreFilter := c.Query("core")
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
@ -28,16 +29,23 @@ func GetPluginList(c *middleware.Context) Response {
continue
}
// filter out core plugins
if coreFilter == "0" && pluginDef.IsCorePlugin {
continue
}
// filter on type
if typeFilter != "" && typeFilter != pluginDef.Type {
continue
}
listItem := dtos.PluginListItem{
Id: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Info: &pluginDef.Info,
Id: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Info: &pluginDef.Info,
LatestVersion: pluginDef.GrafanaNetVersion,
HasUpdate: pluginDef.GrafanaNetHasUpdate,
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
@ -81,6 +89,8 @@ func GetPluginSettingById(c *middleware.Context) Response {
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
LatestVersion: def.GrafanaNetVersion,
HasUpdate: def.GrafanaNetHasUpdate,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}

@ -4,6 +4,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@ -109,6 +110,23 @@ func UserSetUsingOrg(c *middleware.Context) Response {
return ApiSuccess("Active organization changed")
}
// GET /profile/switch-org/:id
func ChangeActiveOrgAndRedirectToHome(c *middleware.Context) {
orgId := c.ParamsInt64(":id")
if !validateUsingOrg(c.UserId, orgId) {
NotFoundHandler(c)
}
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
if err := bus.Dispatch(&cmd); err != nil {
NotFoundHandler(c)
}
c.Redirect(setting.AppSubUrl + "/")
}
func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) Response {
userQuery := m.GetUserByIdQuery{Id: c.UserId}

@ -1,9 +1,10 @@
package commands
import (
"os"
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"os"
)
func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
@ -25,27 +26,33 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
var pluginCommands = []cli.Command{
{
Name: "install",
Usage: "install <plugin name>",
Usage: "install <plugin id>",
Action: runCommand(installCommand),
}, {
Name: "list-remote",
Usage: "list remote available plugins",
Action: runCommand(listremoteCommand),
}, {
Name: "upgrade",
Usage: "upgrade <plugin name>",
Action: runCommand(upgradeCommand),
Name: "update",
Usage: "update <plugin id>",
Aliases: []string{"upgrade"},
Action: runCommand(upgradeCommand),
}, {
Name: "upgrade-all",
Usage: "upgrades all your installed plugins",
Action: runCommand(upgradeAllCommand),
Name: "update-all",
Aliases: []string{"upgrade-all"},
Usage: "update all your installed plugins",
Action: runCommand(upgradeAllCommand),
}, {
Name: "ls",
Usage: "list all installed plugins",
Action: runCommand(lsCommand),
}, {
Name: "uninstall",
Usage: "uninstall <plugin id>",
Action: runCommand(removeCommand),
}, {
Name: "remove",
Usage: "remove <plugin name>",
Usage: "remove <plugin id>",
Action: runCommand(removeCommand),
},
}

@ -120,6 +120,7 @@ func RemoveGitBuildFromName(pluginName, filename string) string {
}
var retryCount = 0
var permissionsDeniedMessage = "Could not create %s. Permission denied. Make sure you have write access to plugindir"
func downloadFile(pluginName, filePath, url string) (err error) {
defer func() {
@ -153,16 +154,16 @@ func downloadFile(pluginName, filePath, url string) (err error) {
newFile := path.Join(filePath, RemoveGitBuildFromName(pluginName, zf.Name))
if zf.FileInfo().IsDir() {
os.Mkdir(newFile, 0777)
err := os.Mkdir(newFile, 0777)
if PermissionsError(err) {
return fmt.Errorf(permissionsDeniedMessage, newFile)
}
} else {
dst, err := os.Create(newFile)
if err != nil {
if strings.Contains(err.Error(), "permission denied") {
return fmt.Errorf(
"Could not create file %s. permission deined. Make sure you have write access to plugindir",
newFile)
}
if PermissionsError(err) {
return fmt.Errorf(permissionsDeniedMessage, newFile)
}
defer dst.Close()
src, err := zf.Open()
if err != nil {
@ -176,3 +177,7 @@ func downloadFile(pluginName, filePath, url string) (err error) {
return nil
}
func PermissionsError(err error) bool {
return err != nil && strings.Contains(err.Error(), "permission denied")
}

@ -51,7 +51,7 @@ func upgradeAllCommand(c CommandLine) error {
}
for _, p := range pluginsToUpgrade {
log.Infof("Upgrading %v \n", p.Id)
log.Infof("Updating %v \n", p.Id)
s.RemoveInstalledPlugin(pluginsDir, p.Id)
InstallPlugin(p.Id, "", c)

@ -8,17 +8,37 @@ import (
"github.com/codegangsta/cli"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
"strings"
)
var version = "master"
func getGrafanaPluginDir() string {
os := runtime.GOOS
if os == "windows" {
currentOS := runtime.GOOS
defaultNix := "/var/lib/grafana/plugins"
if currentOS == "windows" {
return "C:\\opt\\grafana\\plugins"
} else {
return "/var/lib/grafana/plugins"
}
pwd, err := os.Getwd()
if err != nil {
log.Error("Could not get current path. using default")
return defaultNix
}
if isDevenvironment(pwd) {
return "../../../data/plugins"
}
return defaultNix
}
func isDevenvironment(pwd string) bool {
// if grafana-cli is executed from the cmd folder we can assume
// that its in development environment.
return strings.HasSuffix(pwd, "/pkg/cmd/grafana-cli")
}
func main() {

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

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
)
@ -56,6 +57,9 @@ func sendUsageStats() {
metrics["stats.users.count"] = statsQuery.Result.UserCount
metrics["stats.orgs.count"] = statsQuery.Result.OrgCount
metrics["stats.playlist.count"] = statsQuery.Result.PlaylistCount
metrics["stats.plugins.apps.count"] = len(plugins.Apps)
metrics["stats.plugins.panels.count"] = len(plugins.Panels)
metrics["stats.plugins.datasources.count"] = len(plugins.DataSources)
dsStats := m.GetDataSourceStatsQuery{}
if err := bus.Dispatch(&dsStats); err != nil {

@ -21,15 +21,14 @@ type GetDataSourceStatsQuery struct {
}
type AdminStats struct {
UserCount int `json:"user_count"`
OrgCount int `json:"org_count"`
DashboardCount int `json:"dashboard_count"`
DbSnapshotCount int `json:"db_snapshot_count"`
DbTagCount int `json:"db_tag_count"`
DataSourceCount int `json:"data_source_count"`
PlaylistCount int `json:"playlist_count"`
StarredDbCount int `json:"starred_db_count"`
GrafanaAdminCount int `json:"grafana_admin_count"`
UserCount int `json:"user_count"`
OrgCount int `json:"org_count"`
DashboardCount int `json:"dashboard_count"`
DbSnapshotCount int `json:"db_snapshot_count"`
DbTagCount int `json:"db_tag_count"`
DataSourceCount int `json:"data_source_count"`
PlaylistCount int `json:"playlist_count"`
StarredDbCount int `json:"starred_db_count"`
}
type GetAdminStatsQuery struct {

@ -14,7 +14,7 @@ type FrontendPluginBase struct {
}
func (fp *FrontendPluginBase) initFrontendPlugin() {
if isInternalPlugin(fp.PluginDir) {
if isExternalPlugin(fp.PluginDir) {
StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
Directory: fp.PluginDir,
PluginId: fp.Id,
@ -48,17 +48,18 @@ func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
func (fp *FrontendPluginBase) handleModuleDefaults() {
if isInternalPlugin(fp.PluginDir) {
if isExternalPlugin(fp.PluginDir) {
fp.Module = path.Join("plugins", fp.Id, "module")
fp.BaseUrl = path.Join("public/plugins", fp.Id)
return
}
fp.IsCorePlugin = true
fp.Module = path.Join("app/plugins", fp.Type, fp.Id, "module")
fp.BaseUrl = path.Join("public/app/plugins", fp.Type, fp.Id)
}
func isInternalPlugin(pluginDir string) bool {
func isExternalPlugin(pluginDir string) bool {
return !strings.Contains(pluginDir, setting.StaticRootPath)
}

@ -43,6 +43,10 @@ type PluginBase struct {
IncludedInAppId string `json:"-"`
PluginDir string `json:"-"`
DefaultNavUrl string `json:"-"`
IsCorePlugin bool `json:"-"`
GrafanaNetVersion string `json:"-"`
GrafanaNetHasUpdate bool `json:"-"`
// cache for readme file contents
Readme []byte `json:"-"`

@ -22,6 +22,9 @@ var (
Apps map[string]*AppPlugin
Plugins map[string]*PluginBase
PluginTypes map[string]interface{}
GrafanaLatestVersion string
GrafanaHasUpdate bool
)
type PluginScanner struct {
@ -70,6 +73,7 @@ func Init() error {
app.initApp()
}
go StartPluginUpdateChecker()
return nil
}

@ -0,0 +1,119 @@
package plugins
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
type GrafanaNetPlugin struct {
Slug string `json:"slug"`
Version string `json:"version"`
}
type GithubLatest struct {
Stable string `json:"stable"`
Testing string `json:"testing"`
}
func StartPluginUpdateChecker() {
if !setting.CheckForUpdates {
return
}
// do one check directly
go checkForUpdates()
ticker := time.NewTicker(time.Minute * 10)
for {
select {
case <-ticker.C:
checkForUpdates()
}
}
}
func getAllExternalPluginSlugs() string {
str := ""
for _, plug := range Plugins {
if plug.IsCorePlugin {
continue
}
str += plug.Id + ","
}
return str
}
func checkForUpdates() {
log.Trace("Checking for updates")
client := http.Client{Timeout: time.Duration(5 * time.Second)}
pluginSlugs := getAllExternalPluginSlugs()
resp, err := client.Get("https://grafana.net/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion)
if err != nil {
log.Trace("Failed to get plugins repo from grafana.net, %v", err.Error())
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Trace("Update check failed, reading response from grafana.net, %v", err.Error())
return
}
gNetPlugins := []GrafanaNetPlugin{}
err = json.Unmarshal(body, &gNetPlugins)
if err != nil {
log.Trace("Failed to unmarshal plugin repo, reading response from grafana.net, %v", err.Error())
return
}
for _, plug := range Plugins {
for _, gplug := range gNetPlugins {
if gplug.Slug == plug.Id {
plug.GrafanaNetVersion = gplug.Version
plug.GrafanaNetHasUpdate = plug.Info.Version != plug.GrafanaNetVersion
}
}
}
resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json")
if err != nil {
log.Trace("Failed to get lates.json repo from github: %v", err.Error())
return
}
defer resp2.Body.Close()
body, err = ioutil.ReadAll(resp2.Body)
if err != nil {
log.Trace("Update check failed, reading response from github.net, %v", err.Error())
return
}
var githubLatest GithubLatest
err = json.Unmarshal(body, &githubLatest)
if err != nil {
log.Trace("Failed to unmarshal github latest, reading response from github: %v", err.Error())
return
}
if strings.Contains(setting.BuildVersion, "-") {
GrafanaLatestVersion = githubLatest.Testing
GrafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, githubLatest.Testing)
} else {
GrafanaLatestVersion = githubLatest.Stable
GrafanaHasUpdate = githubLatest.Stable != setting.BuildVersion
}
}

@ -10,12 +10,12 @@ func addPreferencesMigrations(mg *Migrator) {
Name: "preferences",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_Int, Nullable: true},
{Name: "user_id", Type: DB_NVarchar, Length: 255, Nullable: true},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
{Name: "user_id", Type: DB_BigInt, Nullable: false},
{Name: "version", Type: DB_Int, Nullable: false},
{Name: "home_dashboard_id", Type: DB_BigInt, Nullable: true},
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: true},
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: true},
{Name: "home_dashboard_id", Type: DB_BigInt, Nullable: false},
{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: false},
{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: false},
{Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
},
@ -25,6 +25,8 @@ func addPreferencesMigrations(mg *Migrator) {
},
}
mg.AddMigration("drop preferences table v3", NewDropTableMigration("preferences"))
// create table
mg.AddMigration("create preferences table v2", NewAddTableMigration(preferencesV2))
mg.AddMigration("create preferences table v3", NewAddTableMigration(preferencesV2))
}

@ -85,12 +85,7 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error {
(
SELECT COUNT(DISTINCT ` + dialect.Quote("dashboard_id") + ` )
FROM ` + dialect.Quote("star") + `
) AS starred_db_count,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
WHERE ` + dialect.Quote("is_admin") + ` = 1
) AS grafana_admin_count
) AS starred_db_count
`
var stats m.AdminStats

@ -124,6 +124,7 @@ var (
appliedEnvOverrides []string
ReportingEnabled bool
CheckForUpdates bool
GoogleAnalyticsId string
GoogleTagManagerId string
@ -475,6 +476,7 @@ func NewConfigContext(args *CommandLineArgs) error {
analytics := Cfg.Section("analytics")
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()

@ -7,10 +7,6 @@ import coreModule from 'app/core/core_module';
var template = `
<div class="graph-legend-popover">
<a class="drop-popopver-close" ng-click="ctrl.close();" href="" ng-hide="ctrl.autoClose">
<i class="fa fa-times-circle"></i>
</a>
<div ng-show="ctrl.series" class="p-b-1">
<label>Y Axis:</label>
<button ng-click="ctrl.toggleAxis(yaxis);" class="btn btn-small"
@ -31,7 +27,6 @@ var template = `
ng-style="{color:color}"
ng-click="ctrl.colorSelected(color);">&nbsp;</i>
</p>
</div>
`;
@ -51,21 +46,17 @@ export class ColorPickerCtrl {
toggleAxis(yaxis) {
this.$scope.toggleAxis();
if (!this.$scope.autoClose) {
if (this.$scope.autoClose) {
this.$scope.dismiss();
}
}
colorSelected(color) {
this.$scope.colorSelected(color);
if (!this.$scope.autoClose) {
if (this.$scope.autoClose) {
this.$scope.dismiss();
}
}
close() {
this.$scope.dismiss();
}
}
export function colorPicker() {

@ -7,6 +7,9 @@ import coreModule from 'app/core/core_module';
var template = `
<select class="gf-form-input" ng-model="ctrl.model" ng-options="f.value as f.text for f in ctrl.options"></select>
<info-popover mode="right-absolute">
Not finding dashboard you want? Star it first, then it should appear in this select box.
</info-popover>
`;
export class DashboardSelectorCtrl {

@ -8,21 +8,30 @@ import Drop from 'tether-drop';
export function infoPopover() {
return {
restrict: 'E',
template: '<i class="fa fa-info-circle"></i>',
transclude: true,
link: function(scope, elem, attrs, ctrl, transclude) {
var inputElem = elem.prev();
if (inputElem.length === 0) {
console.log('Failed to find input element for popover');
return;
}
// var inputElem = elem.prev();
// if (inputElem.length === 0) {
// console.log('Failed to find input element for popover');
// return;
// }
var offset = attrs.offset || '0 -10px';
var position = attrs.position || 'right middle';
var classes = 'drop-help drop-hide-out-of-bounds';
var openOn = 'hover';
elem.addClass('gf-form-help-icon');
if (attrs.wide) {
classes += ' drop-wide';
}
if (attrs.mode) {
elem.addClass('gf-form-help-icon--' + attrs.mode);
}
transclude(function(clone, newScope) {
var content = document.createElement("div");
_.each(clone, (node) => {
@ -30,11 +39,11 @@ export function infoPopover() {
});
var drop = new Drop({
target: inputElem[0],
target: elem[0],
content: content,
position: position,
classes: classes,
openOn: 'click',
openOn: openOn,
tetherOptions: {
offset: offset
}

@ -21,10 +21,6 @@
<i class="{{::menuItem.icon}}" ng-show="::menuItem.icon"></i>
{{::menuItem.text}}
</a>
<a ng-click="menuItem.click()" ng-show="::menuItem.click">
<i class="{{::menuItem.icon}}"></i>
{{::menuItem.text}}
</a>
</li>
</ul>
</li>

@ -72,9 +72,8 @@ export class SideMenuCtrl {
this.orgMenu.push({
text: "Switch to " + org.name,
icon: "fa fa-fw fa-random",
click: () => {
this.switchOrg(org.orgId);
}
url: this.getUrl('/profile/switch-org/' + org.orgId),
target: '_self'
});
});
@ -83,12 +82,6 @@ export class SideMenuCtrl {
}
});
}
switchOrg(orgId) {
this.backendSrv.post('/api/user/using/' + orgId).then(() => {
window.location.href = `${config.appSubUrl}/`;
});
};
}
export function sideMenuDirective() {

@ -7,7 +7,12 @@ import coreModule from 'app/core/core_module';
import Drop from 'tether-drop';
var template = `
<label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer">{{ctrl.label}}</label>
<label for="check-{{ctrl.id}}" class="gf-form-label {{ctrl.labelClass}} pointer">
{{ctrl.label}}
<info-popover mode="right-normal" ng-if="ctrl.tooltip">
{{ctrl.tooltip}}
</info-popover>
</label>
<div class="gf-form-switch {{ctrl.switchClass}}" ng-if="ctrl.show">
<input id="check-{{ctrl.id}}" type="checkbox" ng-model="ctrl.checked" ng-change="ctrl.internalOnChange()">
<label for="check-{{ctrl.id}}" data-on="Yes" data-off="No"></label>
@ -21,17 +26,14 @@ export class SwitchCtrl {
id: any;
/** @ngInject */
constructor($scope) {
constructor($scope, private $timeout) {
this.show = true;
this.id = $scope.$id;
}
internalOnChange() {
return new Promise(resolve => {
setTimeout(() => {
this.onChange();
resolve();
});
return this.$timeout(() => {
return this.onChange();
});
}
@ -52,22 +54,6 @@ export function switchDirective() {
onChange: "&",
},
template: template,
link: (scope, elem) => {
if (scope.ctrl.tooltip) {
var drop = new Drop({
target: elem[0],
content: scope.ctrl.tooltip,
position: "right middle",
classes: 'drop-help',
openOn: 'hover',
hoverOpenDelay: 400,
});
scope.$on('$destroy', function() {
drop.destroy();
});
}
}
};
}

@ -39,7 +39,9 @@ function (angular, coreModule, config) {
$scope.buildInfo = {
version: config.buildInfo.version,
commit: config.buildInfo.commit,
buildstamp: new Date(config.buildInfo.buildstamp * 1000)
buildstamp: new Date(config.buildInfo.buildstamp * 1000),
latestVersion: config.buildInfo.latestVersion,
hasUpdate: config.buildInfo.hasUpdate,
};
$scope.submit = function() {

@ -169,7 +169,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
return System.import(model.module).then(function(appModule) {
return {
baseUrl: model.baseUrl,
name: 'app-config-' + model.pluginId,
name: 'app-config-' + model.id,
bindings: {appModel: "=", appEditCtrl: "="},
attrs: {"app-model": "ctrl.model", "app-edit-ctrl": "ctrl"},
Component: appModule.ConfigCtrl,

@ -46,9 +46,12 @@ function popoverSrv($compile, $rootScope) {
drop.on('close', () => {
popoverScope.dismiss({fromDropClose: true});
destroyDrop();
if (options.onClose) {
options.onClose();
}
});
drop.open();
setTimeout(() => { drop.open(); }, 10);
};
}

@ -42,6 +42,7 @@ export default class TimeSeries {
fillBelowTo: any;
transform: any;
flotpairs: any;
unit: any;
constructor(opts) {
this.datapoints = opts.datapoints;
@ -52,6 +53,7 @@ export default class TimeSeries {
this.valueFormater = kbn.valueFormats.none;
this.stats = {};
this.legend = true;
this.unit = opts.unit;
}
applySeriesOverrides(overrides) {
@ -170,7 +172,7 @@ export default class TimeSeries {
}
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) {
var timestamp = this.datapoints[i][0].toString();
if (timestamp.length === 13 && (timestamp % 1000) !== 0) {

@ -1,6 +1,6 @@
///<reference path="../../headers/common.d.ts" />
import {Subject} from 'vendor/npm/rxjs/Subject';
import EventEmitter from 'eventemitter3';
var hasOwnProp = {}.hasOwnProperty;
@ -9,48 +9,27 @@ function createName(name) {
}
export class Emitter {
subjects: any;
emitter: any;
constructor() {
this.subjects = {};
this.emitter = new EventEmitter();
}
emit(name, data?) {
var fnName = createName(name);
this.subjects[fnName] || (this.subjects[fnName] = new Subject());
this.subjects[fnName].next(data);
this.emitter.emit(name, data);
}
on(name, handler, scope?) {
var fnName = createName(name);
this.subjects[fnName] || (this.subjects[fnName] = new Subject());
var subscription = this.subjects[fnName].subscribe(handler);
this.emitter.on(name, handler);
if (scope) {
scope.$on('$destroy', function() {
subscription.unsubscribe();
this.emitter.off(name, handler);
});
}
return subscription;
};
off(name, handler) {
var fnName = createName(name);
if (this.subjects[fnName]) {
this.subjects[fnName].dispose();
delete this.subjects[fnName];
}
}
dispose() {
var subjects = this.subjects;
for (var prop in subjects) {
if (hasOwnProp.call(subjects, prop)) {
subjects[prop].dispose();
}
}
this.subjects = {};
off(name, handler) {
this.emitter.off(name, handler);
}
}

@ -22,10 +22,6 @@
<td>Total users</td>
<td>{{ctrl.stats.user_count}}</td>
</tr>
<tr>
<td>Total grafana admins</td>
<td>{{ctrl.stats.grafana_admin_count}}</td>
</tr>
<tr>
<td>Total organizations</td>
<td>{{ctrl.stats.org_count}}</td>

@ -45,6 +45,7 @@ function (angular, _, $) {
$scope.reset = function() {
$scope.currentAnnotation = angular.copy(annotationDefaults);
$scope.currentAnnotation.datasource = $scope.datasources[0].name;
$scope.currentIsNew = true;
$scope.datasourceChanged();
};

@ -428,6 +428,8 @@ function (angular, $, _, moment) {
// update graph yaxes changes
panelUpgrades.push(function(panel) {
if (panel.type !== 'graph') { return; }
if (!panel.grid) { return; }
if (!panel.yaxes) {
panel.yaxes = [
{

@ -36,7 +36,7 @@
</ul>
</li>
<li ng-show="::dashboardMeta.canSave">
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard'" data-placement="bottom"><i class="fa fa-save"></i></a>
<a ng-click="saveDashboard()" bs-tooltip="'Save dashboard <br> CTRL+S'" data-placement="bottom"><i class="fa fa-save"></i></a>
</li>
<li ng-if="::showSettingsMenu" class="dropdown">
<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Manage dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-cog"></i></a>
@ -48,7 +48,6 @@
<li ng-if="dashboardMeta.canEdit"><a class="pointer" ng-click="editJson();">View JSON</a></li>
<li ng-if="contextSrv.isEditor && !dashboard.editable"><a class="pointer" ng-click="makeEditable();">Make Editable</a></li>
<li ng-if="contextSrv.isEditor"><a class="pointer" ng-click="saveDashboardAs();">Save As...</a></li>
<li ng-if="dashboard.id"><a class="pointer" ng-click="saveDashboardAsHome();">Set As Home</a></li>
<li ng-if="dashboardMeta.canSave"><a class="pointer" ng-click="deleteDashboard();">Delete dashboard</a></li>
</ul>
</li>

@ -60,6 +60,14 @@ function(angular, $) {
scope.appEvent('zoom-out', evt);
}, { inputDisabled: true });
keyboardManager.bind('left', function(evt) {
scope.appEvent('shift-time-backward', evt);
}, { inputDisabled: true });
keyboardManager.bind('right', function(evt) {
scope.appEvent('shift-time-forward', evt);
}, { inputDisabled: true });
keyboardManager.bind('ctrl+e', function(evt) {
scope.appEvent('export-dashboard', evt);
}, { inputDisabled: true });

@ -26,7 +26,10 @@
<input type="text" class="gf-form-input width-25" ng-model='dashboard.title'></input>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Tags<tip>Press enter to a add tag</tip></label>
<label class="gf-form-label width-7">
Tags
<info-popover mode="right-normal">Press enter to a add tag</info-popover>
</label>
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput>
</div>
@ -46,19 +49,19 @@
label="Editable"
tooltip="Uncheck, then save and reload to disable all dashboard editing"
checked="dashboard.editable"
label-class="width-10">
label-class="width-11">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="Hide Controls"
tooltip="Hide row controls. Shortcut: CTRL+H"
checked="dashboard.hideControls"
label-class="width-10">
label-class="width-11">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="Shared Crosshair"
tooltip="Shared Crosshair line on all graphs. Shortcut: CTRL+O"
checked="dashboard.sharedCrosshair"
label-class="width-10">
label-class="width-11">
</gf-form-switch>
</div>
</div>

@ -38,31 +38,26 @@
<div ng-include src="'shareLinkOptions.html'"></div>
<div class="gf-form-group position-center">
<div class="gf-form width-30" >
<div class="gf-form-group section">
<div class="gf-form width-30">
<textarea rows="5" data-share-panel-url class="gf-form-input width-30" ng-model='iframeHtml'></textarea>
</div>
</div>
<div class="gf-form-group">
<div class="gf-form position-center">
<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
</div>
</div>
</script>
<script type="text/ng-template" id="shareLinkOptions.html">
<div class="gf-form-group position-center">
<div class="gf-form">
<span class="gf-form-label width-5">Include</span>
<editor-checkbox text="Current time range" model="options.forCurrent" change="buildUrl()"></editor-checkbox>
</div>
<div class="gf-form">
<span class="gf-form-label width-5">Include</span>
<editor-checkbox text="Template variables" model="options.includeTemplateVars" change="buildUrl()"></editor-checkbox>
</div>
<div class="gf-form-group section">
<gf-form-switch class="gf-form"
label="Current time range" label-class="width-12" switch-class="max-width-6"
checked="options.forCurrent" on-change="buildUrl()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="Template variables" label-class="width-12" switch-class="max-width-6"
checked="options.includeTemplateVars" on-change="buildUrl()">
</gf-form-switch>
<div class="gf-form">
<span class="gf-form-label width-5">Theme</span>
<div class="gf-form-select-wrapper max-width-10">
<span class="gf-form-label width-12">Theme</span>
<div class="gf-form-select-wrapper width-6">
<select class="gf-form-input" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
</div>
</div>
@ -75,18 +70,19 @@
</div>
<div ng-include src="'shareLinkOptions.html'"></div>
<div class="gf-form-group position-center">
<div class="gf-form-inline">
<div class="gf-form width-30">
<input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
</div>
<div class="gf-form pull-right">
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
<div>
<div class="gf-form-group section">
<div class="gf-form-inline">
<div class="gf-form width-30">
<input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
</div>
<div class="gf-form pull-right">
<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
</div>
</div>
</div>
</div>
<div class="gf-form position-center" ng-show="modeSharePanel">
<div class="gf-form section" ng-show="modeSharePanel">
<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
</div>
</script>
@ -117,7 +113,7 @@
</p>
</div>
<div class="gf-form-group share-modal-options position-center">
<div class="gf-form-group share-modal-options">
<div class="gf-form" ng-if="step === 1">
<span class="gf-form-label width-12">Snapshot name</span>
<input type="text" ng-model="snapshot.name" class="gf-form-input max-width-15" >

@ -1,9 +1,13 @@
<ul class="nav gf-timepicker-nav">
<li class="dashnav-zoom-out" style="padding-top: 2px">
<a class='small' ng-click='ctrl.zoom(2)'>
Zoom Out
</a>
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time backward <br> (left arrow key)'" data-placement="bottom">
<a ng-click='ctrl.move(-1)'><i class="fa fa-chevron-left"></i></a>
</li>
<li class="dashnav-zoom-out gf-timepicker-time-control" bs-tooltip="'Time range zoom out <br> CTRL+Z'" data-placement="bottom">
<a ng-click='ctrl.zoom(2)'>Zoom Out</a></li>
</li>
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time forward <br> (right arrow key)'" data-placement="bottom">
<a ng-click='ctrl.move(1)'><i class="fa fa-chevron-right"></i></a>
</li>
<li>

@ -30,6 +30,8 @@ export class TimePickerCtrl {
$scope.ctrl = this;
$rootScope.onAppEvent('zoom-out', () => this.zoom(2), $scope);
$rootScope.onAppEvent('shift-time-forward', () => this.move(1), $scope);
$rootScope.onAppEvent('shift-time-backward', () => this.move(-1), $scope);
$rootScope.onAppEvent('refresh', () => this.init(), $scope);
$rootScope.onAppEvent('dash-editor-hidden', () => this.isOpen = false, $scope);
@ -87,6 +89,30 @@ export class TimePickerCtrl {
this.timeSrv.setTime({from: moment.utc(from), to: moment.utc(to) });
}
move(direction) {
var range = this.timeSrv.timeRange();
var timespan = (range.to.valueOf() - range.from.valueOf()) / 2;
var to, from;
if (direction === -1) {
to = range.to.valueOf() - timespan;
from = range.from.valueOf() - timespan;
} else if (direction === 1) {
to = range.to.valueOf() + timespan;
from = range.from.valueOf() + timespan;
if (to > Date.now() && range.to < Date.now()) {
to = Date.now();
from = range.from.valueOf();
}
} else {
to = range.to.valueOf();
from = range.from.valueOf();
}
this.timeSrv.setTime({from: moment.utc(from), to: moment.utc(to) });
}
openDropdown() {
this.init();
this.isOpen = true;

@ -102,7 +102,6 @@ function(angular, _) {
value.current = null;
value.options = null;
});
};
p.hasChanges = function() {

@ -49,7 +49,7 @@ export class PrefsControlCtrl {
}
var template = `
<form name="ctrl.prefsForm" class="gf-form-group">
<form name="ctrl.prefsForm" class="section gf-form-group">
<h3 class="page-heading">Preferences</h3>
<div class="gf-form">
@ -61,9 +61,8 @@ var template = `
<div class="gf-form">
<span class="gf-form-label width-9">Home Dashboard</span>
<dashboard-selector
class="gf-form-select-wrapper max-width-20"
model="ctrl.prefs.homeDashboardId">
<dashboard-selector class="gf-form-select-wrapper max-width-20 gf-form-select-wrapper--has-help-icon"
model="ctrl.prefs.homeDashboardId">
</dashboard-selector>
</div>

@ -65,7 +65,7 @@ class MetricsPanelCtrl extends PanelCtrl {
var data = this.panel.snapshotData;
// backward compatability
if (!_.isArray(data)) {
data = data;
data = data.data;
}
this.events.emit('data-snapshot-load', data);
@ -82,6 +82,7 @@ class MetricsPanelCtrl extends PanelCtrl {
this.loading = true;
// load datasource service
this.setTimeQueryStart();
this.datasourceSrv.get(this.panel.datasource)
.then(this.issueQueries.bind(this))
.then(this.handleQueryResult.bind(this))
@ -183,7 +184,6 @@ class MetricsPanelCtrl extends PanelCtrl {
cacheTimeout: this.panel.cacheTimeout
};
this.setTimeQueryStart();
return datasource.query(metricsQuery);
}
@ -251,8 +251,12 @@ class MetricsPanelCtrl extends PanelCtrl {
}
addDataQuery(datasource) {
var target = {
};
var target: any = {};
if (datasource) {
target.datasource = datasource.name;
}
this.panel.targets.push(target);
}
}

@ -46,6 +46,7 @@ export class PanelCtrl {
$scope.$on("refresh", () => this.refresh());
$scope.$on("render", () => this.render());
$scope.$on("$destroy", () => this.events.emit('panel-teardown'));
}
init() {

@ -31,7 +31,7 @@
</div>
<gf-form-switch class="gf-form max-width-30"
label="Hide time override info" label-class="width-12"
checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.render()">
checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.refresh()">
</gf-form-switch>
</div>
</div>

@ -16,6 +16,8 @@ var defaults = {
jsonData: {}
};
var datasourceCreated = false;
export class DataSourceEditCtrl {
isNew: boolean;
datasources: any[];
@ -66,6 +68,11 @@ export class DataSourceEditCtrl {
this.backendSrv.get('/api/datasources/' + id).then(ds => {
this.isNew = false;
this.current = ds;
if (datasourceCreated) {
datasourceCreated = false;
this.testDatasource();
}
return this.typeChanged();
});
}
@ -123,14 +130,14 @@ export class DataSourceEditCtrl {
if (this.current.id) {
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
this.updateFrontendSettings().then(() => {
if (test) {
this.testDatasource();
}
this.testDatasource();
});
});
} else {
return this.backendSrv.post('/api/datasources', this.current).then(result => {
this.updateFrontendSettings();
datasourceCreated = true;
this.$location.path('datasources/edit/' + result.id);
});
}

@ -25,24 +25,24 @@
<div ng-if="ctrl.tabIndex === 0" class="tab-content">
<form name="ctrl.editForm">
<form name="ctrl.editForm" ng-if="ctrl.current">
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-7">Name</span>
<input class="gf-form-input max-width-21" type="text" ng-model="ctrl.current.name" placeholder="My data source name" required>
<info-popover offset="0px -135px" mode="right-absolute">
The name is used when you select the data source in panels.
The <em>Default</em> data source is preselected in new
panels.
</info-popover>
</div>
<info-popover offset="0px -130px">
The name is used when you select the data source in panels.
The <code>Default</code> data source is preselected in new
panels.
</info-popover>
<gf-form-switch class="gf-form" label="Default" checked="ctrl.current.isDefault" switch-class="max-width-6"></gf-form-switch>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">Type</span>
<div class="gf-form-select-wrapper max-width-21">
<div class="gf-form-select-wrapper max-width-23">
<select class="gf-form-input" ng-model="ctrl.current.type" ng-options="v.id as v.name for v in ctrl.types" ng-change="ctrl.typeChanged()"></select>
</div>
</div>
@ -55,7 +55,6 @@
<div ng-if="ctrl.testing" style="margin-top: 25px">
<h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<h5 ng-show="ctrl.testing.done">Test results</h5>
<div class="alert-{{ctrl.testing.status}} alert">
<div class="alert-title">{{ctrl.testing.title}}</div>
<div ng-bind='ctrl.testing.message'></div>
@ -64,10 +63,7 @@
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-show="ctrl.isNew" ng-click="ctrl.saveChanges()">Add</button>
<button type="submit" class="btn btn-success" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges()">Save</button>
<button type="submit" class="btn btn-secondary" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges(true)">
Test Connection
</button>
<button type="submit" class="btn btn-success" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges()">Save &amp; Test</button>
<button type="submit" class="btn btn-danger" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">
Delete
</button>

@ -3,29 +3,34 @@
<div class="gf-form-group">
<h3 class="page-heading">Http settings</h3>
<div class="gf-form">
<span class="gf-form-label width-7">Url</span>
<input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="http://my.server.com:8080" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
<info-popover>
<p>Specify a complete HTTP url (http://your_server:8080)</p>
<span ng-show="current.access === 'direct'">
Your access method is <code>Direct</code>, this means the url
needs to be accessable from the browser.
</span>
<span ng-show="current.access === 'proxy'">
Your access method is currently <code>Proxy</code>, this means the url
needs to be accessable from the grafana backend.
</span>
</info-popover>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">Url</span>
<input class="gf-form-input" type="text" ng-model='current.url' placeholder="for example: http://localhost:8081" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
<info-popover mode="right-absolute">
<p>Specify a complete HTTP url (for example http://your_server:8080)</p>
<span ng-show="current.access === 'direct'">
Your access method is <em>Direct</em>, this means the url
needs to be accessable from the browser.
</span>
<span ng-show="current.access === 'proxy'">
Your access method is currently <em>Proxy</em>, this means the url
needs to be accessable from the grafana backend.
</span>
</info-popover>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label width-7">
Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip>
</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-7">Access</span>
<div class="gf-form-select-wrapper gf-form-select-wrapper--has-help-icon max-width-24">
<select class="gf-form-input" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
<info-popover mode="right-absolute">
Direct = url is used directly from browser<br>
Proxy = Grafana backend will proxy the request
</info-popover>
</div>
</div>
</div>
@ -34,12 +39,12 @@
<label class="gf-form-label width-7">Http Auth</label>
</div>
<gf-form-switch class="gf-form"
label="Basic Auth"
checked="current.basicAuth" switch-class="max-width-6">
label="Basic Auth"
checked="current.basicAuth" switch-class="max-width-6">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="With Credentials"
checked="current.withCredentials" switch-class="max-width-6">
label="With Credentials"
checked="current.withCredentials" switch-class="max-width-6">
</gf-form-switch>
</div>

@ -20,8 +20,9 @@
<li class="card-item-wrapper" ng-repeat="ds in ctrl.datasources">
<a class="card-item" href="datasources/edit/{{ds.id}}/">
<div class="card-item-header">
<i class="icon-gf icon-gf-{{ds.type}}"></i>
{{ds.type}}
<div class="card-item-type">
{{ds.type}}
</div>
</div>
<div class="card-item-body">
<figure class="card-item-figure">

@ -4,8 +4,8 @@
<div class="page-container" ng-init="ctrl.init()">
<div class="page-header">
<div class="plugin-header">
<span ng-show="ctrl.model.info.logos.large" class="plugin-header-logo">
<img src="{{ctrl.model.info.logos.large}}">
<span class="plugin-header-logo">
<img ng-src="{{ctrl.model.info.logos.large}}">
</span>
<div class="plugin-header-info-block">
@ -55,6 +55,9 @@
<section class="page-sidebar-section">
<h4>Version</h4>
<span>{{ctrl.model.info.version}}</span>
<div ng-show="ctrl.model.hasUpdate">
<a ng-click="ctrl.updateAvailable()" bs-tooltip="ctrl.model.latestVersion">Update Available!</a>
</div>
</section>
<section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
<h5>Includes</h4>

@ -33,8 +33,13 @@
<li class="card-item-wrapper" ng-repeat="plugin in ctrl.plugins">
<a class="card-item" href="plugins/{{plugin.id}}/edit">
<div class="card-item-header">
<i class="icon-gf icon-gf-{{plugin.type}}"></i>
{{plugin.type}}
<div class="card-item-type">
<i class="icon-gf icon-gf-{{plugin.type}}"></i>
{{plugin.type}}
</div>
<div class="card-item-notice" ng-show="plugin.hasUpdate">
<span bs-tooltip="plugin.latestVersion">Update available!</span>
</div>
</div>
<div class="card-item-body">
<figure class="card-item-figure">

@ -0,0 +1,21 @@
<div class="modal-body">
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa fa-cloud-download"></i>
<span class="p-l-1">Update Plugin</span>
</h2>
<a class="modal-header-close" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</a>
</div>
<div class="modal-content">
<div class="gf-form-group">
<p>Type the following on the command line to update {{plugin.name}}.</p>
<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre>
<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
</div>
<p class="pluginlist-none-installed code--line"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
</div>
</div>

@ -19,6 +19,7 @@ export class PluginEditCtrl {
/** @ngInject */
constructor(private $scope,
private $rootScope,
private backendSrv,
private $routeParams,
private $sce,
@ -47,6 +48,7 @@ export class PluginEditCtrl {
});
if (this.model.type === 'app') {
this.tabIndex = 1;
this.tabs.push('Config');
this.hasDashboards = _.findWhere(result.includes, {type: 'dashboard'});
@ -73,7 +75,7 @@ export class PluginEditCtrl {
case 'datasource': return 'icon-gf icon-gf-datasources';
case 'panel': return 'icon-gf icon-gf-panel';
case 'app': return 'icon-gf icon-gf-apps';
case 'page': return 'icon-gf icon-gf-share';
case 'page': return 'icon-gf icon-gf-endpoint-tiny';
case 'dashboard': return 'icon-gf icon-gf-dashboard';
}
}
@ -128,6 +130,16 @@ export class PluginEditCtrl {
this.postUpdateHook = callback;
}
updateAvailable() {
var modalScope = this.$scope.$new(true);
modalScope.plugin = this.model;
this.$rootScope.appEvent('show-modal', {
src: 'public/app/features/plugins/partials/update_instructions.html',
scope: modalScope
});
}
enable() {
this.model.enabled = true;
this.model.pinned = true;
@ -142,4 +154,3 @@ export class PluginEditCtrl {
}
angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);

@ -179,55 +179,56 @@
<div class="section gf-form-group" >
<h5 class="section-heading">Selection Options</h5>
<gf-form-switch class="gf-form"
label="Multi-value"
label-class="width-10"
tooltip="Enables multiple values to be selected at the same time"
checked="current.multi"
on-change="runQuery()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="Include All option"
label-class="width-10"
checked="current.includeAll"
on-change="runQuery()">
</gf-form-switch>
<div class="gf-form" ng-if="current.includeAll">
<span class="gf-form-label width-10">Custom all value</span>
<input type="text" class="gf-form-input max-width-15" ng-model='current.allValue' placeholder="blank = auto"></input>
</div>
</div>
<div class="gf-form-group" ng-if="current.type === 'query'">
<h5>Value groups/tags (Experimental feature)</h5>
<div class="gf-form">
<editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
</div>
<div class="gf-form last" ng-if="current.useTags">
<span class="gf-form-label width-10">Tags query</span>
<input type="text" class="gf-form-input" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
</div>
<div class="gf-form" ng-if="current.useTags">
<li class="gf-form-label width-10">Tag values query</li>
<input type="text" class="gf-form-input" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
</div>
</div>
<div class="gf-form-group">
<h5>Preview of values (shows max 20)</h5>
<div class="gf-form">
<span class="gf-form-label" ng-repeat="option in current.options | limitTo: 20">
{{option.text}}
</span>
</div>
</div>
</div>
<div class="gf-form-button-row p-y-0">
<button type="button" class="btn btn-success" ng-show="mode === 'edit'" ng-click="update();">Update</button>
<button type="button" class="btn btn-success" ng-show="mode === 'new'" ng-click="add();">Add</button>
</div>
</div>
<div class="section">
<gf-form-switch class="gf-form"
label="Multi-value"
label-class="width-10"
tooltip="Enables multiple values to be selected at the same time"
checked="current.multi"
on-change="runQuery()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label="Include All option"
label-class="width-10"
checked="current.includeAll"
on-change="runQuery()">
</gf-form-switch>
</div>
<div class="gf-form" ng-if="current.includeAll">
<span class="gf-form-label width-10">Custom all value</span>
<input type="text" class="gf-form-input max-width-15" ng-model='current.allValue' placeholder="blank = auto"></input>
</div>
</div>
<div class="gf-form-group" ng-if="current.type === 'query'">
<h5>Value groups/tags (Experimental feature)</h5>
<div class="gf-form">
<editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
</div>
<div class="gf-form last" ng-if="current.useTags">
<span class="gf-form-label width-10">Tags query</span>
<input type="text" class="gf-form-input" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
</div>
<div class="gf-form" ng-if="current.useTags">
<li class="gf-form-label width-10">Tag values query</li>
<input type="text" class="gf-form-input" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
</div>
</div>
<div class="gf-form-group">
<h5>Preview of values (shows max 20)</h5>
<div class="gf-form-inline">
<div class="gf-form" ng-repeat="option in current.options | limitTo: 20">
<span class="gf-form-label">{{option.text}}</span>
</div>
</div>
</div>
</div>
<div class="gf-form-button-row p-y-0">
<button type="button" class="btn btn-success" ng-show="mode === 'edit'" ng-click="update();">Update</button>
<button type="button" class="btn btn-success" ng-show="mode === 'new'" ng-click="add();">Add</button>
</div>
</div>
</div>

@ -272,22 +272,24 @@ function (angular, _, kbn) {
}
for (i = 0; i < metricNames.length; i++) {
var value = metricNames[i].text;
var item = metricNames[i];
var value = item.value || item.text;
var text = item.text || item.value;
if (regex) {
matches = regex.exec(value);
if (!matches) { continue; }
if (matches.length > 1) {
value = matches[1];
text = value;
}
}
options[value] = value;
options[value] = {text: text, value: value};
}
return _.map(_.keys(options).sort(), function(key) {
var option = { text: key, value: key };
return option;
return options[key];
});
};

@ -1,5 +1,4 @@
///<reference path="../../vendor/npm/angular2/typings/es6-promise/es6-promise.d.ts" />
///<reference path="../../vendor/npm/angular2/typings/es6-collections/es6-collections.d.ts" />
/// <reference path="./es6-shim/es6-shim.d.ts" />
declare var System: any;
@ -48,3 +47,8 @@ declare module 'tether-drop' {
var config: any;
export default config;
}
declare module 'eventemitter3' {
var config: any;
export default config;
}

@ -1,73 +0,0 @@
// Type definitions for es6-promise
// Project: https://github.com/jakearchibald/ES6-Promise
// Definitions by: François de Campredon <https://github.com/fdecampredon/>, vvakame <https://github.com/vvakame>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
interface Thenable<R> {
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
}
declare class Promise<R> implements Thenable<R> {
/**
* If you call resolve in the body of the callback passed to the constructor,
* your promise is fulfilled with result object passed to resolve.
* If you call reject your promise is rejected with the object passed to resolve.
* For consistency and debugging (eg stack traces), obj should be an instanceof Error.
* Any errors thrown in the constructor callback will be implicitly passed to reject().
*/
constructor(callback: (resolve : (value?: R | Thenable<R>) => void, reject: (error?: any) => void) => void);
/**
* onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects.
* Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called.
* Both callbacks have a single parameter , the fulfillment value or rejection reason.
* "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve.
* If an error is thrown in the callback, the returned promise rejects with that error.
*
* @param onFulfilled called when/if "promise" resolves
* @param onRejected called when/if "promise" rejects
*/
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Promise<U>;
/**
* Sugar for promise.then(undefined, onRejected)
*
* @param onRejected called when/if "promise" rejects
*/
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
}
declare module Promise {
/**
* Make a new promise from the thenable.
* A thenable is promise-like in as far as it has a "then" method.
*/
function resolve<R>(value?: R | Thenable<R>): Promise<R>;
/**
* Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error
*/
function reject(error: any): Promise<any>;
/**
* Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects.
* the array passed to all can be a mixture of promise-like objects and other objects.
* The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
*/
function all<R>(promises: (R | Thenable<R>)[]): Promise<R[]>;
/**
* Make a Promise that fulfills when any item fulfills, and rejects if any item rejects.
*/
function race<R>(promises: (R | Thenable<R>)[]): Promise<R>;
}
declare module 'es6-promise' {
var foo: typeof Promise; // Temp variable to reference Promise in local context
module rsvp {
export var Promise: typeof foo;
}
export = rsvp;
}

@ -1,7 +1,9 @@
// Generated by typings
// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7de6c3dd94feaeb21f20054b9f30d5dabc5efabd/es6-shim/es6-shim.d.ts
// Type definitions for es6-shim v0.31.2
// Project: https://github.com/paulmillr/es6-shim
// Definitions by: Ron Buckton <http://github.com/rbuckton>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare type PropertyKey = string | number | symbol;
@ -621,7 +623,7 @@ interface WeakSetConstructor {
declare var WeakSet: WeakSetConstructor;
declare module Reflect {
declare namespace Reflect {
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>): any;
function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
@ -649,7 +651,7 @@ declare module "es6-shim" {
var WeakMap: WeakMapConstructor;
var WeakSet: WeakSetConstructor;
var Promise: PromiseConstructor;
module Reflect {
namespace Reflect {
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>): any;
function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;

@ -28,14 +28,22 @@
<td><span class="label label-info">R</span></td>
<td>Refresh (Fetches new data and rerenders panels)</td>
</tr>
<tr>
<td><span class="label label-info">left arrow key</span></td>
<td>Shift time backward</td>
</tr>
<tr>
<td><span class="label label-info">right arrow key</span></td>
<td>Shift time forward</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+S</span></td>
<td>Save dashboard</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+E</span></td>
<td>Export dashboard</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+E</span></td>
<td>Export dashboard</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+H</span></td>
<td>Hide row controls</td>
@ -44,10 +52,10 @@
<td><span class="label label-info">CTRL+Z</span></td>
<td>Zoom out</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+I</span></td>
<td>Quick snapshot</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+I</span></td>
<td>Quick snapshot</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+O</span></td>
<td>Enable/Disable shared graph crosshair</td>

@ -78,9 +78,9 @@
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>

@ -1,5 +1,6 @@
<cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
<div class="editor-row">
<div class="editor-row" style="padding: 2rem 0">
<div class="section">
<h5>Prefix matching</h5>
<div class="editor-option">

@ -1,22 +1,27 @@
<h3 class="page-heading">CloudWatch details</h3>
<div class="gf-form-group">
<div class="gf-form-group max-width-30">
<div class="gf-form">
<label class="gf-form-label width-14">
Credentials profile name<tip>Credentials profile name, as specified in ~/.aws/credentials, leave blank for default</tip>
</label>
<input type="text" class="gf-form-input max-width-15" ng-model='ctrl.current.database' placeholder="default"></input>
<label class="gf-form-label width-13">Credentials profile name</label>
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.database' placeholder="default"></input>
<info-popover mode="right-absolute">
Credentials profile name, as specified in ~/.aws/credentials, leave blank for default
</info-popover>
</div>
<div class="gf-form">
<label class="gf-form-label width-14">
Default Region<tip>Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.</tip>
</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input max-width-15" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select>
<label class="gf-form-label width-13">Default Region</label>
<div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon">
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select>
<info-popover mode="right-absolute">
Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
</info-popover>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-14">Custom Metrics namespace<tip>Namespaces of Custom Metrics</tip></label>
<input type="text" class="gf-form-input max-width-15" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input>
<label class="gf-form-label width-13">Custom Metrics namespace</label>
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input>
<info-popover mode="right-absolute">
Namespaces of Custom Metrics
</info-popover>
</div>
</div>

@ -55,7 +55,7 @@ export default class InfluxDatasource {
query = query.replace(/\$interval/g, (target.interval || options.interval));
return query;
}).join("\n");
}).join(";");
// replace grafana variables
allQueries = allQueries.replace(/\$timeFilter/g, timeFilter);
@ -107,7 +107,7 @@ export default class InfluxDatasource {
var timeFilter = this.getTimeFilter({rangeRaw: options.rangeRaw});
var query = options.annotation.query.replace('$timeFilter', timeFilter);
query = this.templateSrv.replace(query);
query = this.templateSrv.replace(query, null, 'regex');
return this._seriesQuery(query).then(data => {
if (!data || !data.results || !data.results[0]) {
@ -133,6 +133,17 @@ export default class InfluxDatasource {
return this._influxRequest('GET', '/query', {q: query, epoch: 'ms'});
}
serializeParams(params) {
if (!params) { return '';}
return _.reduce(params, (memo, value, key) => {
if (value === null || value === undefined) { return memo; }
memo.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
return memo;
}, []).join("&");
}
testDatasource() {
return this.metricFindQuery('SHOW MEASUREMENTS LIMIT 1').then(() => {
return { status: "success", message: "Data source is working", title: "Success" };
@ -166,6 +177,7 @@ export default class InfluxDatasource {
data: data,
precision: "ms",
inspect: { type: 'influxdb' },
paramSerializer: this.serializeParams,
};
options.headers = options.headers || {};

@ -16,7 +16,7 @@ export class OpenTsConfigCtrl {
tsdbVersions = [
{name: '<=2.1', value: 1},
{name: '2.2', value: 2},
{name: '>=2.2', value: 2},
];
tsdbResolutions = [

@ -157,7 +157,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
var interpolated;
try {
interpolated = templateSrv.replace(expr);
interpolated = templateSrv.replace(expr, {}, interpolateQueryExpr);
} catch (err) {
return $q.reject(err);
}

@ -1,40 +1,32 @@
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">Mode</span>
<div class="gf-form-select-wrapper max-width-10">
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'">
<span class="gf-form-label">
<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i>
</span>
</div>
</div>
<div>
<div class="section gf-form-group">
<h5 class="section-heading">Options</h5>
<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'">
<div class="gf-form">
<span class="gf-form-label width-10">Search options</span>
<span class="gf-form-label">Query</span>
<gf-form-switch class="gf-form" label="Starred" label-class="width-9" checked="ctrl.panel.starred" on-change="ctrl.refresh()"></gf-form-switch>
<gf-form-switch class="gf-form" label="Recently viewed" label-class="width-9" checked="ctrl.panel.recent" on-change="ctrl.refresh()"></gf-form-switch>
<gf-form-switch class="gf-form" label="Search" label-class="width-9" checked="ctrl.panel.search" on-change="ctrl.refresh()"></gf-form-switch>
<input type="text" class="gf-form-input" placeholder="title query"
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
<gf-form-switch class="gf-form" label="Show headings" label-class="width-9" checked="ctrl.panel.headings" on-change="ctrl.refresh()"></gf-form-switch>
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Max items</span>
<input class="gf-form-input max-width-5" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
</div>
</div>
<div class="gf-form">
<span class="gf-form-label">Tags</span>
<div class="section gf-form-group">
<h5 class="section-heading">Search</h5>
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
</bootstrap-tagsinput>
</div>
</div>
<div class="gf-form">
<span class="gf-form-label width-6">Query</span>
<input type="text" class="gf-form-input" placeholder="title query" ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<span class="gf-form-label width-6">Tags</span>
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
</bootstrap-tagsinput>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">Limit number to</span>
<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
</div>
</div>
</div>

@ -1,12 +1,17 @@
<div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in ctrl.dashList">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
<span class="dashlist-title">
{{dash.title}}
</span>
<span class="dashlist-star">
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i>
</span>
</a>
</div>
<div class="dashlist" ng-repeat="group in ctrl.groups">
<div class="dashlist-section" ng-if="group.show">
<h6 class="dashlist-section-header" ng-show="ctrl.panel.headings">
{{group.header}}
</h6>
<div class="dashlist-item" ng-repeat="dash in group.list">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
<span class="dashlist-title">
{{dash.title}}
</span>
<span class="dashlist-star">
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i>
</span>
</a>
</div>
</div>
</div>

@ -7,16 +7,19 @@ import {impressions} from 'app/features/dashboard/impression_store';
// Set and populate defaults
var panelDefaults = {
mode: 'starred',
query: '',
limit: 10,
tags: []
tags: [],
recent: false,
search: false,
starred: true,
headings: true,
};
class DashListCtrl extends PanelCtrl {
static templateUrl = 'module.html';
dashList: any[];
groups: any[];
modes: any[];
/** @ngInject */
@ -31,6 +34,31 @@ class DashListCtrl extends PanelCtrl {
this.events.on('refresh', this.onRefresh.bind(this));
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.groups = [
{list: [], show: false, header: "Starred dashboards",},
{list: [], show: false, header: "Recently viewed dashboards"},
{list: [], show: false, header: "Search"},
];
// update capability
if (this.panel.mode) {
if (this.panel.mode === 'starred') {
this.panel.starred = true;
this.panel.headings = false;
}
if (this.panel.mode === 'recently viewed') {
this.panel.recent = true;
this.panel.starred = false;
this.panel.headings = false;
}
if (this.panel.mode === 'search') {
this.panel.search = true;
this.panel.starred = false;
this.panel.headings = false;
}
delete this.panel.mode;
}
}
onInitEditMode() {
@ -40,34 +68,60 @@ class DashListCtrl extends PanelCtrl {
}
onRefresh() {
var params: any = {limit: this.panel.limit};
if (this.panel.mode === 'recently viewed') {
var dashIds = _.first(impressions.getDashboardOpened(), this.panel.limit);
return this.backendSrv.search({dashboardIds: dashIds, limit: this.panel.limit}).then(result => {
this.dashList = dashIds.map(orderId => {
return _.find(result, dashboard => {
return dashboard.id === orderId;
});
}).filter(el => {
return el !== undefined;
});
var promises = [];
this.renderingCompleted();
});
promises.push(this.getRecentDashboards());
promises.push(this.getStarred());
promises.push(this.getSearch());
return Promise.all(promises)
.then(this.renderingCompleted.bind(this));
}
getSearch() {
this.groups[2].show = this.panel.search;
if (!this.panel.search) {
return Promise.resolve();
}
if (this.panel.mode === 'starred') {
params.starred = "true";
} else {
params.query = this.panel.query;
params.tag = this.panel.tags;
var params = {
limit: this.panel.limit,
query: this.panel.query,
tag: this.panel.tags,
};
return this.backendSrv.search(params).then(result => {
this.groups[2].list = result;
});
}
getStarred() {
this.groups[0].show = this.panel.starred;
if (!this.panel.starred) {
return Promise.resolve();
}
var params = {limit: this.panel.limit, starred: "true"};
return this.backendSrv.search(params).then(result => {
this.dashList = result;
this.renderingCompleted();
this.groups[0].list = result;
});
}
getRecentDashboards() {
this.groups[1].show = this.panel.recent;
if (!this.panel.recent) {
return Promise.resolve();
}
var dashIds = _.first(impressions.getDashboardOpened(), this.panel.limit);
return this.backendSrv.search({dashboardIds: dashIds, limit: this.panel.limit}).then(result => {
this.groups[1].list = dashIds.map(orderId => {
return _.find(result, dashboard => {
return dashboard.id === orderId;
});
}).filter(el => {
return el !== undefined;
});
});
}
}

@ -151,8 +151,10 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
}
function processOffsetHook(plot, gridMargin) {
if (panel.yaxis) { gridMargin.left = 20; }
if (panel.rightYAxisLabel) { gridMargin.right = 20; }
var left = panel.yaxes[0];
var right = panel.yaxes[1];
if (left.show && left.label) { gridMargin.left = 20; }
if (right.show && right.label) { gridMargin.right = 20; }
}
// Function for rendering panel

@ -9,7 +9,7 @@ function ($) {
var ctrl = scope.ctrl;
var panel = ctrl.panel;
var $tooltip = $('<div id="tooltip">');
var $tooltip = $('<div id="tooltip" class="graph-tooltip">');
this.findHoverIndexFromDataPoints = function(posX, series, last) {
var ps = series.datapoints.pointsize;
@ -33,9 +33,8 @@ function ($) {
return j - 1;
};
this.showTooltip = function(absoluteTime, relativeTime, innerHtml, pos) {
var body = '<div class="graph-tooltip small"><div class="graph-tooltip-time">'+ absoluteTime +
' <span class="tone-down">(' + relativeTime + ')</span></div> ';
this.showTooltip = function(absoluteTime, innerHtml, pos) {
var body = '<div class="graph-tooltip-time">'+ absoluteTime + '</div>';
body += innerHtml + '</div>';
$tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY);
};
@ -109,7 +108,7 @@ function ($) {
var plot = elem.data().plot;
var plotData = plot.getData();
var seriesList = getSeriesFn();
var group, value, absoluteTime, relativeTime, hoverInfo, i, series, seriesHtml, tooltipFormat;
var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat;
if (panel.tooltip.msResolution) {
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
@ -132,7 +131,6 @@ function ($) {
seriesHtml = '';
relativeTime = dashboard.getRelativeTime(seriesHoverInfo.time);
absoluteTime = dashboard.formatDate(seriesHoverInfo.time, tooltipFormat);
for (i = 0; i < seriesHoverInfo.length; i++) {
@ -142,17 +140,22 @@ function ($) {
continue;
}
var highlightClass = '';
if (item && i === item.seriesIndex) {
highlightClass = 'graph-tooltip-list-item--highlight';
}
series = seriesList[i];
value = series.formatValue(hoverInfo.value);
seriesHtml += '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
seriesHtml += '<div class="graph-tooltip-list-item ' + highlightClass + '"><div class="graph-tooltip-series-name">';
seriesHtml += '<i class="fa fa-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
plot.highlight(i, hoverInfo.hoverIndex);
}
self.showTooltip(absoluteTime, relativeTime, seriesHtml, pos);
self.showTooltip(absoluteTime, seriesHtml, pos);
}
// single series tooltip
else if (item) {
@ -169,12 +172,11 @@ function ($) {
value = series.formatValue(value);
relativeTime = dashboard.getRelativeTime(item.datapoint[0]);
absoluteTime = dashboard.formatDate(item.datapoint[0], tooltipFormat);
group += '<div class="graph-tooltip-value">' + value + '</div>';
self.showTooltip(absoluteTime, relativeTime, group, pos);
self.showTooltip(absoluteTime, group, pos);
}
// no hit
else {

@ -49,7 +49,6 @@ function (angular, _, $) {
position: 'bottom center',
template: '<gf-color-picker></gf-color-picker>',
model: {
autoClose: true,
series: series,
toggleAxis: function() {
ctrl.toggleAxis(series);
@ -194,9 +193,9 @@ function (angular, _, $) {
}
var topPadding = 6;
$container.css("height", maxHeight - topPadding);
$container.css("max-height", maxHeight - topPadding);
} else {
$container.css("height", "");
$container.css("max-height", "");
}
}
}

@ -5,6 +5,7 @@ import './legend';
import './series_overrides_ctrl';
import template from './template';
import angular from 'angular';
import moment from 'moment';
import kbn from 'app/core/utils/kbn';
import _ from 'lodash';
@ -108,13 +109,14 @@ class GraphCtrl extends MetricsPanelCtrl {
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel, angular.copy(panelDefaults));
_.defaults(this.panel.tooltip, panelDefaults.tooltip);
_.defaults(this.panel.grid, panelDefaults.grid);
_.defaults(this.panel.legend, panelDefaults.legend);
this.colors = $scope.$root.colors;
this.events.on('render', this.onRender.bind(this));
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this));
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
@ -159,7 +161,7 @@ class GraphCtrl extends MetricsPanelCtrl {
onDataSnapshotLoad(snapshotData) {
this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
this.onDataReceived(snapshotData.data);
this.onDataReceived(snapshotData);
}
onDataError(err) {
@ -200,6 +202,7 @@ class GraphCtrl extends MetricsPanelCtrl {
datapoints: datapoints,
alias: alias,
color: color,
unit: seriesData.unit,
});
if (datapoints && datapoints.length > 0) {
@ -210,14 +213,25 @@ class GraphCtrl extends MetricsPanelCtrl {
}
this.datapointsCount += datapoints.length;
this.panel.tooltip.msResolution = this.panel.tooltip.msResolution || series.isMsResolutionNeeded();
}
series.applySeriesOverrides(this.panel.seriesOverrides);
return series;
}
onRender() {
if (!this.seriesList) { return; }
for (let series of this.seriesList) {
series.applySeriesOverrides(this.panel.seriesOverrides);
if (series.unit) {
this.panel.yaxes[series.yaxis-1].format = series.unit;
}
}
}
changeSeriesColor(series, color) {
series.color = color;
this.panel.aliasColors[series.alias] = series.color;
@ -234,7 +248,6 @@ class GraphCtrl extends MetricsPanelCtrl {
} else {
this.toggleSeriesExclusiveMode(serie);
}
this.render();
}

@ -59,7 +59,11 @@ define([
openOn: 'click',
template: '<gf-color-picker></gf-color-picker>',
model: {
autoClose: true,
colorSelected: $scope.colorSelected,
},
onClose: function() {
$scope.ctrl.render();
}
});
};

@ -69,7 +69,7 @@
</gf-form-switch>
<gf-form-switch class="gf-form" ng-show="ctrl.panel.stack"
label="Percent" label-class="width-7"
checked="ctrl.panel.percent" on-change="ctrl.render()">
checked="ctrl.panel.percentage" on-change="ctrl.render()">
</gf-form-switch>
<div class="gf-form" ng-show="ctrl.panel.stack">
<label class="gf-form-label width-7">Tooltip value</label>

@ -13,7 +13,6 @@
label="To the right" label-class="width-7"
checked="ctrl.panel.legend.rightSide" on-change="ctrl.render()">
</gf-form-switch>
<div ng-if="ctrl.panel.legend.rightSide" class="gf-form">
<label class="gf-form-label width-7">Width</label>
<input type="number" class="gf-form-input max-width-5" placeholder="250" bs-tooltip="'Set a min-width for the legend side table/block'" data-placement="right" ng-model="ctrl.panel.legend.sideWidth" ng-change="ctrl.render()" ng-model-onblur>
@ -31,7 +30,7 @@
<gf-form-switch class="gf-form max-width-12"
label="Max" label-class="width-6" switch-class="max-width-5"
checked="ctrl.panel.legend.max" on-change="ctrl.legendCaluesOptionChanged()">
checked="ctrl.panel.legend.max" on-change="ctrl.legendValuesOptionChanged()">
</gf-form-switch>
</div>

@ -0,0 +1,2 @@
# Plugin List Panel - Native Plugin

@ -0,0 +1,40 @@
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">Mode</span>
<div class="gf-form-select-wrapper max-width-10">
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'">
<span class="gf-form-label">
<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i>
</span>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'">
<div class="gf-form">
<span class="gf-form-label width-10">Search options</span>
<span class="gf-form-label">Query</span>
<input type="text" class="gf-form-input" placeholder="title query"
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<span class="gf-form-label">Tags</span>
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
</bootstrap-tagsinput>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">Limit number to</span>
<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
</div>
</div>
</div>

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<g>
<g>
<path style="fill:#666666;" d="M8.842,11.219h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,11.219z"/>
<path style="fill:#666666;" d="M0.008,2.113l2.054-2.054C0.966,0.139,0.089,1.016,0.008,2.113z"/>
<polygon style="fill:#666666;" points="0,2.998 0,5.533 5.484,0.05 2.948,0.05 "/>
<polygon style="fill:#666666;" points="6.361,0.05 0,6.411 0,8.946 8.896,0.05 "/>
<path style="fill:#666666;" d="M11.169,2.277c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V2.277z"/>
<path style="fill:#666666;" d="M9.654,0.169L0.119,9.704c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,0.812,10.247,0.37,9.654,0.169z"/>
<polygon style="fill:#666666;" points="11.169,5.479 5.429,11.219 7.964,11.219 11.169,8.014 "/>
</g>
<path style="fill:#898989;" d="M88.146,11.031H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,10.212,89.157,11.031,88.146,11.031z"/>
<g>
<path style="fill:#666666;" d="M8.842,23.902h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,23.902z"/>
<path style="fill:#666666;" d="M0.008,14.796l2.054-2.054C0.966,12.822,0.089,13.699,0.008,14.796z"/>
<polygon style="fill:#666666;" points="0,15.681 0,18.216 5.484,12.733 2.948,12.733 "/>
<polygon style="fill:#666666;" points="6.361,12.733 0,19.094 0,21.629 8.896,12.733 "/>
<path style="fill:#666666;" d="M11.169,14.96c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V14.96z"/>
<path style="fill:#666666;" d="M9.654,12.852l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,13.495,10.247,13.053,9.654,12.852z"/>
<polygon style="fill:#666666;" points="11.169,18.162 5.429,23.902 7.964,23.902 11.169,20.697 "/>
</g>
<path style="fill:#898989;" d="M88.146,23.714H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,22.895,89.157,23.714,88.146,23.714z"/>
<g>
<path style="fill:#666666;" d="M8.842,36.585h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,36.585z"/>
<path style="fill:#666666;" d="M0.008,27.479l2.054-2.054C0.966,25.505,0.089,26.382,0.008,27.479z"/>
<polygon style="fill:#666666;" points="0,28.364 0,30.899 5.484,25.416 2.948,25.416 "/>
<polygon style="fill:#666666;" points="6.361,25.416 0,31.777 0,34.312 8.896,25.416 "/>
<path style="fill:#666666;" d="M11.169,27.643c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V27.643z"/>
<path style="fill:#666666;" d="M9.654,25.535L0.119,35.07c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,26.178,10.247,25.736,9.654,25.535z"/>
<polygon style="fill:#666666;" points="11.169,30.845 5.429,36.585 7.964,36.585 11.169,33.38 "/>
</g>
<path style="fill:#898989;" d="M88.146,36.397H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,35.578,89.157,36.397,88.146,36.397z"/>
<g>
<path style="fill:#666666;" d="M8.842,49.268h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,49.268z"/>
<path style="fill:#666666;" d="M0.008,40.162l2.054-2.054C0.966,38.188,0.089,39.065,0.008,40.162z"/>
<polygon style="fill:#666666;" points="0,41.047 0,43.582 5.484,38.099 2.948,38.099 "/>
<polygon style="fill:#666666;" points="6.361,38.099 0,44.46 0,46.995 8.896,38.099 "/>
<path style="fill:#666666;" d="M11.169,40.326c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V40.326z"/>
<path style="fill:#666666;" d="M9.654,38.218l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,38.861,10.247,38.419,9.654,38.218z"/>
<polygon style="fill:#666666;" points="11.169,43.528 5.429,49.268 7.964,49.268 11.169,46.063 "/>
</g>
<path style="fill:#898989;" d="M88.146,49.08H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,48.261,89.157,49.08,88.146,49.08z"/>
<g>
<path style="fill:#666666;" d="M8.842,61.951h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,61.951z"/>
<path style="fill:#666666;" d="M0.008,52.845l2.054-2.054C0.966,50.871,0.089,51.748,0.008,52.845z"/>
<polygon style="fill:#666666;" points="0,53.73 0,56.265 5.484,50.782 2.948,50.782 "/>
<polygon style="fill:#666666;" points="6.361,50.782 0,57.143 0,59.678 8.896,50.782 "/>
<path style="fill:#666666;" d="M11.169,53.009c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V53.009z"/>
<path style="fill:#666666;" d="M9.654,50.901l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,51.544,10.247,51.102,9.654,50.901z"/>
<polygon style="fill:#666666;" points="11.169,56.211 5.429,61.951 7.964,61.951 11.169,58.746 "/>
</g>
<path style="fill:#898989;" d="M88.146,61.763H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,60.944,89.157,61.763,88.146,61.763z"/>
<g>
<path style="fill:#666666;" d="M8.842,74.634h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,74.634z"/>
<path style="fill:#666666;" d="M0.008,65.528l2.054-2.054C0.966,63.554,0.089,64.431,0.008,65.528z"/>
<polygon style="fill:#666666;" points="0,66.413 0,68.948 5.484,63.465 2.948,63.465 "/>
<polygon style="fill:#666666;" points="6.361,63.465 0,69.826 0,72.361 8.896,63.465 "/>
<path style="fill:#666666;" d="M11.169,65.692c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V65.692z"/>
<path style="fill:#666666;" d="M9.654,63.584l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,64.227,10.247,63.785,9.654,63.584z"/>
<polygon style="fill:#666666;" points="11.169,68.894 5.429,74.634 7.964,74.634 11.169,71.429 "/>
</g>
<path style="fill:#898989;" d="M88.146,74.446H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,73.627,89.157,74.446,88.146,74.446z"/>
<g>
<path style="fill:#666666;" d="M8.842,87.317h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,87.317z"/>
<path style="fill:#666666;" d="M0.008,78.211l2.054-2.054C0.966,76.237,0.089,77.114,0.008,78.211z"/>
<polygon style="fill:#666666;" points="0,79.096 0,81.631 5.484,76.148 2.948,76.148 "/>
<polygon style="fill:#666666;" points="6.361,76.148 0,82.509 0,85.044 8.896,76.148 "/>
<path style="fill:#666666;" d="M11.169,78.375c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V78.375z"/>
<path style="fill:#666666;" d="M9.654,76.267l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,76.91,10.247,76.468,9.654,76.267z"/>
<polygon style="fill:#666666;" points="11.169,81.577 5.429,87.317 7.964,87.317 11.169,84.112 "/>
</g>
<path style="fill:#898989;" d="M88.146,87.129H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,86.31,89.157,87.129,88.146,87.129z"/>
<g>
<path style="fill:#666666;" d="M8.842,100h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,100z"/>
<path style="fill:#666666;" d="M0.008,90.894l2.054-2.054C0.966,88.92,0.089,89.797,0.008,90.894z"/>
<polygon style="fill:#666666;" points="0,91.779 0,94.314 5.484,88.831 2.948,88.831 "/>
<polygon style="fill:#666666;" points="6.361,88.831 0,95.192 0,97.727 8.896,88.831 "/>
<path style="fill:#666666;" d="M11.169,91.058c0-0.068-0.004-0.134-0.01-0.2L2.027,99.99c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V91.058z"/>
<path style="fill:#666666;" d="M9.654,88.95l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,89.593,10.247,89.151,9.654,88.95z"/>
<polygon style="fill:#666666;" points="11.169,94.26 5.429,100 7.964,100 11.169,96.795 "/>
</g>
<path style="fill:#898989;" d="M88.146,99.812H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,98.993,89.157,99.812,88.146,99.812z"/>
<circle style="fill:#F7941E;" cx="96.125" cy="5.637" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="18.37" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="31.104" r="3.875"/>
<circle style="fill:#F7941E;" cx="96.125" cy="43.837" r="3.875"/>
<circle style="fill:#F7941E;" cx="96.125" cy="56.57" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="69.304" r="3.875"/>
<circle style="fill:#F7941E;" cx="96.125" cy="82.037" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="94.77" r="3.875"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

@ -0,0 +1,30 @@
<div class="pluginlist">
<div class="pluginlist-section" ng-repeat="category in ctrl.viewModel">
<h6 class="pluginlist-section-header">
{{category.header}}
</h6>
<div class="pluginlist-item" ng-repeat="plugin in category.list">
<a class="pluginlist-link pluginlist-link-{{plugin.state}} pointer" href="plugins/{{plugin.id}}/edit">
<span>
<img ng-src="{{plugin.info.logos.small}}" class="pluginlist-image">
<span class="pluginlist-title">{{plugin.name}}</span>
<span class="pluginlist-version">v{{plugin.info.version}}</span>
</span>
<span class="pluginlist-message pluginlist-message--update" ng-show="plugin.hasUpdate" ng-click="ctrl.updateAvailable(plugin, $event)" bs-tooltip="'New version: ' + plugin.latestVersion">
Update available!
</span>
<span class="pluginlist-message pluginlist-message--enable" ng-show="!plugin.enabled && !plugin.hasUpdate">
Enable now
</span>
<span class="pluginlist-message pluginlist-message--no-update" ng-show="plugin.enabled && !plugin.hasUpdate">
Up to date
</span>
</a>
</div>
<div class="pluginlist-item" ng-show="category.list.length === 0">
<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="http://grafana.net/plugins/">
<span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span>
</a>
</div>
</div>
</div>

@ -0,0 +1,74 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import config from 'app/core/config';
import {PanelCtrl} from '../../../features/panel/panel_ctrl';
// Set and populate defaults
var panelDefaults = {
};
class PluginListCtrl extends PanelCtrl {
static templateUrl = 'module.html';
pluginList: any[];
viewModel: any;
/** @ngInject */
constructor($scope, $injector, private backendSrv, private $location) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.pluginList = [];
this.viewModel = [
{header: "Installed Apps", list: [], type: 'app'},
{header: "Installed Panels", list: [], type: 'panel'},
{header: "Installed Datasources", list: [], type: 'datasource'},
];
this.update();
}
onInitEditMode() {
this.editorTabIndex = 1;
this.addEditorTab('Options', 'public/app/plugins/panel/pluginlist/editor.html');
}
gotoPlugin(plugin, evt) {
if (evt) { evt.stopPropagation(); }
this.$location.url(`plugins/${plugin.id}/edit`);
}
updateAvailable(plugin, $event) {
$event.stopPropagation();
$event.preventDefault();
var modalScope = this.$scope.$new(true);
modalScope.plugin = plugin;
this.publishAppEvent('show-modal', {
src: 'public/app/features/plugins/partials/update_instructions.html',
scope: modalScope
});
}
update() {
this.backendSrv.get('api/plugins', {embedded: 0, core: 0}).then(plugins => {
this.pluginList = plugins;
this.viewModel[0].list = _.filter(plugins, {type: 'app'});
this.viewModel[1].list = _.filter(plugins, {type: 'panel'});
this.viewModel[2].list = _.filter(plugins, {type: 'datasource'});
for (let plugin of this.pluginList) {
if (plugin.hasUpdate) {
plugin.state = 'has-update';
} else if (!plugin.enabled) {
plugin.state = 'not-enabled';
}
}
});
}
}
export {PluginListCtrl, PluginListCtrl as PanelCtrl}

@ -0,0 +1,16 @@
{
"type": "panel",
"name": "Plugin list",
"id": "pluginlist",
"info": {
"author": {
"name": "Grafana Project",
"url": "http://grafana.org"
},
"logos": {
"small": "img/icn-dashlist-panel.svg",
"large": "img/icn-dashlist-panel.svg"
}
}
}

@ -56,7 +56,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this));
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
this.events.on('data-snapshot-load', this.onDataReceived.bind(this));
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
}
@ -71,10 +71,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
this.render();
}
onDataSnapshotLoad(snapshotData) {
this.onDataReceived(snapshotData.data);
}
onDataError(err) {
this.onDataReceived({data: []});
}

@ -60,7 +60,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this));
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
this.events.on('data-snapshot-load', this.onDataReceived.bind(this));
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.events.on('init-panel-actions', this.onInitPanelActions.bind(this));
}
@ -77,19 +77,15 @@ class TablePanelCtrl extends MetricsPanelCtrl {
this.pageIndex = 0;
if (this.panel.transform === 'annotations') {
this.setTimeQueryStart();
return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => {
this.dataRaw = annotations;
this.render();
return {data: annotations};
});
}
return super.issueQueries(datasource);
}
onDataSnapshotLoad(data) {
this.onDataReceived(data.data);
}
onDataError(err) {
this.dataRaw = [];
this.render();
@ -218,6 +214,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
if (data) {
renderPanel();
}
ctrl.renderingCompleted();
});
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save