Przeglądaj źródła

Merge branch 'master' of github.com:grafana/grafana

Conflicts:
	examples/nginx-app/package.json
	examples/nginx-app/plugin.json
Torkel Ödegaard 9 lat temu
rodzic
commit
ece8a925a6
52 zmienionych plików z 155 dodań i 291 usunięć
  1. 10 0
      docs/sources/datasources/cloudwatch.md
  2. 21 1
      docs/sources/http_api/data_source.md
  3. 8 7
      docs/sources/installation/configuration.md
  4. 1 1
      docs/sources/installation/ldap.md
  5. 2 1
      examples/README.md
  6. 0 0
      examples/boilerplate-es5-panel/css/styles.css
  7. 0 0
      examples/boilerplate-es5-panel/module.js
  8. 0 0
      examples/boilerplate-es5-panel/panel.html
  9. 0 0
      examples/boilerplate-es5-panel/plugin.json
  10. 0 7
      examples/nginx-app/.gitignore
  11. 0 13
      examples/nginx-app/.jscs.json
  12. 0 36
      examples/nginx-app/.jshintrc
  13. 0 54
      examples/nginx-app/Gruntfile.js
  14. 0 37
      examples/nginx-app/package.json
  15. 0 7
      examples/nginx-app/readme.md
  16. 0 3
      examples/nginx-app/src/components/config.html
  17. 0 6
      examples/nginx-app/src/components/config.js
  18. 0 3
      examples/nginx-app/src/components/logs.html
  19. 0 6
      examples/nginx-app/src/components/logs.js
  20. 0 3
      examples/nginx-app/src/components/stream.html
  21. 0 6
      examples/nginx-app/src/components/stream.js
  22. 0 0
      examples/nginx-app/src/css/dark.css
  23. 0 0
      examples/nginx-app/src/css/light.css
  24. 0 12
      examples/nginx-app/src/datasource/datasource.js
  25. 0 5
      examples/nginx-app/src/datasource/module.js
  26. 0 5
      examples/nginx-app/src/datasource/plugin.json
  27. BIN
      examples/nginx-app/src/img/logo_large.png
  28. BIN
      examples/nginx-app/src/img/logo_small.png
  29. 0 9
      examples/nginx-app/src/module.js
  30. 0 15
      examples/nginx-app/src/panel/module.js
  31. 0 5
      examples/nginx-app/src/panel/plugin.json
  32. 2 3
      pkg/api/api.go
  33. 4 0
      pkg/api/cloudwatch/metrics.go
  34. 19 0
      pkg/api/datasources.go
  35. 4 0
      pkg/api/dtos/models.go
  36. 16 11
      pkg/api/index.go
  37. 1 0
      pkg/api/render.go
  38. 12 10
      pkg/cmd/grafana-cli/commands/install_command.go
  39. 2 2
      pkg/cmd/grafana-cli/commands/ls_command.go
  40. 3 3
      pkg/cmd/grafana-cli/commands/ls_command_test.go
  41. 1 1
      pkg/cmd/grafana-cli/main.go
  42. 2 1
      pkg/cmd/grafana-cli/services/services.go
  43. 8 1
      pkg/components/renderer/renderer.go
  44. 4 4
      pkg/plugins/dashboards_test.go
  45. 3 3
      pkg/plugins/plugins_test.go
  46. 1 1
      public/app/features/templating/templateValuesSrv.js
  47. 23 1
      public/app/plugins/datasource/cloudwatch/datasource.js
  48. 1 1
      public/app/plugins/datasource/influxdb/datasource.ts
  49. 1 1
      public/dashboards/template_vars.json
  50. 0 0
      tests/test-app/dashboards/connections.json
  51. 0 0
      tests/test-app/dashboards/memory.json
  52. 6 6
      tests/test-app/plugin.json

+ 10 - 0
docs/sources/datasources/cloudwatch.md

@@ -64,9 +64,19 @@ Name | Description
 `metrics(namespace)` | Returns a list of metrics in the namespace.
 `dimension_keys(namespace)` | Returns a list of dimension keys in the namespace.
 `dimension_values(region, namespace, metric, dimension_key)` | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
+`ebs_volume_ids(region, instance_id)` | Returns a list of volume id matching the specified `region`, `instance_id`.
+`ec2_instance_attribute(region, attribute_name, filters)` | Returns a list of attribute matching the specified `region`, `attribute_name`, `filters`.
 
 For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
 
+The `ec2_instance_attribute` query take `filters` in JSON format.  
+You can specify [pre-defined filters of ec2:DescribeInstances](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html).  
+Specify like `{ filter_name1: [ filter_value1 ], filter_name2: [ filter_value2 ] }`
+
+Example `ec2_instance_attribute()` query
+
+    ec2_instance_attribute(us-east-1, InstanceId, { "tag:Environment": [ "production" ] })
+
 ![](/img/v2/cloudwatch_templating.png)
 
 ## Cost

+ 21 - 1
docs/sources/http_api/data_source.md

@@ -74,7 +74,7 @@ page_keywords: grafana, admin, http, api, documentation, datasource
       "jsonData":null
     }
 
-## Get a single data sources by Name
+## Get a single data source by Name
 
 `GET /api/datasources/name/:name`
 
@@ -107,6 +107,26 @@ page_keywords: grafana, admin, http, api, documentation, datasource
       "jsonData":null
     }
 
+## Get data source Id by Name
+
+`GET /api/datasources/id/:name`
+
+**Example Request**:
+
+    GET /api/datasources/id/test_datasource HTTP/1.1
+    Accept: application/json
+    Content-Type: application/json
+    Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+**Example Response**:
+
+    HTTP/1.1 200
+    Content-Type: application/json
+
+    {
+      "id":1
+    }
+
 ## Create data source
 
 `POST /api/datasources`

+ 8 - 7
docs/sources/installation/configuration.md

@@ -159,19 +159,19 @@ The database user's password (not applicable for `sqlite3`).
 For Postgres, use either `disable`, `require` or `verify-full`.
 For MySQL, use either `true`, `false`, or `skip-verify`.
 
-### ca_cert_path 
+### ca_cert_path
 
 (MySQL only) The path to the CA certificate to use. On many linux systems, certs can be found in `/etc/ssl/certs`.
 
-### client_key_path 
+### client_key_path
 
 (MySQL only) The path to the client key. Only if server requires client authentication.
 
-### client_cert_path 
+### client_cert_path
 
 (MySQL only) The path to the client cert. Only if server requires client authentication.
 
-### server_cert_name 
+### server_cert_name
 
 (MySQL only) The common name field of the certificate used by the `mysql` server. Not necessary if `ssl_mode` is set to `skip-verify`.
 
@@ -373,7 +373,7 @@ Set to `true` to enable auto sign up of users who do not exist in Grafana DB. De
 
 ### provider
 
-Valid values are `memory`, `file`, `mysql`, `postgres`, `memcache`. Default is `file`.
+Valid values are `memory`, `file`, `mysql`, `postgres`, `memcache` or `redis`. Default is `file`.
 
 ### provider_config
 
@@ -384,6 +384,7 @@ session provider you have configured.
 - **mysql:** go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
 - **postgres:** ex:  user=a password=b host=localhost port=5432 dbname=c sslmode=disable
 - **memcache:** ex:  127.0.0.1:11211
+- **redis:** ex: `addr=127.0.0.1:6379,pool_size=100,db=grafana`
 
 If you use MySQL or Postgres as the session store you need to create the
 session table manually.
@@ -415,10 +416,10 @@ How long sessions lasts in seconds. Defaults to `86400` (24 hours).
 
 ### reporting_enabled
 
-When enabled Grafana will send anonymous usage statistics to 
+When enabled Grafana will send anonymous usage statistics to
 `stats.grafana.org`. No IP addresses are being tracked, only simple counters to
 track running instances, versions, dashboard & error counts. It is very helpful
-to us, so please leave this enabled. Counters are sent every 24 hours. Default 
+to us, so please leave this enabled. Counters are sent every 24 hours. Default
 value is `true`.
 
 ### google_analytics_ua_id

+ 1 - 1
docs/sources/installation/ldap.md

@@ -6,7 +6,7 @@ page_keywords: grafana, ldap, configuration, documentation, integration
 
 # LDAP Integration
 
-Grafana 2.1 ships with a strong LDAP integration feature. The LDAP integration in Grafana allows your
+Grafana (2.1 and newer) ships with a strong LDAP integration feature. The LDAP integration in Grafana allows your
 Grafana users to login with their LDAP credentials. You can also specify mappings between LDAP
 group memberships and Grafana Organization user roles.
 

+ 2 - 1
examples/README.md

@@ -1,3 +1,4 @@
 ## Example plugin implementations
 
-[datasource-plugin-genericdatsource](https://github.com/grafana/datasource-plugin-genericdatasource/tree/3.0)
+datasource:[simple-json-datasource](https://github.com/grafana/simple-json-datasource)
+app:  [example-app](https://github.com/grafana/example-app)

+ 0 - 0
examples/panel-boilerplate-es5/css/styles.css → examples/boilerplate-es5-panel/css/styles.css


+ 0 - 0
examples/panel-boilerplate-es5/module.js → examples/boilerplate-es5-panel/module.js


+ 0 - 0
examples/panel-boilerplate-es5/panel.html → examples/boilerplate-es5-panel/panel.html


+ 0 - 0
examples/panel-boilerplate-es5/plugin.json → examples/boilerplate-es5-panel/plugin.json


+ 0 - 7
examples/nginx-app/.gitignore

@@ -1,7 +0,0 @@
-.DS_Store
-
-node_modules
-tmp/*
-npm-debug.log
-dist/*
-

+ 0 - 13
examples/nginx-app/.jscs.json

@@ -1,13 +0,0 @@
-{
-    "disallowImplicitTypeConversion": ["string"],
-    "disallowKeywords": ["with"],
-    "disallowMultipleLineBreaks": true,
-    "disallowMixedSpacesAndTabs": true,
-    "disallowTrailingWhitespace": true,
-    "requireSpacesInFunctionExpression": {
-        "beforeOpeningCurlyBrace": true
-    },
-    "disallowSpacesInsideArrayBrackets": true,
-    "disallowSpacesInsideParentheses": true,
-    "validateIndentation": 2
-}

+ 0 - 36
examples/nginx-app/.jshintrc

@@ -1,36 +0,0 @@
-{
-  "browser": true,
-  "esnext": true,
-
-  "bitwise":false,
-  "curly": true,
-  "eqnull": true,
-  "devel": true,
-  "eqeqeq": true,
-  "forin": false,
-  "immed": true,
-  "supernew": true,
-  "expr": true,
-  "indent": 2,
-  "latedef": true,
-  "newcap": true,
-  "noarg": true,
-  "noempty": true,
-  "undef": true,
-  "boss": true,
-  "trailing": true,
-  "laxbreak": true,
-  "laxcomma": true,
-  "sub": true,
-  "unused": true,
-  "maxdepth": 6,
-  "maxlen": 140,
-
-  "globals": {
-    "System": true,
-    "define": true,
-    "require": true,
-    "Chromath": false,
-    "setImmediate": true
-  }
-}

+ 0 - 54
examples/nginx-app/Gruntfile.js

@@ -1,54 +0,0 @@
-module.exports = function(grunt) {
-
-  require('load-grunt-tasks')(grunt);
-
-  grunt.loadNpmTasks('grunt-execute');
-  grunt.loadNpmTasks('grunt-contrib-clean');
-
-  grunt.initConfig({
-
-    clean: ["dist"],
-
-    copy: {
-      src_to_dist: {
-        cwd: 'src',
-        expand: true,
-        src: ['**/*', '!**/*.js', '!**/*.scss'],
-        dest: 'dist'
-      },
-      pluginDef: {
-        expand: true,
-        src: ['plugin.json', 'readme.md'],
-        dest: 'dist',
-      }
-    },
-
-    watch: {
-      rebuild_all: {
-        files: ['src/**/*', 'plugin.json', 'readme.md'],
-        tasks: ['default'],
-        options: {spawn: false}
-      },
-    },
-
-    babel: {
-      options: {
-        sourceMap: true,
-        presets:  ["es2015"],
-        plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"],
-      },
-      dist: {
-        files: [{
-          cwd: 'src',
-          expand: true,
-          src: ['**/*.js'],
-          dest: 'dist',
-          ext:'.js'
-        }]
-      },
-    },
-
-  });
-
-  grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel']);
-};

+ 0 - 37
examples/nginx-app/package.json

@@ -1,37 +0,0 @@
-{
-  "name": "kentik-app",
-  "private": true,
-  "version": "1.0.0",
-  "description": "",
-  "main": "index.js",
-  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/raintank/kentik-app-poc.git"
-  },
-  "author": "",
-  "license": "ISC",
-  "bugs": {
-    "url": "https://github.com/raintank/kentik-app-poc/issues"
-  },
-  "devDependencies": {
-    "grunt": "~0.4.5",
-    "babel": "~6.5.1",
-    "grunt-babel": "~6.0.0",
-    "grunt-contrib-copy": "~0.8.2",
-    "grunt-contrib-watch": "^0.6.1",
-    "grunt-contrib-uglify": "~0.11.0",
-    "grunt-systemjs-builder": "^0.2.5",
-    "load-grunt-tasks": "~3.2.0",
-    "grunt-execute": "~0.2.2",
-    "grunt-contrib-clean": "~0.6.0"
-  },
-  "dependencies": {
-    "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0",
-    "babel-preset-es2015": "^6.5.0",
-    "lodash": "~4.0.0"
-  },
-  "homepage": "https://github.com/raintank/kentik-app-poc#readme"
-}

+ 0 - 7
examples/nginx-app/readme.md

@@ -1,7 +0,0 @@
-## Overview
-
-This application is an example app.
-
-### Awesome
-
-Even though it does not have any features it is still pretty awesome.

+ 0 - 3
examples/nginx-app/src/components/config.html

@@ -1,3 +0,0 @@
-<h3>
-	Nginx config!
-</h3>

+ 0 - 6
examples/nginx-app/src/components/config.js

@@ -1,6 +0,0 @@
-
-export class NginxAppConfigCtrl {
-}
-NginxAppConfigCtrl.templateUrl = 'components/config.html';
-
-

+ 0 - 3
examples/nginx-app/src/components/logs.html

@@ -1,3 +0,0 @@
-<h3>
-	Logs page!
-</h3>

+ 0 - 6
examples/nginx-app/src/components/logs.js

@@ -1,6 +0,0 @@
-
-export class LogsPageCtrl {
-}
-LogsPageCtrl.templateUrl = 'components/logs.html';
-
-

+ 0 - 3
examples/nginx-app/src/components/stream.html

@@ -1,3 +0,0 @@
-<h3>
-	Stream page!
-</h3>

+ 0 - 6
examples/nginx-app/src/components/stream.js

@@ -1,6 +0,0 @@
-
-export class StreamPageCtrl {
-}
-StreamPageCtrl.templateUrl = 'components/stream.html';
-
-

+ 0 - 0
examples/nginx-app/src/css/dark.css


+ 0 - 0
examples/nginx-app/src/css/light.css


+ 0 - 12
examples/nginx-app/src/datasource/datasource.js

@@ -1,12 +0,0 @@
-export default class NginxDatasource {
-
-  constructor() {}
-
-  query(options) {
-    return [];
-  }
-
-  testDatasource() {
-    return false;
-  }
-}

+ 0 - 5
examples/nginx-app/src/datasource/module.js

@@ -1,5 +0,0 @@
-import {Datasource} from  './datasource';
-
-export {
-  Datasource
-};

+ 0 - 5
examples/nginx-app/src/datasource/plugin.json

@@ -1,5 +0,0 @@
-{
-  "type": "datasource",
-  "name": "Nginx Datasource",
-  "id": "nginx-datasource"
-}

BIN
examples/nginx-app/src/img/logo_large.png


BIN
examples/nginx-app/src/img/logo_small.png


+ 0 - 9
examples/nginx-app/src/module.js

@@ -1,9 +0,0 @@
-import {LogsPageCtrl} from './components/logs';
-import {StreamPageCtrl} from './components/stream';
-import {NginxAppConfigCtrl} from './components/config';
-
-export {
-  NginxAppConfigCtrl as ConfigCtrl,
-  StreamPageCtrl,
-  LogsPageCtrl
-};

+ 0 - 15
examples/nginx-app/src/panel/module.js

@@ -1,15 +0,0 @@
-import {PanelCtrl} from  'app/plugins/sdk';
-
-class NginxPanelCtrl extends PanelCtrl {
-
-  constructor($scope, $injector) {
-    super($scope, $injector);
-  }
-
-}
-NginxPanelCtrl.template = '<h2>nginx!</h2>';
-
-export {
-  NginxPanelCtrl as PanelCtrl
-};
-

+ 0 - 5
examples/nginx-app/src/panel/plugin.json

@@ -1,5 +0,0 @@
-{
-  "type": "panel",
-  "name": "Nginx Panel",
-  "id": "nginx-panel"
-}

+ 2 - 3
pkg/api/api.go

@@ -167,11 +167,10 @@ func Register(r *macaron.Macaron) {
 			r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
 			r.Delete("/:id", DeleteDataSource)
 			r.Get("/:id", wrap(GetDataSourceById))
+			r.Get("/name/:name", wrap(GetDataSourceByName))
 		}, reqOrgAdmin)
 
-		r.Group("/datasources/name/:name", func() {
-			r.Get("/", wrap(GetDataSourceByName))
-		}, reqOrgAdmin)
+		r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
 
 		r.Group("/plugins", func() {
 			r.Get("/", wrap(GetPluginList))

+ 4 - 0
pkg/api/cloudwatch/metrics.go

@@ -55,8 +55,10 @@ func init() {
 			"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "CorruptBlocks", "TotalLoad", "MemoryTotalMB", "MemoryReservedMB", "MemoryAvailableMB", "MemoryAllocatedMB", "PendingDeletionBlocks", "UnderReplicatedBlocks", "DfsPendingReplicationBlocks", "CapacityRemainingGB",
 			"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"},
 		"AWS/ES":       {"ClusterStatus.green", "ClusterStatus.yellow", "ClusterStatus.red", "Nodes", "SearchableDocuments", "DeletedDocuments", "CPUUtilization", "FreeStorageSpace", "JVMMemoryPressure", "AutomatedSnapshotFailure", "MasterCPUUtilization", "MasterFreeStorageSpace", "MasterJVMMemoryPressure", "ReadLatency", "WriteLatency", "ReadThroughput", "WriteThroughput", "DiskQueueLength", "ReadIOPS", "WriteIOPS"},
+		"AWS/Events":   {"Invocations", "FailedInvocations", "TriggeredRules", "MatchedEvents", "ThrottledRules"},
 		"AWS/Kinesis":  {"PutRecord.Bytes", "PutRecord.Latency", "PutRecord.Success", "PutRecords.Bytes", "PutRecords.Latency", "PutRecords.Records", "PutRecords.Success", "IncomingBytes", "IncomingRecords", "GetRecords.Bytes", "GetRecords.IteratorAgeMilliseconds", "GetRecords.Latency", "GetRecords.Success"},
 		"AWS/Lambda":   {"Invocations", "Errors", "Duration", "Throttles"},
+		"AWS/Logs":     {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
 		"AWS/ML":       {"PredictCount", "PredictFailureCount"},
 		"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
 		"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "ReadIOPS", "ReadLatency", "ReadThroughput", "WriteIOPS", "WriteLatency", "WriteThroughput"},
@@ -85,8 +87,10 @@ func init() {
 		"AWS/ELB":              {"LoadBalancerName", "AvailabilityZone"},
 		"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
 		"AWS/ES":               {},
+		"AWS/Events":           {"RuleName"},
 		"AWS/Kinesis":          {"StreamName"},
 		"AWS/Lambda":           {"FunctionName"},
+		"AWS/Logs":             {"LogGroupName", "DestinationType", "FilterName"},
 		"AWS/ML":               {"MLModelId", "RequestMode"},
 		"AWS/OpsWorks":         {"StackId", "LayerId", "InstanceId"},
 		"AWS/Redshift":         {"NodeID", "ClusterIdentifier"},

+ 19 - 0
pkg/api/datasources.go

@@ -116,6 +116,25 @@ func GetDataSourceByName(c *middleware.Context) Response {
 	return Json(200, &dtos)
 }
 
+// Get /api/datasources/id/:name
+func GetDataSourceIdByName(c *middleware.Context) Response {
+	query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}
+
+	if err := bus.Dispatch(&query); err != nil {
+		if err == m.ErrDataSourceNotFound {
+			return ApiError(404, "Data source not found", nil)
+		}
+		return ApiError(500, "Failed to query datasources", err)
+	}
+
+	ds := query.Result
+	dtos := dtos.AnyId{
+		Id: ds.Id,
+	}
+
+	return Json(200, &dtos)
+}
+
 func convertModelToDtos(ds m.DataSource) dtos.DataSource {
 	return dtos.DataSource{
 		Id:                ds.Id,

+ 4 - 0
pkg/api/dtos/models.go

@@ -10,6 +10,10 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 )
 
+type AnyId struct {
+	Id int64 `json:"id"`
+}
+
 type LoginCommand struct {
 	User     string `json:"user" binding:"Required"`
 	Password string `json:"password" binding:"Required"`

+ 16 - 11
pkg/api/index.go

@@ -48,18 +48,23 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 		data.User.LightTheme = true
 	}
 
+	dashboardChildNavs := []*dtos.NavLink{
+		{Text: "Home", Url: setting.AppSubUrl + "/"},
+		{Text: "Playlists", Url: setting.AppSubUrl + "/playlists"},
+		{Text: "Snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots"},
+	}
+
+	if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
+		dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Divider: true})
+		dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Text: "New", Url: setting.AppSubUrl + "/dashboard/new"})
+		dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Text: "Import", Url: setting.AppSubUrl + "/import/dashboard"})
+	}
+
 	data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
-		Text: "Dashboards",
-		Icon: "icon-gf icon-gf-dashboard",
-		Url:  setting.AppSubUrl + "/",
-		Children: []*dtos.NavLink{
-			{Text: "Home", Url: setting.AppSubUrl + "/"},
-			{Text: "Playlists", Url: setting.AppSubUrl + "/playlists"},
-			{Text: "Snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots"},
-			{Divider: true},
-			{Text: "New", Url: setting.AppSubUrl + "/dashboard/new"},
-			{Text: "Import", Url: setting.AppSubUrl + "/import/dashboard"},
-		},
+		Text:     "Dashboards",
+		Icon:     "icon-gf icon-gf-dashboard",
+		Url:      setting.AppSubUrl + "/",
+		Children: dashboardChildNavs,
 	})
 
 	if c.OrgRole == m.ROLE_ADMIN {

+ 1 - 0
pkg/api/render.go

@@ -31,6 +31,7 @@ func RenderToPng(c *middleware.Context) {
 		Width:     queryReader.Get("width", "800"),
 		Height:    queryReader.Get("height", "400"),
 		SessionId: c.Session.ID(),
+		Timeout:   queryReader.Get("timeout", "15"),
 	}
 
 	renderOpts.Url = setting.ToAbsUrl(renderOpts.Url)

+ 12 - 10
pkg/cmd/grafana-cli/commands/install_command.go

@@ -4,6 +4,7 @@ import (
 	"archive/zip"
 	"bytes"
 	"errors"
+	"fmt"
 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
 	m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
 	s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
@@ -64,32 +65,33 @@ func InstallPlugin(pluginName, version string, c CommandLine) error {
 		return err
 	}
 
-	url := v.Url
-	commit := v.Commit
-
 	if version == "" {
 		version = v.Version
 	}
 
-	downloadURL := url + "/archive/" + commit + ".zip"
+	downloadURL := fmt.Sprintf("%s/%s/versions/%s/download",
+		c.GlobalString("repo"),
+		pluginName,
+		version)
 
 	log.Infof("installing %v @ %v\n", plugin.Id, version)
 	log.Infof("from url: %v\n", downloadURL)
-	log.Infof("on commit: %v\n", commit)
 	log.Infof("into: %v\n", pluginFolder)
 
 	err = downloadFile(plugin.Id, pluginFolder, downloadURL)
-	if err == nil {
-		log.Infof("Installed %v successfully ✔\n", plugin.Id)
+	if err != nil {
+		return err
 	}
 
-	res, _ := s.ReadPlugin(pluginFolder, pluginName)
+	log.Infof("Installed %v successfully ✔\n", plugin.Id)
 
+	/* Enable once we need support for downloading depedencies
+	res, _ := s.ReadPlugin(pluginFolder, pluginName)
 	for _, v := range res.Dependency.Plugins {
 		InstallPlugin(v.Id, version, c)
-		log.Infof("Installed Dependency: %v ✔\n", v.Id)
+		log.Infof("Installed dependency: %v ✔\n", v.Id)
 	}
-
+	*/
 	return err
 }
 

+ 2 - 2
pkg/cmd/grafana-cli/commands/ls_command.go

@@ -10,7 +10,7 @@ import (
 
 var ls_getPlugins func(path string) []m.InstalledPlugin = s.GetLocalPlugins
 
-var validateLsCommmand = func(pluginDir string) error {
+var validateLsCommand = func(pluginDir string) error {
 	if pluginDir == "" {
 		return errors.New("missing path flag")
 	}
@@ -31,7 +31,7 @@ var validateLsCommmand = func(pluginDir string) error {
 
 func lsCommand(c CommandLine) error {
 	pluginDir := c.GlobalString("path")
-	if err := validateLsCommmand(pluginDir); err != nil {
+	if err := validateLsCommand(pluginDir); err != nil {
 		return err
 	}
 

+ 3 - 3
pkg/cmd/grafana-cli/commands/ls_command_test.go

@@ -9,10 +9,10 @@ import (
 )
 
 func TestMissingPath(t *testing.T) {
-	var org = validateLsCommmand
+	var org = validateLsCommand
 
 	Convey("ls command", t, func() {
-		validateLsCommmand = org
+		validateLsCommand = org
 
 		Convey("Missing path", func() {
 			commandLine := &commandstest.FakeCommandLine{
@@ -61,7 +61,7 @@ func TestMissingPath(t *testing.T) {
 				},
 			}
 
-			validateLsCommmand = func(pluginDir string) error {
+			validateLsCommand = func(pluginDir string) error {
 				return errors.New("dummie error")
 			}
 

+ 1 - 1
pkg/cmd/grafana-cli/main.go

@@ -41,7 +41,7 @@ func main() {
 		cli.StringFlag{
 			Name:  "repo",
 			Usage: "url to the plugin repository",
-			Value: "https://raw.githubusercontent.com/grafana/grafana-plugin-repository/master/repo.json",
+			Value: "",
 		},
 		cli.BoolFlag{
 			Name:  "debug, d",

+ 2 - 1
pkg/cmd/grafana-cli/services/services.go

@@ -12,7 +12,8 @@ import (
 var IoHelper m.IoUtil = IoUtilImp{}
 
 func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
-	res, _ := goreq.Request{Uri: repoUrl}.Do()
+
+	res, _ := goreq.Request{Uri: repoUrl + "/repo", MaxRedirects: 3}.Do()
 
 	var resp m.PluginRepo
 	err := res.Body.FromJsonTo(&resp)

+ 8 - 1
pkg/components/renderer/renderer.go

@@ -11,6 +11,7 @@ import (
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
+	"strconv"
 )
 
 type RenderOpts struct {
@@ -18,6 +19,7 @@ type RenderOpts struct {
 	Width     string
 	Height    string
 	SessionId string
+	Timeout   string
 }
 
 func RenderToPng(params *RenderOpts) (string, error) {
@@ -60,8 +62,13 @@ func RenderToPng(params *RenderOpts) (string, error) {
 		close(done)
 	}()
 
+	timeout, err := strconv.Atoi(params.Timeout)
+	if err != nil {
+		timeout = 15
+	}
+
 	select {
-	case <-time.After(15 * time.Second):
+	case <-time.After(time.Duration(timeout) * time.Second):
 		if err := cmd.Process.Kill(); err != nil {
 			log.Error(4, "failed to kill: %v", err)
 		}

+ 4 - 4
pkg/plugins/dashboards_test.go

@@ -14,8 +14,8 @@ func TestPluginDashboards(t *testing.T) {
 
 	Convey("When asking plugin dashboard info", t, func() {
 		setting.Cfg = ini.Empty()
-		sec, _ := setting.Cfg.NewSection("plugin.nginx-app")
-		sec.NewKey("path", "../../examples/nginx-app")
+		sec, _ := setting.Cfg.NewSection("plugin.test-app")
+		sec.NewKey("path", "../../tests/test-app")
 		err := Init()
 
 		So(err, ShouldBeNil)
@@ -31,7 +31,7 @@ func TestPluginDashboards(t *testing.T) {
 			return m.ErrDashboardNotFound
 		})
 
-		dashboards, err := GetPluginDashboards(1, "nginx-app")
+		dashboards, err := GetPluginDashboards(1, "test-app")
 
 		So(err, ShouldBeNil)
 
@@ -43,7 +43,7 @@ func TestPluginDashboards(t *testing.T) {
 			So(dashboards[0].Title, ShouldEqual, "Nginx Connections")
 			So(dashboards[0].Revision, ShouldEqual, "1.5")
 			So(dashboards[0].InstalledRevision, ShouldEqual, "1.1")
-			So(dashboards[0].InstalledURI, ShouldEqual, "db/nginx-connections")
+			So(dashboards[0].InstalledUri, ShouldEqual, "db/nginx-connections")
 
 			So(dashboards[1].Revision, ShouldEqual, "2.0")
 			So(dashboards[1].InstalledRevision, ShouldEqual, "")

+ 3 - 3
pkg/plugins/plugins_test.go

@@ -28,14 +28,14 @@ func TestPluginScans(t *testing.T) {
 	Convey("When reading app plugin definition", t, func() {
 		setting.Cfg = ini.Empty()
 		sec, _ := setting.Cfg.NewSection("plugin.nginx-app")
-		sec.NewKey("path", "../../examples/nginx-app")
+		sec.NewKey("path", "../../tests/test-app")
 		err := Init()
 
 		So(err, ShouldBeNil)
 		So(len(Apps), ShouldBeGreaterThan, 0)
 
-		So(Apps["nginx-app"].Info.Logos.Large, ShouldEqual, "public/plugins/nginx-app/img/logo_large.png")
-		So(Apps["nginx-app"].Info.Screenshots[1].Path, ShouldEqual, "public/plugins/nginx-app/img/screenshot2.png")
+		So(Apps["test-app"].Info.Logos.Large, ShouldEqual, "public/plugins/test-app/img/logo_large.png")
+		So(Apps["test-app"].Info.Screenshots[1].Path, ShouldEqual, "public/plugins/test-app/img/screenshot2.png")
 	})
 
 }

+ 1 - 1
public/app/features/templating/templateValuesSrv.js

@@ -74,7 +74,7 @@ function (angular, _, kbn) {
         if (urlValue !== void 0) {
           return self.setVariableFromUrl(variable, urlValue).then(lock.resolve);
         }
-        else if (variable.refresh === 'On Dashboard Load' || variable.refresh === 'On Time Change and Dashboard Load') {
+        else if (variable.refresh === 1 || variable.refresh === 2) {
           return self.updateOptions(variable).then(function() {
             if (_.isEmpty(variable.current) && variable.options.length) {
               console.log("setting current for %s", variable.name);

+ 23 - 1
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -143,7 +143,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
       return this.awsRequest({
         region: region,
         action: 'DescribeInstances',
-        parameters: { filter: filters, instanceIds: instanceIds }
+        parameters: { filters: filters, instanceIds: instanceIds }
       });
     };
 
@@ -205,6 +205,28 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
         });
       }
 
+      var ec2InstanceAttributeQuery = query.match(/^ec2_instance_attribute\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/);
+      if (ec2InstanceAttributeQuery) {
+        region = templateSrv.replace(ec2InstanceAttributeQuery[1]);
+        var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3]));
+        var filters = _.map(filterJson, function(values, name) {
+          return {
+            Name: name,
+            Values: values
+          };
+        });
+        var targetAttributeName = templateSrv.replace(ec2InstanceAttributeQuery[2]);
+
+        return this.performEC2DescribeInstances(region, filters, null).then(function(result) {
+          var attributes = _.chain(result.Reservations)
+          .map(function(reservations) {
+            return _.pluck(reservations.Instances, targetAttributeName);
+          })
+          .flatten().value();
+          return transformSuggestData(attributes);
+        });
+      }
+
       return $q.when([]);
     };
 

+ 1 - 1
public/app/plugins/datasource/influxdb/datasource.ts

@@ -104,7 +104,7 @@ export function InfluxDatasource(instanceSettings, $q, backendSrv, templateSrv)
   this.metricFindQuery = function (query) {
     var interpolated;
     try {
-      interpolated = templateSrv.replace(query);
+      interpolated = templateSrv.replace(query, null, 'regex');
     } catch (err) {
       return $q.reject(err);
     }

+ 1 - 1
public/dashboards/template_vars.json

@@ -258,6 +258,6 @@
   "annotations": {
     "enable": false
   },
-  "refresh": "Never",
+  "refresh": 0,
   "version": 6
 }

+ 0 - 0
examples/nginx-app/src/dashboards/connections.json → tests/test-app/dashboards/connections.json


+ 0 - 0
examples/nginx-app/src/dashboards/memory.json → tests/test-app/dashboards/memory.json


+ 6 - 6
examples/nginx-app/plugin.json → tests/test-app/plugin.json

@@ -1,7 +1,7 @@
 {
   "type": "app",
-  "name": "Nginx",
-  "id": "nginx-app",
+  "name": "Test App",
+  "id": "test-app",
 
   "staticRoot": ".",
 
@@ -16,12 +16,12 @@
   },
 
   "info": {
-    "description": "Official Grafana Nginx App & Dashboard bundle",
+    "description": "Official Grafana Test App & Dashboard bundle",
     "author": {
-      "name": "Nginx Inc.",
-      "url": "http://nginx.com"
+      "name": "Test Inc.",
+      "url": "http://test.com"
     },
-    "keywords": ["nginx"],
+    "keywords": ["test"],
     "logos": {
       "small": "img/logo_small.png",
       "large": "img/logo_large.png"