From 36e99ac531651dac15cefa7e303294c262670094 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 19 Jan 2016 21:46:43 +0100 Subject: [PATCH 001/107] feat(tech): force create symlinks --- symlink_git_hooks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symlink_git_hooks.sh b/symlink_git_hooks.sh index 7c3b58feaca..c8a90432a70 100755 --- a/symlink_git_hooks.sh +++ b/symlink_git_hooks.sh @@ -1,3 +1,3 @@ #/bin/bash -ln -s .hooks/* .git/hooks/ +ln -s -f .hooks/* .git/hooks/ From c4a0ec844c242c8a890631b020d522dce59164fd Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Wed, 20 Jan 2016 06:13:45 +0800 Subject: [PATCH 002/107] get apiPlugins working again. --- pkg/plugins/api_plugin.go | 31 +++++++++++++++++++++++++++++++ pkg/plugins/models.go | 9 +++++++++ 2 files changed, 40 insertions(+) create mode 100644 pkg/plugins/api_plugin.go diff --git a/pkg/plugins/api_plugin.go b/pkg/plugins/api_plugin.go new file mode 100644 index 00000000000..b4296479ec1 --- /dev/null +++ b/pkg/plugins/api_plugin.go @@ -0,0 +1,31 @@ +package plugins + +import ( + "encoding/json" + "strings" + + "github.com/grafana/grafana/pkg/models" +) + +type ApiPluginRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"reqSignedIn"` + ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` + ReqRole models.RoleType `json:"reqRole"` + Url string `json:"url"` +} + +type ApiPlugin struct { + PluginBase + Routes []*ApiPluginRoute `json:"routes"` +} + +func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { + if err := decoder.Decode(&app); err != nil { + return err + } + + ApiPlugins[app.Id] = app + return nil +} diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 375bdf1a52c..69230cac629 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -73,3 +73,12 @@ func NewEnabledPlugins() EnabledPlugins { Apps: make([]*AppPlugin, 0), } } + +func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { + if err := decoder.Decode(&app); err != nil { + return err + } + + ApiPlugins[app.Id] = app + return nil +} From 92a085550e122e80470b3fb2ca5a631db77a0dc8 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Wed, 20 Jan 2016 06:17:48 +0800 Subject: [PATCH 003/107] fix up imports --- pkg/plugins/api_plugin.go | 1 - pkg/plugins/models.go | 25 ------------------------- 2 files changed, 26 deletions(-) diff --git a/pkg/plugins/api_plugin.go b/pkg/plugins/api_plugin.go index b4296479ec1..0a47b7d18b2 100644 --- a/pkg/plugins/api_plugin.go +++ b/pkg/plugins/api_plugin.go @@ -2,7 +2,6 @@ package plugins import ( "encoding/json" - "strings" "github.com/grafana/grafana/pkg/models" ) diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 69230cac629..b302181e840 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -2,8 +2,6 @@ package plugins import ( "encoding/json" - - "github.com/grafana/grafana/pkg/models" ) type PluginLoader interface { @@ -44,20 +42,6 @@ type PluginStaticRoute struct { PluginId string } -type ApiPluginRoute struct { - Path string `json:"path"` - Method string `json:"method"` - ReqSignedIn bool `json:"reqSignedIn"` - ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` - ReqRole models.RoleType `json:"reqRole"` - Url string `json:"url"` -} - -type ApiPlugin struct { - PluginBase - Routes []*ApiPluginRoute `json:"routes"` -} - type EnabledPlugins struct { Panels []*PanelPlugin DataSources map[string]*DataSourcePlugin @@ -73,12 +57,3 @@ func NewEnabledPlugins() EnabledPlugins { Apps: make([]*AppPlugin, 0), } } - -func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { - if err := decoder.Decode(&app); err != nil { - return err - } - - ApiPlugins[app.Id] = app - return nil -} From 1c5be9225981569471767b91f8913c32ae7653c6 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 19 Jan 2016 17:15:24 -0800 Subject: [PATCH 004/107] Removed unused component from playlist code --- public/app/features/playlist/partials/playlist-remove.html | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 public/app/features/playlist/partials/playlist-remove.html diff --git a/public/app/features/playlist/partials/playlist-remove.html b/public/app/features/playlist/partials/playlist-remove.html deleted file mode 100644 index 8474f97f8de..00000000000 --- a/public/app/features/playlist/partials/playlist-remove.html +++ /dev/null @@ -1,5 +0,0 @@ -

Are you sure want to delete "{{playlist.title}}" playlist?

-

- - -

From d0074b25db989f583ee1b03e42cc2765cd6781a1 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 20 Jan 2016 18:30:17 +0900 Subject: [PATCH 005/107] (prometheus) add explanation about incompatibility of metric find query --- docs/sources/datasources/prometheus.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/datasources/prometheus.md b/docs/sources/datasources/prometheus.md index 61f6c0d66e8..a7959a41c51 100644 --- a/docs/sources/datasources/prometheus.md +++ b/docs/sources/datasources/prometheus.md @@ -51,6 +51,8 @@ Name | Description For details of `metric names` & `label names`, and `label values`, please refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels). +> Note: The part of queries is incompatible with the version before 2.6, if you specify like `foo.*`, please change like `metrics(foo.*)`. + You can create a template variable in Grafana and have that variable filled with values from any Prometheus metric exploration query. You can then use this variable in your Prometheus metric queries. From 4f7f03a28c4d8aed480429ce62dd8158d78f172d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Jan 2016 10:39:58 +0100 Subject: [PATCH 006/107] fix(playlist): fix for memory leak when running playlist for a long period, fixes #3794 --- CHANGELOG.md | 7 +++++-- public/app/features/dashlinks/module.js | 2 +- public/app/features/playlist/playlist_srv.ts | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb01152d58..e97ab145c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 3.0.0 (unrelased master branch) -### New Features ### +### New Features * **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/pull/3655) * **Metadata**: Settings panel now shows dashboard metadata, closes [#3304](https://github.com/grafana/grafana/issues/3304) * **InfluxDB**: Support for policy selection in query editor, closes [#2018](https://github.com/grafana/grafana/issues/2018) @@ -10,11 +10,14 @@ **InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds. Can easily be installed via improved plugin system, closes #3523 **KairosDB** The data source is no longer included in default builds. Can easily be installed via improved plugin system, closes #3524 -### Enhancements ### +### Enhancements * **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/pull/3458) * **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/pull/3584) * **snapshot**: Annotations are now included in snapshots, closes [#3635](https://github.com/grafana/grafana/pull/3635) +### Bug fixes +* **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794) + # 2.6.1 (unrelased, 2.6.x branch) ### New Features diff --git a/public/app/features/dashlinks/module.js b/public/app/features/dashlinks/module.js index b7029e6cf2d..f133290ceb2 100644 --- a/public/app/features/dashlinks/module.js +++ b/public/app/features/dashlinks/module.js @@ -159,7 +159,7 @@ function (angular, _) { }; updateDashLinks(); - $rootScope.onAppEvent('dash-links-updated', updateDashLinks, $rootScope); + $rootScope.onAppEvent('dash-links-updated', updateDashLinks, $scope); }); module.controller('DashLinkEditorCtrl', function($scope, $rootScope) { diff --git a/public/app/features/playlist/playlist_srv.ts b/public/app/features/playlist/playlist_srv.ts index 0570a3bfc85..8c6be5edaf5 100644 --- a/public/app/features/playlist/playlist_srv.ts +++ b/public/app/features/playlist/playlist_srv.ts @@ -1,6 +1,7 @@ /// import angular from 'angular'; +import config from 'app/core/config'; import coreModule from '../../core/core_module'; import kbn from 'app/core/utils/kbn'; @@ -20,10 +21,9 @@ class PlaylistSrv { var playedAllDashboards = this.index > this.dashboards.length - 1; if (playedAllDashboards) { - this.start(this.playlistId); + window.location.href = `${config.appSubUrl}/playlists/play/${this.playlistId}`; } else { var dash = this.dashboards[this.index]; - this.$location.url('dashboard/' + dash.uri); this.index++; From 1979143e7c3cb9b92c5e4b637fe783a4eaae3c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Jan 2016 11:20:15 +0100 Subject: [PATCH 007/107] fix(export): fix export dashboard to json, been broken in master for 2-3 weeks --- public/app/grafana.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/grafana.ts b/public/app/grafana.ts index 7316a8164bc..d9ad3a449d3 100644 --- a/public/app/grafana.ts +++ b/public/app/grafana.ts @@ -1,6 +1,7 @@ /// import 'bootstrap'; +import 'vendor/filesaver'; import 'lodash-src'; import 'angular-strap'; import 'angular-route'; From 5f5fcc0e04d237f89b6f5a4b43fbb33c2e0c9282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Jan 2016 11:57:56 +0100 Subject: [PATCH 008/107] fix(submenu): added ngInject comment to dashboard submenu --- public/app/features/dashboard/submenu/submenu.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/submenu/submenu.ts index 7e304d88464..75f0778b01f 100644 --- a/public/app/features/dashboard/submenu/submenu.ts +++ b/public/app/features/dashboard/submenu/submenu.ts @@ -7,6 +7,7 @@ export class SubmenuCtrl { variables: any; dashboard: any; + /** @ngInject */ constructor(private $rootScope, private templateValuesSrv, private dynamicDashboardSrv) { this.annotations = this.dashboard.templating.list; this.variables = this.dashboard.templating.list; From 9e121ef0c84cec7b55e5830e2d1a79c64a99ce32 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 02:21:30 +0800 Subject: [PATCH 009/107] add save button to config page configView directives can update the appModel object. We need a save button to persist the updates. --- public/app/features/apps/partials/edit.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/features/apps/partials/edit.html b/public/app/features/apps/partials/edit.html index aae34b8b38d..20df40bd247 100644 --- a/public/app/features/apps/partials/edit.html +++ b/public/app/features/apps/partials/edit.html @@ -98,6 +98,8 @@
+
+
From 1bca28ad12478dc2bbefb759bde00c337e0783f9 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 02:23:24 +0800 Subject: [PATCH 010/107] include the jsonData in the AppSettings DTO --- pkg/api/dtos/apps.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/api/dtos/apps.go b/pkg/api/dtos/apps.go index b2b33370135..d10883a26d7 100644 --- a/pkg/api/dtos/apps.go +++ b/pkg/api/dtos/apps.go @@ -31,6 +31,7 @@ func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSet dto.Enabled = data.Enabled dto.Pinned = data.Pinned dto.Info = &def.Info + dto.JsonData = data.JsonData } return dto From 958649363291f0a561d39d3bcd082f4103e786bd Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 11:37:48 +0800 Subject: [PATCH 011/107] add apiPluginHeader support --- pkg/plugins/api_plugin.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/plugins/api_plugin.go b/pkg/plugins/api_plugin.go index 0a47b7d18b2..0165370a222 100644 --- a/pkg/plugins/api_plugin.go +++ b/pkg/plugins/api_plugin.go @@ -7,12 +7,13 @@ import ( ) type ApiPluginRoute struct { - Path string `json:"path"` - Method string `json:"method"` - ReqSignedIn bool `json:"reqSignedIn"` - ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` - ReqRole models.RoleType `json:"reqRole"` - Url string `json:"url"` + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"reqSignedIn"` + ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` + ReqRole models.RoleType `json:"reqRole"` + Url string `json:"url"` + Headers []ApiPluginHeader `json:"headers"` } type ApiPlugin struct { @@ -20,6 +21,11 @@ type ApiPlugin struct { Routes []*ApiPluginRoute `json:"routes"` } +type ApiPluginHeader struct { + Name string `json:"name"` + Content string `json:"content"` +} + func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { if err := decoder.Decode(&app); err != nil { return err From 462608517be79d06d6cee3420efcfbda2f6eec85 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 11:38:27 +0800 Subject: [PATCH 012/107] set pluginDir of apiPlugin object --- pkg/plugins/api_plugin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/plugins/api_plugin.go b/pkg/plugins/api_plugin.go index 0165370a222..efb16e696d8 100644 --- a/pkg/plugins/api_plugin.go +++ b/pkg/plugins/api_plugin.go @@ -31,6 +31,8 @@ func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { return err } + app.PluginDir = pluginDir + ApiPlugins[app.Id] = app return nil } From fd52320460fa4760ef26d2d798af0e9a687fbdcf Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 11:39:51 +0800 Subject: [PATCH 013/107] set includedAppId of apiPlugins --- pkg/plugins/app_plugin.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/plugins/app_plugin.go b/pkg/plugins/app_plugin.go index 898065be8a2..d2291d487ac 100644 --- a/pkg/plugins/app_plugin.go +++ b/pkg/plugins/app_plugin.go @@ -59,6 +59,18 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error { } } + // check if we have child apiPlugins + for _, plugin := range ApiPlugins { + if strings.HasPrefix(plugin.PluginDir, app.PluginDir) { + plugin.IncludedInAppId = app.Id + app.Includes = append(app.Includes, AppIncludeInfo{ + Name: plugin.Name, + Id: plugin.Id, + Type: plugin.Type, + }) + } + } + Apps[app.Id] = app return nil } From 423eca6e7d99c4fc6c8bfdfa99181dd834d36820 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 11:41:02 +0800 Subject: [PATCH 014/107] add support for fetching appSettings by appId --- pkg/models/app_settings.go | 15 ++++++++++++++- pkg/services/sqlstore/app_settings.go | 13 +++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pkg/models/app_settings.go b/pkg/models/app_settings.go index 558946d0277..f3b60502cb0 100644 --- a/pkg/models/app_settings.go +++ b/pkg/models/app_settings.go @@ -1,6 +1,13 @@ package models -import "time" +import ( + "errors" + "time" +) + +var ( + ErrAppSettingNotFound = errors.New("AppSetting not found") +) type AppSettings struct { Id int64 @@ -33,3 +40,9 @@ type GetAppSettingsQuery struct { OrgId int64 Result []*AppSettings } + +type GetAppSettingByAppIdQuery struct { + AppId string + OrgId int64 + Result *AppSettings +} diff --git a/pkg/services/sqlstore/app_settings.go b/pkg/services/sqlstore/app_settings.go index e9bfbeaa73b..7d9482e7b22 100644 --- a/pkg/services/sqlstore/app_settings.go +++ b/pkg/services/sqlstore/app_settings.go @@ -9,6 +9,7 @@ import ( func init() { bus.AddHandler("sql", GetAppSettings) + bus.AddHandler("sql", GetAppSettingByAppId) bus.AddHandler("sql", UpdateAppSettings) } @@ -19,6 +20,18 @@ func GetAppSettings(query *m.GetAppSettingsQuery) error { return sess.Find(&query.Result) } +func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error { + appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId} + has, err := x.Get(&appSetting) + if err != nil { + return err + } else if has == false { + return m.ErrAppSettingNotFound + } + query.Result = &appSetting + return nil +} + func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { return inTransaction2(func(sess *session) error { var app m.AppSettings From dcde1422b786faffed5b0f636068526c06f8761e Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Thu, 21 Jan 2016 11:41:59 +0800 Subject: [PATCH 015/107] add support for apiPlugin headers. The header content uses text/Template. If the apiPlugin is bundled in an App, then the appSettings.JsonData is passed to the header template. --- pkg/api/api_plugin.go | 63 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/pkg/api/api_plugin.go b/pkg/api/api_plugin.go index b14afec3a62..751c234bfe8 100644 --- a/pkg/api/api_plugin.go +++ b/pkg/api/api_plugin.go @@ -1,13 +1,17 @@ package api import ( + "bytes" "encoding/json" + "fmt" "net/http" "net/http/httputil" "net/url" + "text/template" "gopkg.in/macaron.v1" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" @@ -34,32 +38,28 @@ func InitApiPluginRoutes(r *macaron.Macaron) { handlers = append(handlers, middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)) } } - handlers = append(handlers, ApiPlugin(route.Url)) + handlers = append(handlers, ApiPlugin(route, plugin.IncludedInAppId)) r.Route(url, route.Method, handlers...) log.Info("Plugin: Adding route %s", url) } } } -func ApiPlugin(routeUrl string) macaron.Handler { +func ApiPlugin(route *plugins.ApiPluginRoute, includedInAppId string) macaron.Handler { return func(c *middleware.Context) { path := c.Params("*") - //Create a HTTP header with the context in it. - ctx, err := json.Marshal(c.SignedInUser) - if err != nil { - c.JsonApiErr(500, "failed to marshal context to json.", err) - return - } - targetUrl, _ := url.Parse(routeUrl) - proxy := NewApiPluginProxy(string(ctx), path, targetUrl) + proxy := NewApiPluginProxy(c, path, route, includedInAppId) proxy.Transport = dataProxyTransport proxy.ServeHTTP(c.Resp, c.Req.Request) } } -func NewApiPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { +func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins.ApiPluginRoute, includedInAppId string) *httputil.ReverseProxy { + targetUrl, _ := url.Parse(route.Url) + director := func(req *http.Request) { + req.URL.Scheme = targetUrl.Scheme req.URL.Host = targetUrl.Host req.Host = targetUrl.Host @@ -69,7 +69,46 @@ func NewApiPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httput // clear cookie headers req.Header.Del("Cookie") req.Header.Del("Set-Cookie") - req.Header.Add("Grafana-Context", ctx) + + //Create a HTTP header with the context in it. + ctxJson, err := json.Marshal(ctx.SignedInUser) + if err != nil { + ctx.JsonApiErr(500, "failed to marshal context to json.", err) + return + } + + req.Header.Add("Grafana-Context", string(ctxJson)) + // add custom headers defined in the plugin config. + for _, header := range route.Headers { + var contentBuf bytes.Buffer + t, err := template.New("content").Parse(header.Content) + if err != nil { + ctx.JsonApiErr(500, fmt.Sprintf("could not parse header content template for header %s.", header.Name), err) + return + } + + jsonData := make(map[string]interface{}) + + if includedInAppId != "" { + //lookup appSettings + query := m.GetAppSettingByAppIdQuery{OrgId: ctx.OrgId, AppId: includedInAppId} + + if err := bus.Dispatch(&query); err != nil { + ctx.JsonApiErr(500, "failed to get AppSettings of includedAppId.", err) + return + } + + jsonData = query.Result.JsonData + } + + err = t.Execute(&contentBuf, jsonData) + if err != nil { + ctx.JsonApiErr(500, fmt.Sprintf("failed to execute header content template for header %s.", header.Name), err) + return + } + log.Debug("Adding header to proxy request. %s: %s", header.Name, contentBuf.String()) + req.Header.Add(header.Name, contentBuf.String()) + } } return &httputil.ReverseProxy{Director: director} From cdcc7a71723094c3f858a521cad27b1378a15e46 Mon Sep 17 00:00:00 2001 From: matteo brancaleoni Date: Wed, 20 Jan 2016 16:44:31 +0100 Subject: [PATCH 016/107] add export to csv in table panel --- public/app/core/utils/kbn.js | 25 ++++++++++++++++++-- public/app/plugins/panel/table/controller.ts | 7 ++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index e323a9223e3..e3a9fbaa39e 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -186,8 +186,29 @@ function($, _) { text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; }); }); - var blob = new Blob([text], { type: "text/csv;charset=utf-8" }); - window.saveAs(blob, 'grafana_data_export.csv'); + kbn.saveSaveBlob(text, 'grafana_data_export.csv'); + }; + + kbn.exportTableDataToCsv = function(table) { + var text = ''; + // add header + _.each(table.columns, function(column) { + text += column.text + ';'; + }); + text += '\n'; + // process data + _.each(table.rows, function(row) { + _.each(row, function(value) { + text += value + ';'; + }); + text += '\n'; + }); + kbn.saveSaveBlob(text, 'grafana_data_export.csv'); + }; + + kbn.saveSaveBlob = function(payload, fname) { + var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); + window.saveAs(blob, fname); }; kbn.stringToJsRegex = function(str) { diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts index f05a36c4cdf..971f37de849 100644 --- a/public/app/plugins/panel/table/controller.ts +++ b/public/app/plugins/panel/table/controller.ts @@ -3,6 +3,7 @@ import angular from 'angular'; import _ from 'lodash'; import moment from 'moment'; +import kbn from 'app/core/utils/kbn'; import PanelMeta from 'app/features/panel/panel_meta2'; import {transformDataToTable} from './transformers'; @@ -23,6 +24,8 @@ export class TablePanelCtrl { $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); + var panelDefaults = { targets: [{}], transform: 'timeseries_to_columns', @@ -124,6 +127,10 @@ export class TablePanelCtrl { panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw); }; + $scope.exportCsv = function() { + kbn.exportTableDataToCsv($scope.table); + }; + $scope.init(); } } From 4a8f82ca9b88ab4c2a72bf14e5d31a2b7555744b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 21 Jan 2016 10:10:12 +0100 Subject: [PATCH 017/107] fix(image rendering): fixed issue with image rendering, fixes #3804 --- public/less/sidemenu.less | 1 + public/views/index.html | 1 + tasks/options/concat.js | 1 + vendor/phantomjs/render.js | 104 ++++++++++++++++++++----------------- 4 files changed, 59 insertions(+), 48 deletions(-) diff --git a/public/less/sidemenu.less b/public/less/sidemenu.less index 3bb1ae393a7..61882f0e4c3 100644 --- a/public/less/sidemenu.less +++ b/public/less/sidemenu.less @@ -13,6 +13,7 @@ min-height: 100%; z-index: 101; transform: translate3d(-100%, 0, 0); + visibility: hidden; a:focus { text-decoration: none; diff --git a/public/views/index.html b/public/views/index.html index 2d05f414658..1d612e53385 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -56,6 +56,7 @@ + diff --git a/tasks/options/concat.js b/tasks/options/concat.js index f62e1f09982..7c2e9ed17c2 100644 --- a/tasks/options/concat.js +++ b/tasks/options/concat.js @@ -28,6 +28,7 @@ module.exports = function(config) { js: { src: [ + '<%= genDir %>/vendor/npm/es6-shim/es5-shim.js', '<%= genDir %>/vendor/npm/es6-shim/es6-shim.js', '<%= genDir %>/vendor/npm/es6-promise/es6-promise.js', '<%= genDir %>/vendor/npm/systemjs/dist/system.js', diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index 057d464a74c..95885a17886 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -1,55 +1,63 @@ -var page = require('webpage').create(); -var args = require('system').args; -var params = {}; -var regexp = /^([^=]+)=([^$]+)/; +(function() { + 'use strict'; -args.forEach(function(arg) { - var parts = arg.match(regexp); - if (!parts) { return; } - params[parts[1]] = parts[2]; -}); + var page = require('webpage').create(); + var args = require('system').args; + var params = {}; + var regexp = /^([^=]+)=([^$]+)/; -var usage = "url= png= width= height= cookiename= sessionid= domain="; + args.forEach(function(arg) { + var parts = arg.match(regexp); + if (!parts) { return; } + params[parts[1]] = parts[2]; + }); -if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) { - console.log(usage); - phantom.exit(); -} + var usage = "url= png= width= height= cookiename= sessionid= domain="; -phantom.addCookie({ - 'name': params.cookiename, - 'value': params.sessionid, - 'domain': params.domain -}); - -page.viewportSize = { - width: params.width || '800', - height: params.height || '400' -}; - -var tries = 0; - -page.open(params.url, function (status) { - console.log('Loading a web page: ' + params.url); - - function checkIsReady() { - var canvas = page.evaluate(function() { - var body = angular.element(document.body); // 1 - var rootScope = body.scope().$root; - var panelsToLoad = angular.element('div.panel').length; - return rootScope.performance.panelsRendered >= panelsToLoad; - }); - - if (canvas || tries === 1000) { - page.render(params.png); - phantom.exit(); - } - else { - tries++; - setTimeout(checkIsReady, 10); - } + if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) { + console.log(usage); + phantom.exit(); } - setTimeout(checkIsReady, 200); + phantom.addCookie({ + 'name': params.cookiename, + 'value': params.sessionid, + 'domain': params.domain + }); -}); + page.viewportSize = { + width: params.width || '800', + height: params.height || '400' + }; + + var tries = 0; + + page.open(params.url, function (status) { + console.log('Loading a web page: ' + params.url + ' status: ' + status); + + function checkIsReady() { + var canvas = page.evaluate(function() { + if (!window.angular) { return false; } + var body = window.angular.element(document.body); // 1 + if (!body.scope) { return false; } + + var rootScope = body.scope(); + if (!rootScope) {return false;} + if (!rootScope.performance) { return false; } + var panelsToLoad = window.angular.element('div.panel').length; + return rootScope.performance.panelsRendered >= panelsToLoad; + }); + + if (canvas || tries === 1000) { + page.render(params.png); + phantom.exit(); + } + else { + tries++; + setTimeout(checkIsReady, 10); + } + } + + setTimeout(checkIsReady, 200); + }); +})(); From c260c319eede99513e234f0e16391706e51f1552 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 21 Jan 2016 01:48:29 -0800 Subject: [PATCH 018/107] Started Grafana stats poc --- .../app/core/components/sidemenu/sidemenu.ts | 7 +++ public/app/core/routes/all.js | 4 ++ public/app/features/admin/adminStatsCtrl.js | 24 +++++++++ public/app/features/admin/all.js | 1 + public/app/features/admin/partials/stats.html | 53 +++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 public/app/features/admin/adminStatsCtrl.js create mode 100644 public/app/features/admin/partials/stats.html diff --git a/public/app/core/components/sidemenu/sidemenu.ts b/public/app/core/components/sidemenu/sidemenu.ts index d2a640c1345..8f0c57bfade 100644 --- a/public/app/core/components/sidemenu/sidemenu.ts +++ b/public/app/core/components/sidemenu/sidemenu.ts @@ -107,6 +107,12 @@ export class SideMenuCtrl { url: this.getUrl("/admin/settings"), }); + this.mainLinks.push({ + text: "Grafana stats", + icon: "fa fa-fw fa-bar-chart", + url: this.getUrl("/admin/stats"), + }); + this.mainLinks.push({ text: "Global Users", icon: "fa fa-fw fa-user", @@ -118,6 +124,7 @@ export class SideMenuCtrl { icon: "fa fa-fw fa-users", url: this.getUrl("/admin/orgs"), }); + } updateMenu() { diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index cc4d73ef708..bc71a63a094 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -112,6 +112,10 @@ define([ templateUrl: 'app/features/admin/partials/edit_org.html', controller : 'AdminEditOrgCtrl', }) + .when('/admin/stats', { + templateUrl: 'app/features/admin/partials/stats.html', + controller : 'AdminStatsCtrl', + }) .when('/login', { templateUrl: 'app/partials/login.html', controller : 'LoginCtrl', diff --git a/public/app/features/admin/adminStatsCtrl.js b/public/app/features/admin/adminStatsCtrl.js new file mode 100644 index 00000000000..94b40b59591 --- /dev/null +++ b/public/app/features/admin/adminStatsCtrl.js @@ -0,0 +1,24 @@ +define([ + 'angular', +], +function (angular) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('AdminStatsCtrl', function($scope) { + + $scope.init = function() { + $scope.getStats(); + }; + + $scope.getStats = function() { +// backendSrv.get('/api/admin/stats').then(function(stats) { +// $scope.stats = stats; +// }); + }; + + $scope.init(); + + }); +}); diff --git a/public/app/features/admin/all.js b/public/app/features/admin/all.js index 14bff249b0e..786210f064f 100644 --- a/public/app/features/admin/all.js +++ b/public/app/features/admin/all.js @@ -4,4 +4,5 @@ define([ './adminEditOrgCtrl', './adminEditUserCtrl', './adminSettingsCtrl', + './adminStatsCtrl', ], function () {}); diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html new file mode 100644 index 00000000000..048be83c83e --- /dev/null +++ b/public/app/features/admin/partials/stats.html @@ -0,0 +1,53 @@ + + + +
+
+

+ Stats +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue
Total dashboards213
Total users97
Total organizations4
Total playlists12
Total snapshots64
Total dashboard tags15
Total starred dashboards131
Total panels2739
+
+
From 6461981aa47365090de7daa16fb0f1311fab6247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Jan 2016 13:28:10 +0100 Subject: [PATCH 019/107] build(): minor change --- tasks/build_task.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/build_task.js b/tasks/build_task.js index d10a83d78dc..d844b661722 100644 --- a/tasks/build_task.js +++ b/tasks/build_task.js @@ -10,7 +10,7 @@ module.exports = function(grunt) { 'clean:release', 'copy:public_to_gen', 'typescript:build', - // 'karma:test', + 'karma:test', 'phantomjs', 'css', 'htmlmin:build', From cbb5811d8b6516aa888429205e980509f106e321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 21 Jan 2016 11:39:44 +0100 Subject: [PATCH 020/107] fix(cleanup): removed unused js file --- .../datasource/elasticsearch/directives.js | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 public/app/plugins/datasource/elasticsearch/directives.js diff --git a/public/app/plugins/datasource/elasticsearch/directives.js b/public/app/plugins/datasource/elasticsearch/directives.js deleted file mode 100644 index 3bb27885ef2..00000000000 --- a/public/app/plugins/datasource/elasticsearch/directives.js +++ /dev/null @@ -1,56 +0,0 @@ -define([ - 'angular', - './bucket_agg', - './metric_agg', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorElasticsearch', function() { - return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'}; - }); - - module.directive('metricQueryOptionsElasticsearch', function() { - return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.options.html'}; - }); - - module.directive('annotationsQueryEditorElasticsearch', function() { - return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'}; - }); - - module.directive('elastic', function() { - return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/config.html'}; - }); - - module.directive('elasticMetricAgg', function() { - return { - templateUrl: 'app/plugins/datasource/elasticsearch/partials/metric_agg.html', - controller: 'ElasticMetricAggCtrl', - restrict: 'E', - scope: { - target: "=", - index: "=", - onChange: "&", - getFields: "&", - esVersion: '=' - } - }; - }); - - module.directive('elasticBucketAgg', function() { - return { - templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucket_agg.html', - controller: 'ElasticBucketAggCtrl', - restrict: 'E', - scope: { - target: "=", - index: "=", - onChange: "&", - getFields: "&", - } - }; - }); - -}); From bc05cc49772c0962476145243aae94221ca52224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 21 Jan 2016 12:56:47 +0100 Subject: [PATCH 021/107] fix(phantomjs): another fix for phantomjs rendering, #3804 --- public/views/index.html | 1 + tasks/options/concat.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/views/index.html b/public/views/index.html index 1d612e53385..728cd084c88 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -59,6 +59,7 @@ + diff --git a/tasks/options/concat.js b/tasks/options/concat.js index 7c2e9ed17c2..0c76ce98d23 100644 --- a/tasks/options/concat.js +++ b/tasks/options/concat.js @@ -28,9 +28,10 @@ module.exports = function(config) { js: { src: [ - '<%= genDir %>/vendor/npm/es6-shim/es5-shim.js', + '<%= genDir %>/vendor/npm/es5-shim/es5-shim.js', '<%= genDir %>/vendor/npm/es6-shim/es6-shim.js', - '<%= genDir %>/vendor/npm/es6-promise/es6-promise.js', + '<%= genDir %>/vendor/npm/es6-promise/dist/es6-promise.js', + '<%= genDir %>/vendor/npm/systemjs/dist/system-polyfills.js', '<%= genDir %>/vendor/npm/systemjs/dist/system.js', '<%= genDir %>/app/system.conf.js', '<%= genDir %>/app/boot.js', From 3d353c7d6d266dee47b858499a5604b0c4c91efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 21 Jan 2016 13:14:25 +0100 Subject: [PATCH 022/107] fix(mixed datasource): fixed issue with mixed data source in optimized build --- public/app/plugins/datasource/mixed/datasource.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/plugins/datasource/mixed/datasource.ts b/public/app/plugins/datasource/mixed/datasource.ts index bd0628fe210..b5cea9cc5a8 100644 --- a/public/app/plugins/datasource/mixed/datasource.ts +++ b/public/app/plugins/datasource/mixed/datasource.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; class MixedDatasource { + /** @ngInject */ constructor(private $q, private datasourceSrv) { } From ba65b89bbb62ff85a6e6327936ca07f55efeb52a Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Jan 2016 14:21:38 +0100 Subject: [PATCH 023/107] feat(csv export): extract csv export into a new file --- public/app/core/utils/file_export.ts | 74 ++++++++++++++++++++ public/app/core/utils/kbn.js | 32 --------- public/app/plugins/panel/graph/module.js | 5 +- public/app/plugins/panel/table/controller.ts | 4 +- 4 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 public/app/core/utils/file_export.ts diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts new file mode 100644 index 00000000000..c3959aec12f --- /dev/null +++ b/public/app/core/utils/file_export.ts @@ -0,0 +1,74 @@ +/// + +import _ from 'lodash'; + +declare var window: any; + +export function exportSeriesListToCsv(seriesList) { + var text = 'Series;Time;Value\n'; + _.each(seriesList, function(series) { + _.each(series.datapoints, function(dp) { + text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; + }); + }); + saveSaveBlob(text, 'grafana_data_export.csv'); +}; + +export function exportTableDataToCsv(table) { + var text = ''; + // add header + _.each(table.columns, function(column) { + text += column.text + ';'; + }); + text += '\n'; + // process data + _.each(table.rows, function(row) { + _.each(row, function(value) { + text += value + ';'; + }); + text += '\n'; + }); + saveSaveBlob(text, 'grafana_data_export.csv'); +}; + +export function saveSaveBlob(payload, fname) { + var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); + window.saveAs(blob, fname); +}; + +/* +export default function flatten(target, opts): any { + opts = opts || {}; + + var delimiter = opts.delimiter || '.'; + var maxDepth = opts.maxDepth || 3; + var currentDepth = 1; + var output = {}; + + function step(object, prev) { + Object.keys(object).forEach(function(key) { + var value = object[key]; + var isarray = opts.safe && Array.isArray(value); + var type = Object.prototype.toString.call(value); + var isobject = type === "[object Object]"; + + var newKey = prev ? prev + delimiter + key : key; + + if (!opts.maxDepth) { + maxDepth = currentDepth + 1; + } + + if (!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) { + ++currentDepth; + return step(value, newKey); + } + + output[newKey] = value; + }); + } + + step(target, null); + + return output; +} +*/ \ No newline at end of file diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index e3a9fbaa39e..959de5fa42a 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -179,38 +179,6 @@ function($, _) { .replace(/ +/g,'-'); }; - kbn.exportSeriesListToCsv = function(seriesList) { - var text = 'Series;Time;Value\n'; - _.each(seriesList, function(series) { - _.each(series.datapoints, function(dp) { - text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; - }); - }); - kbn.saveSaveBlob(text, 'grafana_data_export.csv'); - }; - - kbn.exportTableDataToCsv = function(table) { - var text = ''; - // add header - _.each(table.columns, function(column) { - text += column.text + ';'; - }); - text += '\n'; - // process data - _.each(table.rows, function(row) { - _.each(row, function(value) { - text += value + ';'; - }); - text += '\n'; - }); - kbn.saveSaveBlob(text, 'grafana_data_export.csv'); - }; - - kbn.saveSaveBlob = function(payload, fname) { - var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); - window.saveAs(blob, fname); - }; - kbn.stringToJsRegex = function(str) { if (str[0] !== '/') { return new RegExp('^' + str + '$'); diff --git a/public/app/plugins/panel/graph/module.js b/public/app/plugins/panel/graph/module.js index 47d0e4f83a3..c1039e394a3 100644 --- a/public/app/plugins/panel/graph/module.js +++ b/public/app/plugins/panel/graph/module.js @@ -3,13 +3,14 @@ define([ 'lodash', 'moment', 'app/core/utils/kbn', + 'app/core/utils/file_export', 'app/core/time_series', 'app/features/panel/panel_meta', './seriesOverridesCtrl', './graph', './legend', ], -function (angular, _, moment, kbn, TimeSeries, PanelMeta) { +function (angular, _, moment, kbn, fileExport, TimeSeries, PanelMeta) { 'use strict'; /** @ngInject */ @@ -282,7 +283,7 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) { }; $scope.exportCsv = function() { - kbn.exportSeriesListToCsv($scope.seriesList); + fileExport.exportSeriesListToCsv($scope.seriesList); }; panelSrv.init($scope); diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts index 971f37de849..97c8f7a728a 100644 --- a/public/app/plugins/panel/table/controller.ts +++ b/public/app/plugins/panel/table/controller.ts @@ -3,7 +3,7 @@ import angular from 'angular'; import _ from 'lodash'; import moment from 'moment'; -import kbn from 'app/core/utils/kbn'; +import * as FileExport from 'app/core/utils/file_export'; import PanelMeta from 'app/features/panel/panel_meta2'; import {transformDataToTable} from './transformers'; @@ -128,7 +128,7 @@ export class TablePanelCtrl { }; $scope.exportCsv = function() { - kbn.exportTableDataToCsv($scope.table); + FileExport.exportTableDataToCsv($scope.table); }; $scope.init(); From d3f90dcfcc0ce354858fd2c754cf7271b1ec461e Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Jan 2016 14:26:31 +0100 Subject: [PATCH 024/107] style(fileexport): remove commented code --- public/app/core/utils/file_export.ts | 39 +--------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index c3959aec12f..ec8a604c1c2 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -34,41 +34,4 @@ export function exportTableDataToCsv(table) { export function saveSaveBlob(payload, fname) { var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); window.saveAs(blob, fname); -}; - -/* -export default function flatten(target, opts): any { - opts = opts || {}; - - var delimiter = opts.delimiter || '.'; - var maxDepth = opts.maxDepth || 3; - var currentDepth = 1; - var output = {}; - - function step(object, prev) { - Object.keys(object).forEach(function(key) { - var value = object[key]; - var isarray = opts.safe && Array.isArray(value); - var type = Object.prototype.toString.call(value); - var isobject = type === "[object Object]"; - - var newKey = prev ? prev + delimiter + key : key; - - if (!opts.maxDepth) { - maxDepth = currentDepth + 1; - } - - if (!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) { - ++currentDepth; - return step(value, newKey); - } - - output[newKey] = value; - }); - } - - step(target, null); - - return output; -} -*/ \ No newline at end of file +}; \ No newline at end of file From 08f02397b6bcdc584c47a3c86710d82b87d6efcb Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Jan 2016 14:31:20 +0100 Subject: [PATCH 025/107] fix(fileexport): tslint fix --- public/app/core/utils/file_export.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/utils/file_export.ts b/public/app/core/utils/file_export.ts index ec8a604c1c2..ad203f58495 100644 --- a/public/app/core/utils/file_export.ts +++ b/public/app/core/utils/file_export.ts @@ -34,4 +34,4 @@ export function exportTableDataToCsv(table) { export function saveSaveBlob(payload, fname) { var blob = new Blob([payload], { type: "text/csv;charset=utf-8" }); window.saveAs(blob, fname); -}; \ No newline at end of file +}; From dfd4fbc566446df11c4ae85c3dd5913193690303 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Jan 2016 14:39:32 +0100 Subject: [PATCH 026/107] feat(hooks): improve symlinks script --- symlink_git_hooks.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/symlink_git_hooks.sh b/symlink_git_hooks.sh index c8a90432a70..9e272f82ed6 100755 --- a/symlink_git_hooks.sh +++ b/symlink_git_hooks.sh @@ -1,3 +1,5 @@ #/bin/bash -ln -s -f .hooks/* .git/hooks/ +#ln -s -f .hooks/* .git/hooks/ +cd .git/hooks/ +cp --symbolic-link -f ../../.hooks/* . From e5e8e2021ff8253a300b146f11a07c3b3f73b701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volker=20Fr=C3=B6hlich?= Date: Thu, 21 Jan 2016 14:58:15 +0100 Subject: [PATCH 027/107] Fix dashboard sorting in playlists --- public/app/features/playlist/playlist_edit_ctrl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/playlist/playlist_edit_ctrl.js b/public/app/features/playlist/playlist_edit_ctrl.js index 3817ec12634..9b493c47d25 100644 --- a/public/app/features/playlist/playlist_edit_ctrl.js +++ b/public/app/features/playlist/playlist_edit_ctrl.js @@ -132,11 +132,11 @@ function (angular, config, _) { }; $scope.movePlaylistItemUp = function(playlistItem) { - $scope.moveDashboard(playlistItem, -1); + $scope.movePlaylistItem(playlistItem, -1); }; $scope.movePlaylistItemDown = function(playlistItem) { - $scope.moveDashboard(playlistItem, 1); + $scope.movePlaylistItem(playlistItem, 1); }; $scope.init(); From 28fabadeaebf8f3b152a654f4c44381faf2f828a Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 21 Jan 2016 15:04:23 +0100 Subject: [PATCH 028/107] style: remove empty row --- public/app/plugins/panel/table/controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/plugins/panel/table/controller.ts b/public/app/plugins/panel/table/controller.ts index 97c8f7a728a..376f36fd84d 100644 --- a/public/app/plugins/panel/table/controller.ts +++ b/public/app/plugins/panel/table/controller.ts @@ -23,7 +23,6 @@ export class TablePanelCtrl { $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); - $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); var panelDefaults = { From bbfdbaf95212edecb18738ba660279b121d52ff9 Mon Sep 17 00:00:00 2001 From: Ivan Babrou Date: Thu, 21 Jan 2016 16:38:39 +0000 Subject: [PATCH 029/107] Support OpenTSDB 2.2 fill policies, closes #3802 --- .../plugins/datasource/opentsdb/datasource.js | 4 ++++ .../opentsdb/partials/query.editor.html | 16 ++++++++++++++-- .../app/plugins/datasource/opentsdb/queryCtrl.js | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index b9593c013a1..d7cc354861f 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -272,6 +272,10 @@ function (angular, _, dateMath) { } query.downsample = interval + "-" + target.downsampleAggregator; + + if (target.downsampleFillPolicy !== "none") { + query.downsample += "-" + target.downsampleFillPolicy; + } } query.tags = angular.copy(target.tags); diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index 8a3c0c0f753..fffa7194228 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -104,8 +104,20 @@ -
  • - Disable downsampling +
  • + Fill policy + Available since OpenTSDB 2.2 +
  • + +
  • + +
  • + +
  • + Disable downsampling
  • diff --git a/public/app/plugins/datasource/opentsdb/queryCtrl.js b/public/app/plugins/datasource/opentsdb/queryCtrl.js index 8af3af206b6..14d28c310c2 100644 --- a/public/app/plugins/datasource/opentsdb/queryCtrl.js +++ b/public/app/plugins/datasource/opentsdb/queryCtrl.js @@ -13,6 +13,7 @@ function (angular, _, kbn) { $scope.init = function() { $scope.target.errors = validateTarget($scope.target); $scope.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax']; + $scope.fillPolicies = ['none', 'nan', 'null', 'zero']; if (!$scope.target.aggregator) { $scope.target.aggregator = 'sum'; @@ -22,6 +23,10 @@ function (angular, _, kbn) { $scope.target.downsampleAggregator = 'avg'; } + if (!$scope.target.downsampleFillPolicy) { + $scope.target.downsampleFillPolicy = 'none'; + } + $scope.datasource.getAggregators().then(function(aggs) { $scope.aggregators = aggs; }); From f94599cd29f69f9b5ff56ba398bac46dfa4cc6d5 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Fri, 22 Jan 2016 01:15:04 +0800 Subject: [PATCH 030/107] merge apiPlugins with appPlugins --- pkg/api/api.go | 2 +- pkg/api/{api_plugin.go => app_routes.go} | 32 ++++++++------------ pkg/plugins/api_plugin.go | 38 ------------------------ pkg/plugins/app_plugin.go | 34 +++++++++++---------- pkg/plugins/models.go | 2 -- pkg/plugins/plugins.go | 3 -- pkg/plugins/queries.go | 6 ---- 7 files changed, 33 insertions(+), 84 deletions(-) rename pkg/api/{api_plugin.go => app_routes.go} (75%) delete mode 100644 pkg/plugins/api_plugin.go diff --git a/pkg/api/api.go b/pkg/api/api.go index ea434ed71da..8bbb64eb1b2 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -215,7 +215,7 @@ func Register(r *macaron.Macaron) { // rendering r.Get("/render/*", reqSignedIn, RenderToPng) - InitApiPluginRoutes(r) + InitAppPluginRoutes(r) r.NotFound(NotFoundHandler) } diff --git a/pkg/api/api_plugin.go b/pkg/api/app_routes.go similarity index 75% rename from pkg/api/api_plugin.go rename to pkg/api/app_routes.go index 751c234bfe8..6ad41f79b53 100644 --- a/pkg/api/api_plugin.go +++ b/pkg/api/app_routes.go @@ -19,10 +19,10 @@ import ( "github.com/grafana/grafana/pkg/util" ) -func InitApiPluginRoutes(r *macaron.Macaron) { - for _, plugin := range plugins.ApiPlugins { - log.Info("Plugin: Adding proxy routes for api plugin") +func InitAppPluginRoutes(r *macaron.Macaron) { + for _, plugin := range plugins.Apps { for _, route := range plugin.Routes { + log.Info("Plugin: Adding proxy route for app plugin") url := util.JoinUrlFragments("/api/plugin-proxy/", route.Path) handlers := make([]macaron.Handler, 0) if route.ReqSignedIn { @@ -38,24 +38,24 @@ func InitApiPluginRoutes(r *macaron.Macaron) { handlers = append(handlers, middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)) } } - handlers = append(handlers, ApiPlugin(route, plugin.IncludedInAppId)) + handlers = append(handlers, AppPluginRoute(route, plugin.Id)) r.Route(url, route.Method, handlers...) log.Info("Plugin: Adding route %s", url) } } } -func ApiPlugin(route *plugins.ApiPluginRoute, includedInAppId string) macaron.Handler { +func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler { return func(c *middleware.Context) { path := c.Params("*") - proxy := NewApiPluginProxy(c, path, route, includedInAppId) + proxy := NewApiPluginProxy(c, path, route, appId) proxy.Transport = dataProxyTransport proxy.ServeHTTP(c.Resp, c.Req.Request) } } -func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins.ApiPluginRoute, includedInAppId string) *httputil.ReverseProxy { +func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins.AppPluginRoute, appId string) *httputil.ReverseProxy { targetUrl, _ := url.Parse(route.Url) director := func(req *http.Request) { @@ -87,21 +87,15 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins return } - jsonData := make(map[string]interface{}) + //lookup appSettings + query := m.GetAppSettingByAppIdQuery{OrgId: ctx.OrgId, AppId: appId} - if includedInAppId != "" { - //lookup appSettings - query := m.GetAppSettingByAppIdQuery{OrgId: ctx.OrgId, AppId: includedInAppId} - - if err := bus.Dispatch(&query); err != nil { - ctx.JsonApiErr(500, "failed to get AppSettings of includedAppId.", err) - return - } - - jsonData = query.Result.JsonData + if err := bus.Dispatch(&query); err != nil { + ctx.JsonApiErr(500, "failed to get AppSettings.", err) + return } - err = t.Execute(&contentBuf, jsonData) + err = t.Execute(&contentBuf, query.Result.JsonData) if err != nil { ctx.JsonApiErr(500, fmt.Sprintf("failed to execute header content template for header %s.", header.Name), err) return diff --git a/pkg/plugins/api_plugin.go b/pkg/plugins/api_plugin.go deleted file mode 100644 index efb16e696d8..00000000000 --- a/pkg/plugins/api_plugin.go +++ /dev/null @@ -1,38 +0,0 @@ -package plugins - -import ( - "encoding/json" - - "github.com/grafana/grafana/pkg/models" -) - -type ApiPluginRoute struct { - Path string `json:"path"` - Method string `json:"method"` - ReqSignedIn bool `json:"reqSignedIn"` - ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` - ReqRole models.RoleType `json:"reqRole"` - Url string `json:"url"` - Headers []ApiPluginHeader `json:"headers"` -} - -type ApiPlugin struct { - PluginBase - Routes []*ApiPluginRoute `json:"routes"` -} - -type ApiPluginHeader struct { - Name string `json:"name"` - Content string `json:"content"` -} - -func (app *ApiPlugin) Load(decoder *json.Decoder, pluginDir string) error { - if err := decoder.Decode(&app); err != nil { - return err - } - - app.PluginDir = pluginDir - - ApiPlugins[app.Id] = app - return nil -} diff --git a/pkg/plugins/app_plugin.go b/pkg/plugins/app_plugin.go index d2291d487ac..547cd91283c 100644 --- a/pkg/plugins/app_plugin.go +++ b/pkg/plugins/app_plugin.go @@ -26,14 +26,30 @@ type AppIncludeInfo struct { type AppPlugin struct { FrontendPluginBase - Css *AppPluginCss `json:"css"` - Pages []AppPluginPage `json:"pages"` - Includes []AppIncludeInfo `json:"-"` + Css *AppPluginCss `json:"css"` + Pages []AppPluginPage `json:"pages"` + Routes []*AppPluginRoute `json:"routes"` + Includes []AppIncludeInfo `json:"-"` Pinned bool `json:"-"` Enabled bool `json:"-"` } +type AppPluginRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"reqSignedIn"` + ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"` + ReqRole models.RoleType `json:"reqRole"` + Url string `json:"url"` + Headers []AppPluginRouteHeader `json:"headers"` +} + +type AppPluginRouteHeader struct { + Name string `json:"name"` + Content string `json:"content"` +} + func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error { if err := decoder.Decode(&app); err != nil { return err @@ -59,18 +75,6 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error { } } - // check if we have child apiPlugins - for _, plugin := range ApiPlugins { - if strings.HasPrefix(plugin.PluginDir, app.PluginDir) { - plugin.IncludedInAppId = app.Id - app.Includes = append(app.Includes, AppIncludeInfo{ - Name: plugin.Name, - Id: plugin.Id, - Type: plugin.Type, - }) - } - } - Apps[app.Id] = app return nil } diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index b302181e840..55ba8f97767 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -45,7 +45,6 @@ type PluginStaticRoute struct { type EnabledPlugins struct { Panels []*PanelPlugin DataSources map[string]*DataSourcePlugin - ApiList []*ApiPlugin Apps []*AppPlugin } @@ -53,7 +52,6 @@ func NewEnabledPlugins() EnabledPlugins { return EnabledPlugins{ Panels: make([]*PanelPlugin, 0), DataSources: make(map[string]*DataSourcePlugin), - ApiList: make([]*ApiPlugin, 0), Apps: make([]*AppPlugin, 0), } } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index d6d390d93c0..5878a25a2da 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -17,7 +17,6 @@ import ( var ( DataSources map[string]*DataSourcePlugin Panels map[string]*PanelPlugin - ApiPlugins map[string]*ApiPlugin StaticRoutes []*PluginStaticRoute Apps map[string]*AppPlugin PluginTypes map[string]interface{} @@ -30,14 +29,12 @@ type PluginScanner struct { func Init() error { DataSources = make(map[string]*DataSourcePlugin) - ApiPlugins = make(map[string]*ApiPlugin) StaticRoutes = make([]*PluginStaticRoute, 0) Panels = make(map[string]*PanelPlugin) Apps = make(map[string]*AppPlugin) PluginTypes = map[string]interface{}{ "panel": PanelPlugin{}, "datasource": DataSourcePlugin{}, - "api": ApiPlugin{}, "app": AppPlugin{}, } diff --git a/pkg/plugins/queries.go b/pkg/plugins/queries.go index 58e7d865c63..8e628c5024e 100644 --- a/pkg/plugins/queries.go +++ b/pkg/plugins/queries.go @@ -68,11 +68,5 @@ func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { } } - for _, api := range ApiPlugins { - if isPluginEnabled(api.IncludedInAppId) { - enabledPlugins.ApiList = append(enabledPlugins.ApiList, api) - } - } - return &enabledPlugins, nil } From ab3b586838578d84a0c3495c84d9dd8efe28f776 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Sat, 23 Jan 2016 03:15:39 +0800 Subject: [PATCH 031/107] add encryption util functions --- pkg/util/encryption.go | 70 +++++++++++++++++++++++++++++++++++++ pkg/util/encryption_test.go | 28 +++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 pkg/util/encryption.go create mode 100644 pkg/util/encryption_test.go diff --git a/pkg/util/encryption.go b/pkg/util/encryption.go new file mode 100644 index 00000000000..f24a9b29397 --- /dev/null +++ b/pkg/util/encryption.go @@ -0,0 +1,70 @@ +package util + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" + + "github.com/grafana/grafana/pkg/log" +) + +func Decrypt(payload []byte, secret string) []byte { + key := encryptionKeyToBytes(secret) + + block, err := aes.NewCipher(key) + if err != nil { + log.Fatal(4, err.Error()) + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + if len(payload) < aes.BlockSize { + log.Fatal(4, "payload too short") + } + iv := payload[:aes.BlockSize] + payload = payload[aes.BlockSize:] + + stream := cipher.NewCFBDecrypter(block, iv) + + // XORKeyStream can work in-place if the two arguments are the same. + stream.XORKeyStream(payload, payload) + return payload +} + +func Encrypt(payload []byte, secret string) []byte { + key := encryptionKeyToBytes(secret) + + block, err := aes.NewCipher(key) + if err != nil { + log.Fatal(4, err.Error()) + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + ciphertext := make([]byte, aes.BlockSize+len(payload)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + log.Fatal(4, err.Error()) + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], payload) + + return ciphertext +} + +// Key needs to be 32bytes +func encryptionKeyToBytes(secret string) []byte { + key := make([]byte, 32, 32) + keyBytes := []byte(secret) + secretLength := len(keyBytes) + for i := 0; i < 32; i++ { + if secretLength > i { + key[i] = keyBytes[i] + } else { + key[i] = 0 + } + } + return key +} diff --git a/pkg/util/encryption_test.go b/pkg/util/encryption_test.go new file mode 100644 index 00000000000..254f0f178c0 --- /dev/null +++ b/pkg/util/encryption_test.go @@ -0,0 +1,28 @@ +package util + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestEncryption(t *testing.T) { + + Convey("When getting encryption key", t, func() { + + key := encryptionKeyToBytes("secret") + So(len(key), ShouldEqual, 32) + + key = encryptionKeyToBytes("a very long secret key that is larger then 32bytes") + So(len(key), ShouldEqual, 32) + + }) + + Convey("When decrypting basic payload", t, func() { + encrypted := Encrypt([]byte("grafana"), "1234") + decrypted := Decrypt(encrypted, "1234") + + So(string(decrypted), ShouldEqual, "grafana") + }) + +} From 32f78d465bb1bd8d393db4bf2624cf8845345a7d Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Sat, 23 Jan 2016 06:17:22 +0800 Subject: [PATCH 032/107] add secureJsonData to appSettings model. - adds the new column to the DB table. - data stored in the DB is encrypted - update appRouteHeaders templates to use the jsonData and decrypted secureJsonData --- pkg/api/app_routes.go | 11 +++++-- pkg/models/app_settings.go | 33 ++++++++++++++----- pkg/services/sqlstore/app_settings.go | 25 ++++++++++---- .../sqlstore/migrations/app_settings.go | 1 + public/app/features/apps/edit_ctrl.ts | 1 + 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index 6ad41f79b53..169c5c6d15c 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -94,8 +94,15 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins ctx.JsonApiErr(500, "failed to get AppSettings.", err) return } - - err = t.Execute(&contentBuf, query.Result.JsonData) + type templateData struct { + JsonData map[string]interface{} + SecureJsonData map[string]string + } + data := templateData{ + JsonData: query.Result.JsonData, + SecureJsonData: query.Result.SecureJsonData.Decrypt(), + } + err = t.Execute(&contentBuf, data) if err != nil { ctx.JsonApiErr(500, fmt.Sprintf("failed to execute header content template for header %s.", header.Name), err) return diff --git a/pkg/models/app_settings.go b/pkg/models/app_settings.go index f3b60502cb0..78d4c483f2b 100644 --- a/pkg/models/app_settings.go +++ b/pkg/models/app_settings.go @@ -3,6 +3,9 @@ package models import ( "errors" "time" + + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) var ( @@ -10,25 +13,37 @@ var ( ) type AppSettings struct { - Id int64 - AppId string - OrgId int64 - Enabled bool - Pinned bool - JsonData map[string]interface{} + Id int64 + AppId string + OrgId int64 + Enabled bool + Pinned bool + JsonData map[string]interface{} + SecureJsonData SecureJsonData Created time.Time Updated time.Time } +type SecureJsonData map[string][]byte + +func (s SecureJsonData) Decrypt() map[string]string { + decrypted := make(map[string]string) + for key, data := range s { + decrypted[key] = string(util.Decrypt(data, setting.SecretKey)) + } + return decrypted +} + // ---------------------- // COMMANDS // Also acts as api DTO type UpdateAppSettingsCmd struct { - Enabled bool `json:"enabled"` - Pinned bool `json:"pinned"` - JsonData map[string]interface{} `json:"jsonData"` + Enabled bool `json:"enabled"` + Pinned bool `json:"pinned"` + JsonData map[string]interface{} `json:"jsonData"` + SecureJsonData map[string]string `json:"secureJsonData"` AppId string `json:"-"` OrgId int64 `json:"-"` diff --git a/pkg/services/sqlstore/app_settings.go b/pkg/services/sqlstore/app_settings.go index 7d9482e7b22..f454d2cc5ff 100644 --- a/pkg/services/sqlstore/app_settings.go +++ b/pkg/services/sqlstore/app_settings.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) func init() { @@ -40,18 +42,27 @@ func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { sess.UseBool("enabled") sess.UseBool("pinned") if !exists { + // encrypt secureJsonData + secureJsonData := make(map[string][]byte) + for key, data := range cmd.SecureJsonData { + secureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) + } app = m.AppSettings{ - AppId: cmd.AppId, - OrgId: cmd.OrgId, - Enabled: cmd.Enabled, - Pinned: cmd.Pinned, - JsonData: cmd.JsonData, - Created: time.Now(), - Updated: time.Now(), + AppId: cmd.AppId, + OrgId: cmd.OrgId, + Enabled: cmd.Enabled, + Pinned: cmd.Pinned, + JsonData: cmd.JsonData, + SecureJsonData: secureJsonData, + Created: time.Now(), + Updated: time.Now(), } _, err = sess.Insert(&app) return err } else { + for key, data := range cmd.SecureJsonData { + app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) + } app.Updated = time.Now() app.Enabled = cmd.Enabled app.JsonData = cmd.JsonData diff --git a/pkg/services/sqlstore/migrations/app_settings.go b/pkg/services/sqlstore/migrations/app_settings.go index 437debbe95b..8b970a5062a 100644 --- a/pkg/services/sqlstore/migrations/app_settings.go +++ b/pkg/services/sqlstore/migrations/app_settings.go @@ -13,6 +13,7 @@ func addAppSettingsMigration(mg *Migrator) { {Name: "enabled", Type: DB_Bool, Nullable: false}, {Name: "pinned", Type: DB_Bool, Nullable: false}, {Name: "json_data", Type: DB_Text, Nullable: true}, + {Name: "secure_json_data", Type: DB_Text, Nullable: true}, {Name: "created", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false}, }, diff --git a/public/app/features/apps/edit_ctrl.ts b/public/app/features/apps/edit_ctrl.ts index dfbce64df5e..ccdaf529b5e 100644 --- a/public/app/features/apps/edit_ctrl.ts +++ b/public/app/features/apps/edit_ctrl.ts @@ -24,6 +24,7 @@ export class AppEditCtrl { enabled: this.appModel.enabled, pinned: this.appModel.pinned, jsonData: this.appModel.jsonData, + secureJsonData: this.appModel.secureJsonData, }, options); this.backendSrv.post(`/api/org/apps/${this.$routeParams.appId}/settings`, updateCmd).then(function() { From a85bda13db2df04175889662883f3c2ded474661 Mon Sep 17 00:00:00 2001 From: bergquist Date: Sun, 24 Jan 2016 18:07:42 +0100 Subject: [PATCH 033/107] tech(docker-dev): update dev docker for graphite --- docker/blocks/graphite/Dockerfile | 72 +++++++------------ docker/blocks/graphite/fig | 8 ++- docker/blocks/graphite/files/my_htpasswd | 1 + docker/blocks/graphite/files/supervisord.conf | 7 -- 4 files changed, 35 insertions(+), 53 deletions(-) create mode 100644 docker/blocks/graphite/files/my_htpasswd diff --git a/docker/blocks/graphite/Dockerfile b/docker/blocks/graphite/Dockerfile index 0c85798dd8a..aeac91b7a58 100644 --- a/docker/blocks/graphite/Dockerfile +++ b/docker/blocks/graphite/Dockerfile @@ -1,68 +1,50 @@ -from ubuntu:14.10 +from ubuntu:14.04 -run apt-get -y update +run apt-get -y update -run apt-get -y install software-properties-common - -run apt-get -y install python-software-properties &&\ - add-apt-repository ppa:chris-lea/node.js &&\ - apt-get -y update - -run apt-get -y install python-django-tagging python-simplejson python-memcache \ - python-ldap python-cairo python-django python-twisted \ - python-pysqlite2 python-support python-pip gunicorn \ - supervisor nginx-light nodejs git wget curl - -# Install statsd -run mkdir /src && git clone https://github.com/etsy/statsd.git /src/statsd +run apt-get -y install libcairo2-dev libffi-dev pkg-config python-dev python-pip fontconfig apache2 libapache2-mod-wsgi git-core collectd memcached gcc g++ make supervisor nginx-light gunicorn run cd /usr/local/src && git clone https://github.com/graphite-project/graphite-web.git run cd /usr/local/src && git clone https://github.com/graphite-project/carbon.git run cd /usr/local/src && git clone https://github.com/graphite-project/whisper.git run cd /usr/local/src/whisper && git checkout master && python setup.py install -run cd /usr/local/src/carbon && git checkout 0.9.x && python setup.py install -run cd /usr/local/src/graphite-web && git checkout 0.9.x && python check-dependencies.py; python setup.py install - -# statsd -add ./files/statsd_config.js /src/statsd/config.js +run cd /usr/local/src/carbon && git checkout 0.9.x && pip install -r requirements.txt; python setup.py install +run cd /usr/local/src/graphite-web && git checkout 0.9.x && pip install -r requirements.txt; python check-dependencies.py; python setup.py install # Add graphite config -add ./files/initial_data.json /opt/graphite/webapp/graphite/initial_data.json -add ./files/local_settings.py /opt/graphite/webapp/graphite/local_settings.py -add ./files/carbon.conf /opt/graphite/conf/carbon.conf -add ./files/storage-schemas.conf /opt/graphite/conf/storage-schemas.conf -add ./files/storage-aggregation.conf /opt/graphite/conf/storage-aggregation.conf -add ./files/events_views.py /opt/graphite/webapp/graphite/events/views.py +add ./files/initial_data.json /opt/graphite/webapp/graphite/initial_data.json +add ./files/local_settings.py /opt/graphite/webapp/graphite/local_settings.py +add ./files/carbon.conf /opt/graphite/conf/carbon.conf +add ./files/storage-schemas.conf /opt/graphite/conf/storage-schemas.conf +add ./files/storage-aggregation.conf /opt/graphite/conf/storage-aggregation.conf +add ./files/events_views.py /opt/graphite/webapp/graphite/events/views.py -run mkdir -p /opt/graphite/storage/whisper -run touch /opt/graphite/storage/graphite.db /opt/graphite/storage/index -run chown -R www-data /opt/graphite/storage -run chmod 0775 /opt/graphite/storage /opt/graphite/storage/whisper -run chmod 0664 /opt/graphite/storage/graphite.db -run cd /opt/graphite/webapp/graphite && python manage.py syncdb --noinput +run mkdir -p /opt/graphite/storage/whisper +run touch /opt/graphite/storage/graphite.db /opt/graphite/storage/index +run chown -R www-data /opt/graphite/storage +run chmod 0775 /opt/graphite/storage /opt/graphite/storage/whisper +run chmod 0664 /opt/graphite/storage/graphite.db +run cd /opt/graphite/webapp/graphite && python manage.py syncdb --noinput + +add ./files/my_htpasswd /etc/nginx/.htpasswd # Add system service config -add ./files/nginx.conf /etc/nginx/nginx.conf -add ./files/supervisord.conf /etc/supervisor/conf.d/supervisord.conf - +add ./files/nginx.conf /etc/nginx/nginx.conf +add ./files/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +# Nginx +# # graphite -expose 80 +expose 80 # Carbon line receiver port -expose 2003 +expose 2003 # Carbon cache query port -expose 7002 +expose 7002 -# Statsd UDP port -expose 8125/udp -# Statsd Management port -expose 8126 - -VOLUME ["/var/lib/elasticsearch"] VOLUME ["/opt/graphite/storage/whisper"] VOLUME ["/var/lib/log/supervisor"] -cmd ["/usr/bin/supervisord"] +cmd ["/usr/bin/supervisord"] # vim:ts=8:noet: diff --git a/docker/blocks/graphite/fig b/docker/blocks/graphite/fig index 28e7d3c53a2..84da45341e1 100644 --- a/docker/blocks/graphite/fig +++ b/docker/blocks/graphite/fig @@ -1,4 +1,10 @@ graphite: build: blocks/graphite ports: - - "8776:80" + - "8080:80" + - "2003:2003" + volumes: + - /var/docker/gfdev/graphite:/opt/graphite/storage/whisper + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + diff --git a/docker/blocks/graphite/files/my_htpasswd b/docker/blocks/graphite/files/my_htpasswd new file mode 100644 index 00000000000..52a72d01b4c --- /dev/null +++ b/docker/blocks/graphite/files/my_htpasswd @@ -0,0 +1 @@ +grafana:$apr1$4R/20xhC$8t37jPP5dbcLr48btdkU// diff --git a/docker/blocks/graphite/files/supervisord.conf b/docker/blocks/graphite/files/supervisord.conf index 25ba39c8819..c9812bb16dc 100644 --- a/docker/blocks/graphite/files/supervisord.conf +++ b/docker/blocks/graphite/files/supervisord.conf @@ -24,10 +24,3 @@ stdout_logfile = /var/log/supervisor/%(program_name)s.log stderr_logfile = /var/log/supervisor/%(program_name)s.log autorestart = true -[program:statsd] -;user = www-data -command = /usr/bin/node /src/statsd/stats.js /src/statsd/config.js -stdout_logfile = /var/log/supervisor/%(program_name)s.log -stderr_logfile = /var/log/supervisor/%(program_name)s.log -autorestart = true - From 42802ac7102668a4b7b4c35eb2d3094d08171e80 Mon Sep 17 00:00:00 2001 From: bergquist Date: Sun, 24 Jan 2016 17:10:26 +0100 Subject: [PATCH 034/107] tech(singlestat): convert singlestat panel to typescript --- public/app/core/time_series2.ts | 1 + .../{controller.js => controller.ts} | 39 ++- public/app/plugins/panel/singlestat/module.js | 235 ------------------ public/app/plugins/panel/singlestat/module.ts | 231 +++++++++++++++++ .../panel/singlestat/singleStatPanel.js | 221 ---------------- .../singlestat/specs/singlestat_panel_spec.ts | 0 6 files changed, 250 insertions(+), 477 deletions(-) rename public/app/plugins/panel/singlestat/{controller.js => controller.ts} (92%) delete mode 100644 public/app/plugins/panel/singlestat/module.js create mode 100644 public/app/plugins/panel/singlestat/module.ts delete mode 100644 public/app/plugins/panel/singlestat/singleStatPanel.js create mode 100644 public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index c8384b5b40a..ef2e6b9c8b2 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -41,6 +41,7 @@ export default class TimeSeries { nullPointMode: any; fillBelowTo: any; transform: any; + flotpairs: any; constructor(opts) { this.datapoints = opts.datapoints; diff --git a/public/app/plugins/panel/singlestat/controller.js b/public/app/plugins/panel/singlestat/controller.ts similarity index 92% rename from public/app/plugins/panel/singlestat/controller.js rename to public/app/plugins/panel/singlestat/controller.ts index 62ae13d0f5f..4b55a4568be 100644 --- a/public/app/plugins/panel/singlestat/controller.js +++ b/public/app/plugins/panel/singlestat/controller.ts @@ -1,17 +1,15 @@ -define([ - 'angular', - 'app/app', - 'lodash', - 'app/core/utils/kbn', - 'app/core/time_series', - 'app/features/panel/panel_meta', -], -function (angular, app, _, kbn, TimeSeries, PanelMeta) { - 'use strict'; +/// + +import angular from 'angular'; +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import PanelMeta from 'app/features/panel/panel_meta2'; +import TimeSeries from '../../../core/time_series2'; + +export class SingleStatCtrl { /** @ngInject */ - function SingleStatCtrl($scope, panelSrv, panelHelper) { - + constructor($scope, panelSrv, panelHelper) { $scope.panelMeta = new PanelMeta({ panelName: 'Singlestat', editIcon: "fa fa-dashboard", @@ -57,6 +55,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { }; _.defaults($scope.panel, _d); + $scope.unitFormats = kbn.getUnitFormats(); $scope.setUnitFormat = function(subItem) { @@ -104,8 +103,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { if (options.background) { $scope.panel.colorValue = false; $scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; - } - else { + } else { $scope.panel.colorBackground = false; $scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; } @@ -151,7 +149,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { // reduce starting decimals if not needed if (Math.floor(value) === value) { dec = 0; } - var result = {}; + var result: any = {}; result.decimals = Math.max(0, dec); result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; @@ -159,7 +157,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { }; $scope.render = function() { - var data = {}; + var data: any = {}; $scope.setValues(data); @@ -176,7 +174,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { $scope.setValues = function(data) { data.flotpairs = []; - if($scope.series.length > 1) { + if ($scope.series.length > 1) { $scope.inspector.error = new Error(); $scope.inspector.error.message = 'Multiple Series Error'; $scope.inspector.error.data = 'Metric query returns ' + $scope.series.length + @@ -204,7 +202,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { } // check value to text mappings - for(var i = 0; i < $scope.panel.valueMaps.length; i++) { + for (var i = 0; i < $scope.panel.valueMaps.length; i++) { var map = $scope.panel.valueMaps[i]; // special null case if (map.value === 'null') { @@ -239,7 +237,6 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { }; $scope.init(); - } - return SingleStatCtrl; -}); + } +} diff --git a/public/app/plugins/panel/singlestat/module.js b/public/app/plugins/panel/singlestat/module.js deleted file mode 100644 index 8ce441818e6..00000000000 --- a/public/app/plugins/panel/singlestat/module.js +++ /dev/null @@ -1,235 +0,0 @@ -define([ - './controller', - 'lodash', - 'jquery', - 'jquery.flot', -], -function (SingleStatCtrl, _, $) { - 'use strict'; - - /** @ngInject */ - function singleStatPanel($location, linkSrv, $timeout, templateSrv) { - return { - controller: SingleStatCtrl, - templateUrl: 'app/plugins/panel/singlestat/module.html', - link: function(scope, elem) { - var data, panel, linkInfo, $panelContainer; - var firstRender = true; - - scope.$on('render', function() { - if (firstRender) { - var inner = elem.find('.singlestat-panel'); - if (inner.length) { - elem = inner; - $panelContainer = elem.parents('.panel-container'); - firstRender = false; - hookupDrilldownLinkTooltip(); - } - } - - render(); - scope.panelRenderingComplete(); - }); - - function setElementHeight() { - try { - var height = scope.height || panel.height || scope.row.height; - if (_.isString(height)) { - height = parseInt(height.replace('px', ''), 10); - } - - height -= 5; // padding - height -= panel.title ? 24 : 9; // subtract panel title bar - - elem.css('height', height + 'px'); - - return true; - } catch(e) { // IE throws errors sometimes - return false; - } - } - - function applyColoringThresholds(value, valueString) { - if (!panel.colorValue) { - return valueString; - } - - var color = getColorForValue(value); - if (color) { - return ''+ valueString + ''; - } - - return valueString; - } - - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value >= data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - - function getSpan(className, fontSize, value) { - value = templateSrv.replace(value); - return '' + - value + ''; - } - - function getBigValueHtml() { - var body = '
    '; - - if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } - - var value = applyColoringThresholds(data.valueRounded, data.valueFormated); - body += getSpan('singlestat-panel-value', panel.valueFontSize, value); - - if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } - - body += '
    '; - - return body; - } - - function addSparkline() { - var panel = scope.panel; - var width = elem.width() + 20; - var height = elem.height() || 100; - - var plotCanvas = $('
    '); - var plotCss = {}; - plotCss.position = 'absolute'; - - if (panel.sparkline.full) { - plotCss.bottom = '5px'; - plotCss.left = '-5px'; - plotCss.width = (width - 10) + 'px'; - var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; - plotCss.height = (height - dynamicHeightMargin) + 'px'; - } - else { - plotCss.bottom = "0px"; - plotCss.left = "-5px"; - plotCss.width = (width - 10) + 'px'; - plotCss.height = Math.floor(height * 0.25) + "px"; - } - - plotCanvas.css(plotCss); - - var options = { - legend: { show: false }, - series: { - lines: { - show: true, - fill: 1, - lineWidth: 1, - fillColor: panel.sparkline.fillColor, - }, - }, - yaxes: { show: false }, - xaxis: { - show: false, - mode: "time", - min: scope.range.from.valueOf(), - max: scope.range.to.valueOf(), - }, - grid: { hoverable: false, show: false }, - }; - - elem.append(plotCanvas); - - var plotSeries = { - data: data.flotpairs, - color: panel.sparkline.lineColor - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function render() { - if (!scope.data) { return; } - - data = scope.data; - panel = scope.panel; - - setElementHeight(); - - var body = getBigValueHtml(); - - if (panel.colorBackground && !isNaN(data.valueRounded)) { - var color = getColorForValue(data.valueRounded); - if (color) { - $panelContainer.css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } - } else { - $panelContainer.css('background-color', ''); - elem.css('background-color', ''); - } - - elem.html(body); - - if (panel.sparkline.show) { - addSparkline(); - } - - elem.toggleClass('pointer', panel.links.length > 0); - - if (panel.links.length > 0) { - linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); - } else { - linkInfo = null; - } - } - - function hookupDrilldownLinkTooltip() { - // drilldown link tooltip - var drilldownTooltip = $('
    hello
    "'); - - elem.mouseleave(function() { - if (panel.links.length === 0) { return;} - drilldownTooltip.detach(); - }); - - elem.click(function(evt) { - if (!linkInfo) { return; } - // ignore title clicks in title - if ($(evt).parents('.panel-header').length > 0) { return; } - - if (linkInfo.target === '_blank') { - var redirectWindow = window.open(linkInfo.href, '_blank'); - redirectWindow.location; - return; - } - - if (linkInfo.href.indexOf('http') === 0) { - window.location.href = linkInfo.href; - } else { - $timeout(function() { - $location.url(linkInfo.href); - }); - } - - drilldownTooltip.detach(); - }); - - elem.mousemove(function(e) { - if (!linkInfo) { return;} - - drilldownTooltip.text('click to go to: ' + linkInfo.title); - drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); - }); - } - } - }; - } - - return { - panel: singleStatPanel - }; -}); diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts new file mode 100644 index 00000000000..c6d10afb567 --- /dev/null +++ b/public/app/plugins/panel/singlestat/module.ts @@ -0,0 +1,231 @@ +/// + +import _ from 'lodash'; +import $ from 'jquery'; +import angular from 'angular'; +import {SingleStatCtrl} from './controller'; + +angular.module('grafana.directives').directive('singleStatPanel', singleStatPanel); + +function singleStatPanel($location, linkSrv, $timeout, templateSrv) { + 'use strict'; + return { + controller: SingleStatCtrl, + templateUrl: 'app/plugins/panel/singlestat/module.html', + link: function(scope, elem) { + var data, panel, linkInfo, $panelContainer; + var firstRender = true; + + scope.$on('render', function() { + if (firstRender) { + var inner = elem.find('.singlestat-panel'); + if (inner.length) { + elem = inner; + $panelContainer = elem.parents('.panel-container'); + firstRender = false; + hookupDrilldownLinkTooltip(); + } + } + + render(); + scope.panelRenderingComplete(); + }); + + function setElementHeight() { + try { + var height = scope.height || panel.height || scope.row.height; + if (_.isString(height)) { + height = parseInt(height.replace('px', ''), 10); + } + + height -= 5; // padding + height -= panel.title ? 24 : 9; // subtract panel title bar + + elem.css('height', height + 'px'); + + return true; + } catch (e) { // IE throws errors sometimes + return false; + } + } + + function applyColoringThresholds(value, valueString) { + if (!panel.colorValue) { + return valueString; + } + + var color = getColorForValue(value); + if (color) { + return ''+ valueString + ''; + } + + return valueString; + } + + function getColorForValue(value) { + for (var i = data.thresholds.length - 1; i >= 0 ; i--) { + if (value >= data.thresholds[i]) { + return data.colorMap[i]; + } + } + return null; + } + + function getSpan(className, fontSize, value) { + value = templateSrv.replace(value); + return '' + + value + ''; + } + + function getBigValueHtml() { + var body = '
    '; + + if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } + + var value = applyColoringThresholds(data.valueRounded, data.valueFormated); + body += getSpan('singlestat-panel-value', panel.valueFontSize, value); + + if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } + + body += '
    '; + + return body; + } + + function addSparkline() { + var panel = scope.panel; + var width = elem.width() + 20; + var height = elem.height() || 100; + + var plotCanvas = $('
    '); + var plotCss: any = {}; + plotCss.position = 'absolute'; + + if (panel.sparkline.full) { + plotCss.bottom = '5px'; + plotCss.left = '-5px'; + plotCss.width = (width - 10) + 'px'; + var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; + plotCss.height = (height - dynamicHeightMargin) + 'px'; + } else { + plotCss.bottom = "0px"; + plotCss.left = "-5px"; + plotCss.width = (width - 10) + 'px'; + plotCss.height = Math.floor(height * 0.25) + "px"; + } + + plotCanvas.css(plotCss); + + var options = { + legend: { show: false }, + series: { + lines: { + show: true, + fill: 1, + lineWidth: 1, + fillColor: panel.sparkline.fillColor, + }, + }, + yaxes: { show: false }, + xaxis: { + show: false, + mode: "time", + min: scope.range.from.valueOf(), + max: scope.range.to.valueOf(), + }, + grid: { hoverable: false, show: false }, + }; + + elem.append(plotCanvas); + + var plotSeries = { + data: data.flotpairs, + color: panel.sparkline.lineColor + }; + + $.plot(plotCanvas, [plotSeries], options); + } + + function render() { + if (!scope.data) { return; } + + data = scope.data; + panel = scope.panel; + + setElementHeight(); + + var body = getBigValueHtml(); + + if (panel.colorBackground && !isNaN(data.valueRounded)) { + var color = getColorForValue(data.valueRounded); + if (color) { + $panelContainer.css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + $panelContainer.css('background-color', ''); + elem.css('background-color', ''); + } + + elem.html(body); + + if (panel.sparkline.show) { + addSparkline(); + } + + elem.toggleClass('pointer', panel.links.length > 0); + + if (panel.links.length > 0) { + linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); + } else { + linkInfo = null; + } + } + + function hookupDrilldownLinkTooltip() { + // drilldown link tooltip + var drilldownTooltip = $('
    hello
    "'); + + elem.mouseleave(function() { + if (panel.links.length === 0) { return;} + drilldownTooltip.detach(); + }); + + elem.click(function(evt) { + if (!linkInfo) { return; } + // ignore title clicks in title + if ($(evt).parents('.panel-header').length > 0) { return; } + + if (linkInfo.target === '_blank') { + var redirectWindow = window.open(linkInfo.href, '_blank'); + redirectWindow.location; + return; + } + + if (linkInfo.href.indexOf('http') === 0) { + window.location.href = linkInfo.href; + } else { + $timeout(function() { + $location.url(linkInfo.href); + }); + } + + drilldownTooltip.detach(); + }); + + elem.mousemove(function(e) { + if (!linkInfo) { return;} + + drilldownTooltip.text('click to go to: ' + linkInfo.title); + drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); + }); + } + } + }; +} + +export {singleStatPanel as panel}; diff --git a/public/app/plugins/panel/singlestat/singleStatPanel.js b/public/app/plugins/panel/singlestat/singleStatPanel.js deleted file mode 100644 index a176ea974ae..00000000000 --- a/public/app/plugins/panel/singlestat/singleStatPanel.js +++ /dev/null @@ -1,221 +0,0 @@ -define([ - 'angular', - 'app/app', - 'lodash', - 'jquery', - 'jquery.flot', -], -function (angular, app, _, $) { - 'use strict'; - - var module = angular.module('grafana.panels.singlestat', []); - app.useModule(module); - - module.directive('singlestatPanel', function($location, linkSrv, $timeout, templateSrv) { - - return { - link: function(scope, elem) { - var data, panel, linkInfo; - var $panelContainer = elem.parents('.panel-container'); - - scope.$on('render', function() { - render(); - scope.panelRenderingComplete(); - }); - - function setElementHeight() { - try { - var height = scope.height || panel.height || scope.row.height; - if (_.isString(height)) { - height = parseInt(height.replace('px', ''), 10); - } - - height -= 5; // padding - height -= panel.title ? 24 : 9; // subtract panel title bar - - elem.css('height', height + 'px'); - - return true; - } catch(e) { // IE throws errors sometimes - return false; - } - } - - function applyColoringThresholds(value, valueString) { - if (!panel.colorValue) { - return valueString; - } - - var color = getColorForValue(value); - if (color) { - return ''+ valueString + ''; - } - - return valueString; - } - - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value >= data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - - function getSpan(className, fontSize, value) { - value = templateSrv.replace(value); - return '' + - value + ''; - } - - function getBigValueHtml() { - var body = '
    '; - - if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } - - var value = applyColoringThresholds(data.valueRounded, data.valueFormated); - body += getSpan('singlestat-panel-value', panel.valueFontSize, value); - - if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } - - body += '
    '; - - return body; - } - - function addSparkline() { - var panel = scope.panel; - var width = elem.width() + 20; - var height = elem.height() || 100; - - var plotCanvas = $('
    '); - var plotCss = {}; - plotCss.position = 'absolute'; - - if (panel.sparkline.full) { - plotCss.bottom = '5px'; - plotCss.left = '-5px'; - plotCss.width = (width - 10) + 'px'; - var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; - plotCss.height = (height - dynamicHeightMargin) + 'px'; - } - else { - plotCss.bottom = "0px"; - plotCss.left = "-5px"; - plotCss.width = (width - 10) + 'px'; - plotCss.height = Math.floor(height * 0.25) + "px"; - } - - plotCanvas.css(plotCss); - - var options = { - legend: { show: false }, - series: { - lines: { - show: true, - fill: 1, - lineWidth: 1, - fillColor: panel.sparkline.fillColor, - }, - }, - yaxes: { show: false }, - xaxis: { - show: false, - mode: "time", - min: scope.range.from.valueOf(), - max: scope.range.to.valueOf(), - }, - grid: { hoverable: false, show: false }, - }; - - elem.append(plotCanvas); - - var plotSeries = { - data: data.flotpairs, - color: panel.sparkline.lineColor - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function render() { - if (!scope.data) { return; } - - data = scope.data; - panel = scope.panel; - - setElementHeight(); - - var body = getBigValueHtml(); - - if (panel.colorBackground && !isNaN(data.valueRounded)) { - var color = getColorForValue(data.valueRounded); - if (color) { - $panelContainer.css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } - } else { - $panelContainer.css('background-color', ''); - elem.css('background-color', ''); - } - - elem.html(body); - - if (panel.sparkline.show) { - addSparkline(); - } - - elem.toggleClass('pointer', panel.links.length > 0); - - if (panel.links.length > 0) { - linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); - } else { - linkInfo = null; - } - } - - // drilldown link tooltip - var drilldownTooltip = $('
    hello
    "'); - - elem.mouseleave(function() { - if (panel.links.length === 0) { return;} - drilldownTooltip.detach(); - }); - - elem.click(function() { - if (!linkInfo) { return; } - - if (linkInfo.target === '_blank') { - var redirectWindow = window.open(linkInfo.href, '_blank'); - redirectWindow.location; - return; - } - - if (linkInfo.href.indexOf('http') === 0) { - window.location.href = linkInfo.href; - } else { - $timeout(function() { - $location.url(linkInfo.href); - }); - } - - drilldownTooltip.detach(); - }); - - elem.mousemove(function(e) { - if (!linkInfo) { return;} - - drilldownTooltip.text('click to go to: ' + linkInfo.title); - - drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); - }); - } - }; - }); - -}); diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts new file mode 100644 index 00000000000..e69de29bb2d From c7fae5386daf060d848bc840a33cbe60aef1457d Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 24 Jan 2016 11:01:33 -0800 Subject: [PATCH 035/107] Added backend API for stats --- pkg/api/{admin_settings.go => admin.go} | 14 ++++++++ pkg/api/api.go | 2 ++ pkg/models/stats.go | 15 ++++++++ pkg/services/sqlstore/stats.go | 47 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+) rename pkg/api/{admin_settings.go => admin.go} (65%) diff --git a/pkg/api/admin_settings.go b/pkg/api/admin.go similarity index 65% rename from pkg/api/admin_settings.go rename to pkg/api/admin.go index 1f800cfe558..1264cfa12eb 100644 --- a/pkg/api/admin_settings.go +++ b/pkg/api/admin.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" ) func AdminGetSettings(c *middleware.Context) { @@ -27,3 +29,15 @@ func AdminGetSettings(c *middleware.Context) { c.JSON(200, settings) } + +func AdminGetStats(c *middleware.Context) { + + statsQuery := m.GetAdminStatsQuery{} + + if err := bus.Dispatch(&statsQuery); err != nil { + c.JsonApiErr(500, "Failed to get admin stats from database", err) + return + } + + c.JSON(200, statsQuery.Result) +} diff --git a/pkg/api/api.go b/pkg/api/api.go index ea434ed71da..7a4fc684497 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -40,6 +40,7 @@ func Register(r *macaron.Macaron) { r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/orgs", reqGrafanaAdmin, Index) r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) + r.Get("/admin/stats", reqGrafanaAdmin, Index) r.Get("/apps", reqSignedIn, Index) r.Get("/apps/edit/*", reqSignedIn, Index) @@ -210,6 +211,7 @@ func Register(r *macaron.Macaron) { r.Delete("/users/:id", AdminDeleteUser) r.Get("/users/:id/quotas", wrap(GetUserQuotas)) r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) + r.Get("/stats", AdminGetStats) }, reqGrafanaAdmin) // rendering diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 6a060137ac7..8fd7614ffd2 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -18,3 +18,18 @@ type GetSystemStatsQuery struct { type GetDataSourceStatsQuery struct { Result []*DataSourceStats } + +type AdminStats struct { + UserCount int + OrgCount int + DashboardCount int + DBSnapshotCount int + DBTagCount int + DataSourceCount int + PlaylistCount int + StarredDBCount int +} + +type GetAdminStatsQuery struct { + Result *AdminStats +} diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 044aa185f19..c57128bc76a 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -8,6 +8,7 @@ import ( func init() { bus.AddHandler("sql", GetSystemStats) bus.AddHandler("sql", GetDataSourceStats) + bus.AddHandler("sql", GetAdminStats) } func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error { @@ -46,3 +47,49 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { query.Result = &stats return err } + +func GetAdminStats(query *m.GetAdminStatsQuery) error { + var rawSql = `SELECT + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` + ) AS user_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("org") + ` + ) AS org_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("dashboard") + ` + ) AS dashboard_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("dashboard_snapshot") + ` + ) AS db_snapshot_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("dashboard_tag") + ` + ) AS db_tag_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("data_source") + ` + ) AS datasource_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("playlist") + ` + ) AS playlist_count, + ( + SELECT DISTINCT(dashboard_id) + FROM ` + dialect.Quote("star") + ` + ) AS starred_db_count + ` + + var stats m.AdminStats + _, err := x.Sql(rawSql).Get(&stats) + if err != nil { + return err + } + + query.Result = &stats + return err +} From da67afa51ede6e72ac61f7e499d0a704390c11cb Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 24 Jan 2016 21:18:17 -0800 Subject: [PATCH 036/107] Fixed api bugs, stats endpoint working --- pkg/api/admin.go | 18 +++++++++--------- pkg/api/api.go | 4 ++-- pkg/models/stats.go | 18 +++++++++--------- pkg/services/sqlstore/stats.go | 22 +++++++++++----------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pkg/api/admin.go b/pkg/api/admin.go index 1264cfa12eb..d7f5a240416 100644 --- a/pkg/api/admin.go +++ b/pkg/api/admin.go @@ -3,10 +3,10 @@ package api import ( "strings" + "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/bus" - m "github.com/grafana/grafana/pkg/models" ) func AdminGetSettings(c *middleware.Context) { @@ -32,12 +32,12 @@ func AdminGetSettings(c *middleware.Context) { func AdminGetStats(c *middleware.Context) { - statsQuery := m.GetAdminStatsQuery{} - - if err := bus.Dispatch(&statsQuery); err != nil { - c.JsonApiErr(500, "Failed to get admin stats from database", err) - return - } + statsQuery := m.GetAdminStatsQuery{} - c.JSON(200, statsQuery.Result) + if err := bus.Dispatch(&statsQuery); err != nil { + c.JsonApiErr(500, "Failed to get admin stats from database", err) + return + } + + c.JSON(200, statsQuery.Result) } diff --git a/pkg/api/api.go b/pkg/api/api.go index 7a4fc684497..32ccd5dc793 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -40,7 +40,7 @@ func Register(r *macaron.Macaron) { r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/orgs", reqGrafanaAdmin, Index) r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) - r.Get("/admin/stats", reqGrafanaAdmin, Index) + r.Get("/admin/stats", reqGrafanaAdmin, Index) r.Get("/apps", reqSignedIn, Index) r.Get("/apps/edit/*", reqSignedIn, Index) @@ -211,7 +211,7 @@ func Register(r *macaron.Macaron) { r.Delete("/users/:id", AdminDeleteUser) r.Get("/users/:id/quotas", wrap(GetUserQuotas)) r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) - r.Get("/stats", AdminGetStats) + r.Get("/stats", AdminGetStats) }, reqGrafanaAdmin) // rendering diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 8fd7614ffd2..6d8618d6ddf 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -20,16 +20,16 @@ type GetDataSourceStatsQuery struct { } type AdminStats struct { - UserCount int - OrgCount int - DashboardCount int - DBSnapshotCount int - DBTagCount int - DataSourceCount int - PlaylistCount int - StarredDBCount int + 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 { - Result *AdminStats + Result *AdminStats } diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index c57128bc76a..3c9325fa149 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -8,7 +8,7 @@ import ( func init() { bus.AddHandler("sql", GetSystemStats) bus.AddHandler("sql", GetDataSourceStats) - bus.AddHandler("sql", GetAdminStats) + bus.AddHandler("sql", GetAdminStats) } func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error { @@ -49,7 +49,7 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { } func GetAdminStats(query *m.GetAdminStatsQuery) error { - var rawSql = `SELECT + var rawSql = `SELECT ( SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` @@ -73,23 +73,23 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { ( SELECT COUNT(*) FROM ` + dialect.Quote("data_source") + ` - ) AS datasource_count, + ) AS data_source_count, ( SELECT COUNT(*) FROM ` + dialect.Quote("playlist") + ` ) AS playlist_count, ( - SELECT DISTINCT(dashboard_id) + SELECT COUNT (DISTINCT ` + dialect.Quote("dashboard_id") + ` ) FROM ` + dialect.Quote("star") + ` ) AS starred_db_count ` - var stats m.AdminStats - _, err := x.Sql(rawSql).Get(&stats) - if err != nil { - return err - } + var stats m.AdminStats + _, err := x.Sql(rawSql).Get(&stats) + if err != nil { + return err + } - query.Result = &stats - return err + query.Result = &stats + return err } From 4c12703e0c756af1b9ec00ac8da1f4d23f11e11c Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 24 Jan 2016 21:39:30 -0800 Subject: [PATCH 037/107] Integrated angularjs with go api --- public/app/features/admin/adminStatsCtrl.js | 8 +++---- public/app/features/admin/partials/stats.html | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/public/app/features/admin/adminStatsCtrl.js b/public/app/features/admin/adminStatsCtrl.js index 94b40b59591..217429d1253 100644 --- a/public/app/features/admin/adminStatsCtrl.js +++ b/public/app/features/admin/adminStatsCtrl.js @@ -6,16 +6,16 @@ function (angular) { var module = angular.module('grafana.controllers'); - module.controller('AdminStatsCtrl', function($scope) { + module.controller('AdminStatsCtrl', function($scope, backendSrv) { $scope.init = function() { $scope.getStats(); }; $scope.getStats = function() { -// backendSrv.get('/api/admin/stats').then(function(stats) { -// $scope.stats = stats; -// }); + backendSrv.get('/api/admin/stats').then(function(stats) { + $scope.stats = stats; + }); }; $scope.init(); diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index 048be83c83e..0df6d251f5c 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -17,35 +17,35 @@ Total dashboards - 213 + {{stats.dashboard_count}} Total users - 97 + {{stats.user_count}} Total organizations - 4 + {{stats.org_count}} + + + Total datasources + {{stats.data_source_count}} Total playlists - 12 + {{stats.playlist_count}} Total snapshots - 64 + {{stats.db_snapshot_count}} Total dashboard tags - 15 + {{stats.db_tag_count}} Total starred dashboards - 131 - - - Total panels - 2739 + {{stats.starred_db_count}} From a621c0d27356321da6e71d766a24aa583dfc1320 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 00:02:05 -0800 Subject: [PATCH 038/107] Added docs for stats api --- docs/sources/reference/http_api.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index 7cdd8a872d5..a7ae066a8db 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -1422,6 +1422,33 @@ Keys: } } +### Grafana Stats + +`GET /api/admin/stats` + +**Example Request**: + + GET /api/admin/stats + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "user_count":2, + "org_count":1, + "dashboard_count":4, + "db_snapshot_count":2, + "db_tag_count":6, + "data_source_count":1, + "playlist_count":1, + "starred_db_count":2 + } + ### Global Users `POST /api/admin/users` From 2190392e052221905eba1b6f164ddc9574d92c9a Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 00:39:31 -0800 Subject: [PATCH 039/107] Added grafana_admins count --- docs/sources/reference/http_api.md | 3 ++- pkg/models/stats.go | 17 +++++++++-------- pkg/services/sqlstore/stats.go | 6 +++++- public/app/features/admin/partials/stats.html | 11 +++++++++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index a7ae066a8db..4b0dff6c4b6 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -1446,7 +1446,8 @@ Keys: "db_tag_count":6, "data_source_count":1, "playlist_count":1, - "starred_db_count":2 + "starred_db_count":2, + "grafana_admin_count":2 } ### Global Users diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 6d8618d6ddf..4b1d863d1d2 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -20,14 +20,15 @@ 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"` + 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"` } type GetAdminStatsQuery struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 3c9325fa149..ad1d87299b1 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -81,7 +81,11 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { ( SELECT COUNT (DISTINCT ` + dialect.Quote("dashboard_id") + ` ) FROM ` + dialect.Quote("star") + ` - ) AS starred_db_count + ) AS starred_db_count, + ( + SELECT SUM ( ` + dialect.Quote("is_admin") + ` ) + FROM ` + dialect.Quote("user") + ` + ) AS grafana_admin_count ` var stats m.AdminStats diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index 0df6d251f5c..3743b0c81b8 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -1,10 +1,13 @@ - + +

    - Stats + Overview

    @@ -23,6 +26,10 @@ + + + + From b0a24ae4aadc3067b55f1f8cb140f125c77652cb Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 14:48:23 +0100 Subject: [PATCH 040/107] tech(singlestat): move singlestat test to plugin and change to typescript --- .../singlestat/specs/singlestat-specs.ts | 88 +++++++++++++++++++ .../singlestat/specs/singlestat_panel_spec.ts | 58 ++++++++++++ public/test/specs/singlestat-specs.js | 88 ------------------- 3 files changed, 146 insertions(+), 88 deletions(-) create mode 100644 public/app/plugins/panel/singlestat/specs/singlestat-specs.ts delete mode 100644 public/test/specs/singlestat-specs.js diff --git a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts new file mode 100644 index 00000000000..8d532d17d80 --- /dev/null +++ b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts @@ -0,0 +1,88 @@ +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; + +import 'app/features/panel/panel_srv'; +import 'app/features/panel/panel_helper'; + +import angular from 'angular'; +import helpers from '../../../../../test/specs/helpers'; +import {SingleStatCtrl} from '../controller'; + + +angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl); + +describe('SingleStatCtrl', function() { + var ctx = new helpers.ControllerTestContext(); + + function singleStatScenario(desc, func) { + + describe(desc, function() { + + ctx.setup = function (setupFunc) { + + beforeEach(angularMocks.module('grafana.services')); + beforeEach(angularMocks.module('grafana.controllers')); + + beforeEach(ctx.providePhase()); + beforeEach(ctx.createControllerPhase('SingleStatCtrl')); + + beforeEach(function() { + setupFunc(); + ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ + data: [{target: 'test.cpu1', datapoints: ctx.datapoints}] + })); + + ctx.scope.refreshData(ctx.datasource); + ctx.scope.$digest(); + ctx.data = ctx.scope.data; + }); + }; + + func(ctx); + }); + } + + singleStatScenario('with defaults', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[10,1], [20,2]]; + }); + + it('Should use series avg as default main value', function() { + expect(ctx.data.value).to.be(15); + expect(ctx.data.valueRounded).to.be(15); + }); + + it('should set formated falue', function() { + expect(ctx.data.valueFormated).to.be('15'); + }); + }); + + singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[99.999,1], [99.99999,2]]; + }); + + it('Should be rounded', function() { + expect(ctx.data.value).to.be(99.999495); + expect(ctx.data.valueRounded).to.be(100); + }); + + it('should set formated falue', function() { + expect(ctx.data.valueFormated).to.be('100'); + }); + }); + + singleStatScenario('When value to text mapping is specified', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[10,1]]; + ctx.scope.panel.valueMaps = [{value: '10', text: 'OK'}]; + }); + + it('Should replace value with text', function() { + expect(ctx.data.value).to.be(10); + expect(ctx.data.valueFormated).to.be('OK'); + }); + + }); +}); diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts index e69de29bb2d..17b34f6bcef 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts @@ -0,0 +1,58 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {getColorForValue} from '../module'; + +describe('grafanaSingleStat', function() { + describe('legacy thresholds', () => { + describe('positive thresholds', () => { + var data: any = { + colorMap: ['green', 'yellow', 'red'], + thresholds: [0, 20, 50] + }; + + it('5 should return green', () => { + expect(getColorForValue(data, 5)).to.be('green'); + }); + + it('25 should return green', () => { + expect(getColorForValue(data, 25)).to.be('yellow'); + }); + + it('55 should return green', () => { + expect(getColorForValue(data, 55)).to.be('red'); + }); + }); + }); + + + + describe('negative thresholds', () => { + var data: any = { + colorMap: ['green', 'yellow', 'red'], + thresholds: [ -20, 0, 20] + }; + + it('-30 should return green', () => { + expect(getColorForValue(data, -30)).to.be('green'); + }); + + it('1 should return green', () => { + expect(getColorForValue(data, 1)).to.be('yellow'); + }); + + it('22 should return green', () => { + expect(getColorForValue(data, 22)).to.be('red'); + }); + }); + + describe('negative thresholds', () => { + var data: any = { + colorMap: ['green', 'yellow', 'red'], + thresholds: [ -40, -27, 20] + }; + + it('-30 should return green', () => { + expect(getColorForValue(data, -26)).to.be('yellow'); + }); + }); +}); diff --git a/public/test/specs/singlestat-specs.js b/public/test/specs/singlestat-specs.js deleted file mode 100644 index 60953345f2d..00000000000 --- a/public/test/specs/singlestat-specs.js +++ /dev/null @@ -1,88 +0,0 @@ -define([ - 'angular', - './helpers', - 'app/plugins/panel/singlestat/controller', - 'app/features/panel/panel_srv', - 'app/features/panel/panel_helper', -], function(angular, helpers, SingleStatCtrl) { - 'use strict'; - - angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl); - - describe('SingleStatCtrl', function() { - var ctx = new helpers.ControllerTestContext(); - - function singleStatScenario(desc, func) { - - describe(desc, function() { - - ctx.setup = function (setupFunc) { - - beforeEach(module('grafana.services')); - beforeEach(module('grafana.controllers')); - - beforeEach(ctx.providePhase()); - beforeEach(ctx.createControllerPhase('SingleStatCtrl')); - - beforeEach(function() { - setupFunc(); - ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ - data: [{target: 'test.cpu1', datapoints: ctx.datapoints}] - })); - - ctx.scope.refreshData(ctx.datasource); - ctx.scope.$digest(); - ctx.data = ctx.scope.data; - }); - }; - - func(ctx); - }); - } - - singleStatScenario('with defaults', function(ctx) { - ctx.setup(function() { - ctx.datapoints = [[10,1], [20,2]]; - }); - - it('Should use series avg as default main value', function() { - expect(ctx.data.value).to.be(15); - expect(ctx.data.valueRounded).to.be(15); - }); - - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('15'); - }); - }); - - singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) { - ctx.setup(function() { - ctx.datapoints = [[99.999,1], [99.99999,2]]; - }); - - it('Should be rounded', function() { - expect(ctx.data.value).to.be(99.999495); - expect(ctx.data.valueRounded).to.be(100); - }); - - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('100'); - }); - }); - - singleStatScenario('When value to text mapping is specified', function(ctx) { - ctx.setup(function() { - ctx.datapoints = [[10,1]]; - ctx.scope.panel.valueMaps = [{value: '10', text: 'OK'}]; - }); - - it('Should replace value with text', function() { - expect(ctx.data.value).to.be(10); - expect(ctx.data.valueFormated).to.be('OK'); - }); - - }); - - }); -}); - From cd1b2e28417eed741f32f839a09ba5c9a92ead14 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 16:27:00 +0100 Subject: [PATCH 041/107] feat(singlestat): reduce max thresholds to two. closes #3248 --- public/app/features/dashboard/dashboardSrv.js | 21 ++++++++++++++-- .../app/plugins/panel/singlestat/editor.html | 4 +-- public/app/plugins/panel/singlestat/module.ts | 25 ++++++++++--------- public/test/specs/dashboardSrv-specs.js | 12 ++++++++- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index 559adff5c54..e4d74c985ba 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -234,9 +234,9 @@ function (angular, $, _, moment) { var i, j, k; var oldVersion = this.schemaVersion; var panelUpgrades = []; - this.schemaVersion = 8; + this.schemaVersion = 9; - if (oldVersion === 8) { + if (oldVersion === this.schemaVersion) { return; } @@ -390,6 +390,23 @@ function (angular, $, _, moment) { }); } + // schema version 9 changes + if (oldVersion < 9) { + // move aliasYAxis changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; } + + if (panel.thresholds) { + var k = panel.thresholds.split(","); + + if (k.length >= 3) { + k.shift(); + panel.thresholds = k.join(","); + } + } + }); + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index f8df6d8adc9..76c264e7b3a 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -97,10 +97,10 @@
  • - ThresholdsComma seperated values + ThresholdsDefine two threshold values<br /> 50,80 will produce: <50 = Green, 50:80 = Yellow, >80 = Red
  • - +
  • Colors diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index c6d10afb567..0a171207bcf 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -54,7 +54,7 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { return valueString; } - var color = getColorForValue(value); + var color = getColorForValue(data, value); if (color) { return ''+ valueString + ''; } @@ -62,15 +62,6 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { return valueString; } - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value >= data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - function getSpan(className, fontSize, value) { value = templateSrv.replace(value); return '' + @@ -157,7 +148,7 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { var body = getBigValueHtml(); if (panel.colorBackground && !isNaN(data.valueRounded)) { - var color = getColorForValue(data.valueRounded); + var color = getColorForValue(data, data.valueRounded); if (color) { $panelContainer.css('background-color', color); if (scope.fullscreen) { @@ -228,4 +219,14 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { }; } -export {singleStatPanel as panel}; +function getColorForValue(data, value) { + for (var i = data.thresholds.length; i > 0; i--) { + if (value >= data.thresholds[i]) { + return data.colorMap[i]; + } + } + + return _.first(data.colorMap); +} + +export {singleStatPanel as panel, getColorForValue}; diff --git a/public/test/specs/dashboardSrv-specs.js b/public/test/specs/dashboardSrv-specs.js index 5b2fefd384d..4a6c01bdc3b 100644 --- a/public/test/specs/dashboardSrv-specs.js +++ b/public/test/specs/dashboardSrv-specs.js @@ -141,6 +141,7 @@ define([ describe('when creating dashboard with old schema', function() { var model; var graph; + var singlestat; beforeEach(function() { model = _dashboardSrv.create({ @@ -155,6 +156,10 @@ define([ { type: 'graphite', legend: true, aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 }, targets: [{refId: 'A'}, {}], + }, + { + type: 'singlestat', legend: true, thresholds: '10,20,30', aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 }, + targets: [{refId: 'A'}, {}], } ] } @@ -162,6 +167,7 @@ define([ }); graph = model.rows[0].panels[0]; + singlestat = model.rows[0].panels[1]; }); it('should have title', function() { @@ -181,6 +187,10 @@ define([ expect(graph.type).to.be('graph'); }); + it('single stat panel should have two thresholds', function() { + expect(singlestat.thresholds).to.be('20,30'); + }); + it('queries without refId should get it', function() { expect(graph.targets[1].refId).to.be('B'); }); @@ -204,7 +214,7 @@ define([ }); it('dashboard schema version should be set to latest', function() { - expect(model.schemaVersion).to.be(8); + expect(model.schemaVersion).to.be(9); }); }); From b939a27a8dcdbd6a405a270784692ad226a84d8b Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 18:40:28 +0100 Subject: [PATCH 042/107] revert f3bc726001bfbc707597bce5fa169b46e992eb7ao This change have caused alot of questions. So we revert --- public/app/plugins/panel/graph/graph_tooltip.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index 99fb20b2ed5..1eabbd0f7af 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/public/app/plugins/panel/graph/graph_tooltip.js @@ -79,9 +79,9 @@ function ($) { // Stacked series can increase its length on each new stacked serie if null points found, // to speed the index search we begin always on the last found hoverIndex. var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); - results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label }); + results.push({ value: value, hoverIndex: newhoverIndex }); } else { - results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label }); + results.push({ value: value, hoverIndex: hoverIndex }); } } @@ -126,8 +126,6 @@ function ($) { relativeTime = dashboard.getRelativeTime(seriesHoverInfo.time); absoluteTime = dashboard.formatDate(seriesHoverInfo.time); - seriesHoverInfo.sort(byToolTipValue); - for (i = 0; i < seriesHoverInfo.length; i++) { hoverInfo = seriesHoverInfo[i]; @@ -140,7 +138,7 @@ function ($) { value = series.formatValue(hoverInfo.value); seriesHtml += '
    '; - seriesHtml += ' ' + hoverInfo.label + ':
    '; + seriesHtml += ' ' + series.label + ':
    '; seriesHtml += '
    ' + value + '
    '; plot.highlight(i, hoverInfo.hoverIndex); } @@ -176,9 +174,5 @@ function ($) { }); } - function byToolTipValue(a, b) { - return parseFloat(b.value) - parseFloat(a.value); - } - return GraphTooltip; }); From 40088cd4fe31a7a6fa61ffa4493c101cbd6ebf86 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 20:30:25 +0100 Subject: [PATCH 043/107] tech(docker): add elastic fig --- docker/blocks/elastic/elasticsearch/config/.placeholder | 1 + docker/blocks/elastic/fig | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docker/blocks/elastic/elasticsearch/config/.placeholder create mode 100644 docker/blocks/elastic/fig diff --git a/docker/blocks/elastic/elasticsearch/config/.placeholder b/docker/blocks/elastic/elasticsearch/config/.placeholder new file mode 100644 index 00000000000..9ad266259c2 --- /dev/null +++ b/docker/blocks/elastic/elasticsearch/config/.placeholder @@ -0,0 +1 @@ +Ensure the existence of the parent folder. diff --git a/docker/blocks/elastic/fig b/docker/blocks/elastic/fig new file mode 100644 index 00000000000..498402ac7b0 --- /dev/null +++ b/docker/blocks/elastic/fig @@ -0,0 +1,6 @@ +elasticsearch: + image: elasticsearch:latest + command: elasticsearch -Des.network.host=0.0.0.0 + ports: + - "9200:9200" + - "9300:9300" From 1cac6ecedbda59067966ff9b8354836fae3cabe6 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 20:46:15 +0100 Subject: [PATCH 044/107] tech(docker): update influxdb to use latest --- docker/blocks/influxdb/Dockerfile | 16 ---------------- docker/blocks/influxdb/fig | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 docker/blocks/influxdb/Dockerfile diff --git a/docker/blocks/influxdb/Dockerfile b/docker/blocks/influxdb/Dockerfile deleted file mode 100644 index 69d10992464..00000000000 --- a/docker/blocks/influxdb/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# influxdb - -FROM ubuntu - -RUN mkdir -p /opt/influxdb/shared/data - -ADD http://s3.amazonaws.com/influxdb/influxdb_0.8.8_amd64.deb /influx88.deb -RUN dpkg -i /influx88.deb -RUN rm -rf /opt/influxdb/shared/data - -ADD config.toml /opt/influxdb/shared/config.toml - -EXPOSE 8083 8086 2004 - -ENTRYPOINT ["/usr/bin/influxdb"] -CMD ["-config=/opt/influxdb/shared/config.toml"] diff --git a/docker/blocks/influxdb/fig b/docker/blocks/influxdb/fig index 3f247f756e2..931f8a2640a 100644 --- a/docker/blocks/influxdb/fig +++ b/docker/blocks/influxdb/fig @@ -1,5 +1,5 @@ influxdb: - build: blocks/influxdb + image: tutum/influxdb:latest ports: - "2004:2004" - "8083:8083" From d8bb7c3094831d7847527692b96d25ea6d52ac3c Mon Sep 17 00:00:00 2001 From: jimmyR Date: Mon, 25 Jan 2016 20:57:55 +0100 Subject: [PATCH 045/107] resolves #3741 merge conflict After a refactoring the sidemenu-canvas css class disappeared so changed the code from #3741 to use the main-view class instead. --- vendor/phantomjs/render.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index 95885a17886..02acabebc33 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -49,6 +49,15 @@ }); if (canvas || tries === 1000) { + var bb = page.evaluate(function () { + return document.getElementsByClassName("main-view")[0].getBoundingClientRect(); + }); + page.clipRect = { + top: bb.top, + left: bb.left, + width: bb.width, + height: bb.height + }; page.render(params.png); phantom.exit(); } From 40d946a6e3399f05ccd9cf654debfa7f2eb02786 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Tue, 26 Jan 2016 04:18:18 +0800 Subject: [PATCH 046/107] add drop table to ensure existing installs get new schema --- pkg/services/sqlstore/migrations/app_settings.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/services/sqlstore/migrations/app_settings.go b/pkg/services/sqlstore/migrations/app_settings.go index 8b970a5062a..9c01b242b6e 100644 --- a/pkg/services/sqlstore/migrations/app_settings.go +++ b/pkg/services/sqlstore/migrations/app_settings.go @@ -4,7 +4,7 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" func addAppSettingsMigration(mg *Migrator) { - appSettingsV1 := Table{ + appSettingsV2 := Table{ Name: "app_settings", Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, @@ -22,7 +22,9 @@ func addAppSettingsMigration(mg *Migrator) { }, } - mg.AddMigration("create app_settings table v1", NewAddTableMigration(appSettingsV1)) + mg.AddMigration("Drop old table app_settings v1", NewDropTableMigration("app_settings")) + + mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2)) //------- indexes ------------------ addTableIndicesMigrations(mg, "v3", appSettingsV1) From 092bb69c416e3b22536a01e5bdae9671ad18651e Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Tue, 26 Jan 2016 04:18:44 +0800 Subject: [PATCH 047/107] instead of padding with 0's, cycle through the secret. --- pkg/util/encryption.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/util/encryption.go b/pkg/util/encryption.go index f24a9b29397..ee0f188f04d 100644 --- a/pkg/util/encryption.go +++ b/pkg/util/encryption.go @@ -60,11 +60,7 @@ func encryptionKeyToBytes(secret string) []byte { keyBytes := []byte(secret) secretLength := len(keyBytes) for i := 0; i < 32; i++ { - if secretLength > i { - key[i] = keyBytes[i] - } else { - key[i] = 0 - } + key[i] = keyBytes[i%secretLength] } return key } From 05868bc1dfb28a2fd36792b6259ba3ba7951fefb Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Tue, 26 Jan 2016 04:24:44 +0800 Subject: [PATCH 048/107] fix typo --- pkg/services/sqlstore/migrations/app_settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/migrations/app_settings.go b/pkg/services/sqlstore/migrations/app_settings.go index 9c01b242b6e..885dbbf9f05 100644 --- a/pkg/services/sqlstore/migrations/app_settings.go +++ b/pkg/services/sqlstore/migrations/app_settings.go @@ -27,5 +27,5 @@ func addAppSettingsMigration(mg *Migrator) { mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2)) //------- indexes ------------------ - addTableIndicesMigrations(mg, "v3", appSettingsV1) + addTableIndicesMigrations(mg, "v3", appSettingsV2) } From 52403ca17eaf2e469c5978a962195c2f886fb22e Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 21:39:04 +0100 Subject: [PATCH 049/107] feat(playlist): add usage statisics --- pkg/metrics/report_usage.go | 1 + pkg/models/stats.go | 1 + pkg/services/sqlstore/stats.go | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/metrics/report_usage.go b/pkg/metrics/report_usage.go index abda18d12b7..c5c2afed7f7 100644 --- a/pkg/metrics/report_usage.go +++ b/pkg/metrics/report_usage.go @@ -55,6 +55,7 @@ func sendUsageStats() { metrics["stats.dashboards.count"] = statsQuery.Result.DashboardCount metrics["stats.users.count"] = statsQuery.Result.UserCount metrics["stats.orgs.count"] = statsQuery.Result.OrgCount + metrics["stats.playlist.count"] = statsQuery.Result.PlaylistCount dsStats := m.GetDataSourceStatsQuery{} if err := bus.Dispatch(&dsStats); err != nil { diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 6a060137ac7..30c09deb768 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -4,6 +4,7 @@ type SystemStats struct { DashboardCount int UserCount int OrgCount int + PlaylistCount int } type DataSourceStats struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 044aa185f19..203534b3b93 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -1,6 +1,8 @@ package sqlstore import ( + "fmt" + "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) @@ -34,7 +36,11 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { ( SELECT COUNT(*) FROM ` + dialect.Quote("dashboard") + ` - ) AS dashboard_count + ) AS dashboard_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("playlist") + ` + ) AS playlist_count ` var stats m.SystemStats From 92cba9403181ad1094722ddf5440e71cc34ba3cf Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 22:11:24 +0100 Subject: [PATCH 050/107] tech(fmt): remove unused code --- pkg/services/sqlstore/stats.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 203534b3b93..6c5dfaea906 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -1,8 +1,6 @@ package sqlstore import ( - "fmt" - "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) From c8c337ceadc1ef97114cce30828800b6cb6bd842 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Tue, 26 Jan 2016 05:15:29 +0800 Subject: [PATCH 051/107] use PBKDF2 to esnure key is 23bytes. --- pkg/util/encryption.go | 30 +++++++++++++++--------------- pkg/util/encryption_test.go | 5 ++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/pkg/util/encryption.go b/pkg/util/encryption.go index ee0f188f04d..42586ddac8b 100644 --- a/pkg/util/encryption.go +++ b/pkg/util/encryption.go @@ -4,13 +4,17 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/sha256" "io" "github.com/grafana/grafana/pkg/log" ) +const saltLength = 8 + func Decrypt(payload []byte, secret string) []byte { - key := encryptionKeyToBytes(secret) + salt := payload[:saltLength] + key := encryptionKeyToBytes(secret, string(salt)) block, err := aes.NewCipher(key) if err != nil { @@ -22,8 +26,8 @@ func Decrypt(payload []byte, secret string) []byte { if len(payload) < aes.BlockSize { log.Fatal(4, "payload too short") } - iv := payload[:aes.BlockSize] - payload = payload[aes.BlockSize:] + iv := payload[saltLength : saltLength+aes.BlockSize] + payload = payload[saltLength+aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) @@ -33,8 +37,9 @@ func Decrypt(payload []byte, secret string) []byte { } func Encrypt(payload []byte, secret string) []byte { - key := encryptionKeyToBytes(secret) + salt := GetRandomString(saltLength) + key := encryptionKeyToBytes(secret, salt) block, err := aes.NewCipher(key) if err != nil { log.Fatal(4, err.Error()) @@ -42,25 +47,20 @@ func Encrypt(payload []byte, secret string) []byte { // The IV needs to be unique, but not secure. Therefore it's common to // include it at the beginning of the ciphertext. - ciphertext := make([]byte, aes.BlockSize+len(payload)) - iv := ciphertext[:aes.BlockSize] + ciphertext := make([]byte, saltLength+aes.BlockSize+len(payload)) + copy(ciphertext[:saltLength], []byte(salt)) + iv := ciphertext[saltLength : saltLength+aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { log.Fatal(4, err.Error()) } stream := cipher.NewCFBEncrypter(block, iv) - stream.XORKeyStream(ciphertext[aes.BlockSize:], payload) + stream.XORKeyStream(ciphertext[saltLength+aes.BlockSize:], payload) return ciphertext } // Key needs to be 32bytes -func encryptionKeyToBytes(secret string) []byte { - key := make([]byte, 32, 32) - keyBytes := []byte(secret) - secretLength := len(keyBytes) - for i := 0; i < 32; i++ { - key[i] = keyBytes[i%secretLength] - } - return key +func encryptionKeyToBytes(secret, salt string) []byte { + return PBKDF2([]byte(secret), []byte(salt), 10000, 32, sha256.New) } diff --git a/pkg/util/encryption_test.go b/pkg/util/encryption_test.go index 254f0f178c0..5f1dc18fea3 100644 --- a/pkg/util/encryption_test.go +++ b/pkg/util/encryption_test.go @@ -10,12 +10,11 @@ func TestEncryption(t *testing.T) { Convey("When getting encryption key", t, func() { - key := encryptionKeyToBytes("secret") + key := encryptionKeyToBytes("secret", "salt") So(len(key), ShouldEqual, 32) - key = encryptionKeyToBytes("a very long secret key that is larger then 32bytes") + key = encryptionKeyToBytes("a very long secret key that is larger then 32bytes", "salt") So(len(key), ShouldEqual, 32) - }) Convey("When decrypting basic payload", t, func() { From be2e2577b82d7570abb6d6330eb7516d2c856763 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 25 Jan 2016 22:59:48 +0100 Subject: [PATCH 052/107] fixes #3839 Uses width set to 800px for invite div instead of position fixed. It looks the same for desktop and makes the button clickable on smaller resolutions and mobile. Although it is not really responsive so the text will be small on mobiles. Better that though than a non-clickable button. --- public/less/login.less | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public/less/login.less b/public/less/login.less index 924659d9e16..e599bca1927 100644 --- a/public/less/login.less +++ b/public/less/login.less @@ -110,13 +110,9 @@ text-align: center; border: 1px solid @grafanaTargetFuncBackground; background-color: @grafanaPanelBackground; - position: fixed; - max-width: 800px; - left: 0; - right: 0; + width: 800px; margin-left: auto; margin-right: auto; - top: 20%; .tight-form { text-align: left; From 442db7fee1cc87a04fd95477b8a432784311f448 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 14:30:36 -0800 Subject: [PATCH 053/107] Changed sql query for grafana_admin_count --- pkg/services/sqlstore/stats.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index ad1d87299b1..a2e61aa0c3e 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -83,8 +83,9 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { FROM ` + dialect.Quote("star") + ` ) AS starred_db_count, ( - SELECT SUM ( ` + dialect.Quote("is_admin") + ` ) + SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` + WHERE ` + dialect.Quote("is_admin") + ` = 1 ) AS grafana_admin_count ` From 29185eeef78f54e59cf2a91b6bed0017a9c87edd Mon Sep 17 00:00:00 2001 From: Pablo Fischer Date: Tue, 26 Jan 2016 00:12:56 +0000 Subject: [PATCH 054/107] If OpenTSDB is 3rd-party hosted (or by another team) and does not support OPTIONS, send the request as POST (urlencoded) --- public/app/plugins/datasource/opentsdb/datasource.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index d7cc354861f..f4680038e8d 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -72,6 +72,9 @@ function (angular, _, dateMath) { data: reqBody }; + // In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests + // go as POST rather than OPTIONS+POST + options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; return backendSrv.datasourceRequest(options); }; From 07fee0a810f0e0a2f6058ec6be8df5e1d9863508 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 17:49:39 -0800 Subject: [PATCH 055/107] Converted adminStatsCtrl to typescript --- public/app/core/routes/all.js | 1 + public/app/features/admin/adminStatsCtrl.js | 24 ------------------- public/app/features/admin/adminStatsCtrl.ts | 18 ++++++++++++++ public/app/features/admin/partials/stats.html | 20 ++++++++-------- 4 files changed, 29 insertions(+), 34 deletions(-) delete mode 100644 public/app/features/admin/adminStatsCtrl.js create mode 100644 public/app/features/admin/adminStatsCtrl.ts diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index bc71a63a094..d9726ee782c 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -115,6 +115,7 @@ define([ .when('/admin/stats', { templateUrl: 'app/features/admin/partials/stats.html', controller : 'AdminStatsCtrl', + controllerAs: 'ctrl', }) .when('/login', { templateUrl: 'app/partials/login.html', diff --git a/public/app/features/admin/adminStatsCtrl.js b/public/app/features/admin/adminStatsCtrl.js deleted file mode 100644 index 217429d1253..00000000000 --- a/public/app/features/admin/adminStatsCtrl.js +++ /dev/null @@ -1,24 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('AdminStatsCtrl', function($scope, backendSrv) { - - $scope.init = function() { - $scope.getStats(); - }; - - $scope.getStats = function() { - backendSrv.get('/api/admin/stats').then(function(stats) { - $scope.stats = stats; - }); - }; - - $scope.init(); - - }); -}); diff --git a/public/app/features/admin/adminStatsCtrl.ts b/public/app/features/admin/adminStatsCtrl.ts new file mode 100644 index 00000000000..0331524dcc7 --- /dev/null +++ b/public/app/features/admin/adminStatsCtrl.ts @@ -0,0 +1,18 @@ +// + +import angular from 'angular'; + +export class AdminStatsCtrl { + stats: any; + + /** @ngInject */ + constructor(private backendSrv: any) {} + + init() { + this.backendSrv.get('/api/admin/stats').then(stats => { + this.stats = stats; + }); + } +} + +angular.module('grafana.controllers').controller('AdminStatsCtrl', AdminStatsCtrl); diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index 3743b0c81b8..4949e71e441 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -5,7 +5,7 @@
    -
    +

    Overview

    @@ -20,39 +20,39 @@
  • - + - + - + - + - + - + - + - + - +
    Total users {{stats.user_count}}
    Total grafana admins{{stats.grafana_admin_count}}
    Total organizations {{stats.org_count}}
    Total dashboards{{stats.dashboard_count}}{{ctrl.stats.dashboard_count}}
    Total users{{stats.user_count}}{{ctrl.stats.user_count}}
    Total grafana admins{{stats.grafana_admin_count}}{{ctrl.stats.grafana_admin_count}}
    Total organizations{{stats.org_count}}{{ctrl.stats.org_count}}
    Total datasources{{stats.data_source_count}}{{ctrl.stats.data_source_count}}
    Total playlists{{stats.playlist_count}}{{ctrl.stats.playlist_count}}
    Total snapshots{{stats.db_snapshot_count}}{{ctrl.stats.db_snapshot_count}}
    Total dashboard tags{{stats.db_tag_count}}{{ctrl.stats.db_tag_count}}
    Total starred dashboards{{stats.starred_db_count}}{{ctrl.stats.starred_db_count}}
    From e59b0c0694106e2fb131c54ebd03c950756b447d Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 18:10:48 -0800 Subject: [PATCH 056/107] Fixed ts file comment --- public/app/features/admin/adminStatsCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/admin/adminStatsCtrl.ts b/public/app/features/admin/adminStatsCtrl.ts index 0331524dcc7..aa3ed6de343 100644 --- a/public/app/features/admin/adminStatsCtrl.ts +++ b/public/app/features/admin/adminStatsCtrl.ts @@ -1,4 +1,4 @@ -// +/// import angular from 'angular'; From 09de46e5ac1fdfb4cce18f0d374037a0c84bceb2 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 26 Jan 2016 17:54:19 +0100 Subject: [PATCH 057/107] tech(search): convert search to typescript --- .../components/search}/search.html | 31 ++-- public/app/core/components/search/search.ts | 144 ++++++++++++++++++ public/app/core/core.ts | 3 +- .../dashboard/directives/dashSearchView.js | 2 +- 4 files changed, 161 insertions(+), 19 deletions(-) rename public/app/{partials => core/components/search}/search.html (61%) create mode 100644 public/app/core/components/search/search.ts diff --git a/public/app/partials/search.html b/public/app/core/components/search/search.html similarity index 61% rename from public/app/partials/search.html rename to public/app/core/components/search/search.html index fab8fd9291a..1f51c8e0650 100644 --- a/public/app/partials/search.html +++ b/public/app/core/components/search/search.html @@ -1,24 +1,22 @@ -