diff --git a/docs/sources/datasources/cloudwatch.md b/docs/sources/datasources/cloudwatch.md index 4d9d8b38c1c..351b08eb2aa 100644 --- a/docs/sources/datasources/cloudwatch.md +++ b/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 diff --git a/docs/sources/http_api/data_source.md b/docs/sources/http_api/data_source.md index f60c9cd7000..2a3c20e9af8 100644 --- a/docs/sources/http_api/data_source.md +++ b/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` diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index b652abcabb6..cdc24dc7c59 100644 --- a/docs/sources/installation/configuration.md +++ b/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 diff --git a/docs/sources/installation/ldap.md b/docs/sources/installation/ldap.md index 82309ec0f19..a5311fb4fa5 100644 --- a/docs/sources/installation/ldap.md +++ b/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. diff --git a/examples/README.md b/examples/README.md index 2e8a46f2aa3..64341a7fdbc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,4 @@ ## Example plugin implementations -[datasource-plugin-genericdatsource](https://github.com/grafana/datasource-plugin-genericdatasource/tree/3.0) \ No newline at end of file +datasource:[simple-json-datasource](https://github.com/grafana/simple-json-datasource) +app: [example-app](https://github.com/grafana/example-app) \ No newline at end of file diff --git a/examples/panel-boilerplate-es5/css/styles.css b/examples/boilerplate-es5-panel/css/styles.css similarity index 100% rename from examples/panel-boilerplate-es5/css/styles.css rename to examples/boilerplate-es5-panel/css/styles.css diff --git a/examples/panel-boilerplate-es5/module.js b/examples/boilerplate-es5-panel/module.js similarity index 100% rename from examples/panel-boilerplate-es5/module.js rename to examples/boilerplate-es5-panel/module.js diff --git a/examples/panel-boilerplate-es5/panel.html b/examples/boilerplate-es5-panel/panel.html similarity index 100% rename from examples/panel-boilerplate-es5/panel.html rename to examples/boilerplate-es5-panel/panel.html diff --git a/examples/panel-boilerplate-es5/plugin.json b/examples/boilerplate-es5-panel/plugin.json similarity index 100% rename from examples/panel-boilerplate-es5/plugin.json rename to examples/boilerplate-es5-panel/plugin.json diff --git a/examples/nginx-app/.gitignore b/examples/nginx-app/.gitignore deleted file mode 100644 index 8c2c350441b..00000000000 --- a/examples/nginx-app/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.DS_Store - -node_modules -tmp/* -npm-debug.log -dist/* - diff --git a/examples/nginx-app/.jscs.json b/examples/nginx-app/.jscs.json deleted file mode 100644 index dcf694dcc63..00000000000 --- a/examples/nginx-app/.jscs.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "disallowImplicitTypeConversion": ["string"], - "disallowKeywords": ["with"], - "disallowMultipleLineBreaks": true, - "disallowMixedSpacesAndTabs": true, - "disallowTrailingWhitespace": true, - "requireSpacesInFunctionExpression": { - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInsideArrayBrackets": true, - "disallowSpacesInsideParentheses": true, - "validateIndentation": 2 -} \ No newline at end of file diff --git a/examples/nginx-app/.jshintrc b/examples/nginx-app/.jshintrc deleted file mode 100644 index 3725af83afc..00000000000 --- a/examples/nginx-app/.jshintrc +++ /dev/null @@ -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 - } -} diff --git a/examples/nginx-app/Gruntfile.js b/examples/nginx-app/Gruntfile.js deleted file mode 100644 index b88d857cde3..00000000000 --- a/examples/nginx-app/Gruntfile.js +++ /dev/null @@ -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']); -}; diff --git a/examples/nginx-app/package.json b/examples/nginx-app/package.json deleted file mode 100644 index 91c53734ec8..00000000000 --- a/examples/nginx-app/package.json +++ /dev/null @@ -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" -} diff --git a/examples/nginx-app/readme.md b/examples/nginx-app/readme.md deleted file mode 100644 index c267cfbb1fc..00000000000 --- a/examples/nginx-app/readme.md +++ /dev/null @@ -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. diff --git a/examples/nginx-app/src/components/config.html b/examples/nginx-app/src/components/config.html deleted file mode 100644 index c531ec36d76..00000000000 --- a/examples/nginx-app/src/components/config.html +++ /dev/null @@ -1,3 +0,0 @@ -

- Nginx config! -

diff --git a/examples/nginx-app/src/components/config.js b/examples/nginx-app/src/components/config.js deleted file mode 100644 index bb8f007b9bc..00000000000 --- a/examples/nginx-app/src/components/config.js +++ /dev/null @@ -1,6 +0,0 @@ - -export class NginxAppConfigCtrl { -} -NginxAppConfigCtrl.templateUrl = 'components/config.html'; - - diff --git a/examples/nginx-app/src/components/logs.html b/examples/nginx-app/src/components/logs.html deleted file mode 100644 index ca215772bf5..00000000000 --- a/examples/nginx-app/src/components/logs.html +++ /dev/null @@ -1,3 +0,0 @@ -

- Logs page! -

diff --git a/examples/nginx-app/src/components/logs.js b/examples/nginx-app/src/components/logs.js deleted file mode 100644 index 5b67290381b..00000000000 --- a/examples/nginx-app/src/components/logs.js +++ /dev/null @@ -1,6 +0,0 @@ - -export class LogsPageCtrl { -} -LogsPageCtrl.templateUrl = 'components/logs.html'; - - diff --git a/examples/nginx-app/src/components/stream.html b/examples/nginx-app/src/components/stream.html deleted file mode 100644 index ad70ca4df50..00000000000 --- a/examples/nginx-app/src/components/stream.html +++ /dev/null @@ -1,3 +0,0 @@ -

- Stream page! -

diff --git a/examples/nginx-app/src/components/stream.js b/examples/nginx-app/src/components/stream.js deleted file mode 100644 index 8684b36c64d..00000000000 --- a/examples/nginx-app/src/components/stream.js +++ /dev/null @@ -1,6 +0,0 @@ - -export class StreamPageCtrl { -} -StreamPageCtrl.templateUrl = 'components/stream.html'; - - diff --git a/examples/nginx-app/src/css/dark.css b/examples/nginx-app/src/css/dark.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/nginx-app/src/css/light.css b/examples/nginx-app/src/css/light.css deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/nginx-app/src/datasource/datasource.js b/examples/nginx-app/src/datasource/datasource.js deleted file mode 100644 index 7f4ed363707..00000000000 --- a/examples/nginx-app/src/datasource/datasource.js +++ /dev/null @@ -1,12 +0,0 @@ -export default class NginxDatasource { - - constructor() {} - - query(options) { - return []; - } - - testDatasource() { - return false; - } -} diff --git a/examples/nginx-app/src/datasource/module.js b/examples/nginx-app/src/datasource/module.js deleted file mode 100644 index a3473e59889..00000000000 --- a/examples/nginx-app/src/datasource/module.js +++ /dev/null @@ -1,5 +0,0 @@ -import {Datasource} from './datasource'; - -export { - Datasource -}; \ No newline at end of file diff --git a/examples/nginx-app/src/datasource/plugin.json b/examples/nginx-app/src/datasource/plugin.json deleted file mode 100644 index ffce9492418..00000000000 --- a/examples/nginx-app/src/datasource/plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "datasource", - "name": "Nginx Datasource", - "id": "nginx-datasource" -} diff --git a/examples/nginx-app/src/img/logo_large.png b/examples/nginx-app/src/img/logo_large.png deleted file mode 100644 index c28955960e4..00000000000 Binary files a/examples/nginx-app/src/img/logo_large.png and /dev/null differ diff --git a/examples/nginx-app/src/img/logo_small.png b/examples/nginx-app/src/img/logo_small.png deleted file mode 100644 index a6040f66f3d..00000000000 Binary files a/examples/nginx-app/src/img/logo_small.png and /dev/null differ diff --git a/examples/nginx-app/src/module.js b/examples/nginx-app/src/module.js deleted file mode 100644 index b5aeecc6ccf..00000000000 --- a/examples/nginx-app/src/module.js +++ /dev/null @@ -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 -}; diff --git a/examples/nginx-app/src/panel/module.js b/examples/nginx-app/src/panel/module.js deleted file mode 100644 index 899586da81b..00000000000 --- a/examples/nginx-app/src/panel/module.js +++ /dev/null @@ -1,15 +0,0 @@ -import {PanelCtrl} from 'app/plugins/sdk'; - -class NginxPanelCtrl extends PanelCtrl { - - constructor($scope, $injector) { - super($scope, $injector); - } - -} -NginxPanelCtrl.template = '

nginx!

'; - -export { - NginxPanelCtrl as PanelCtrl -}; - diff --git a/examples/nginx-app/src/panel/plugin.json b/examples/nginx-app/src/panel/plugin.json deleted file mode 100644 index f3548c987f3..00000000000 --- a/examples/nginx-app/src/panel/plugin.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "panel", - "name": "Nginx Panel", - "id": "nginx-panel" -} diff --git a/pkg/api/api.go b/pkg/api/api.go index 44b56ea62f6..d4bb22149f8 100644 --- a/pkg/api/api.go +++ b/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)) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index a4e97cdb347..54ab565bede 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/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"}, diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index a156d10f098..0982d73e88e 100644 --- a/pkg/api/datasources.go +++ b/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, diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index b810701233e..36e51fdc28a 100644 --- a/pkg/api/dtos/models.go +++ b/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"` diff --git a/pkg/api/index.go b/pkg/api/index.go index 7cd2842b050..691c50f04f4 100644 --- a/pkg/api/index.go +++ b/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 { diff --git a/pkg/api/render.go b/pkg/api/render.go index 728128acaab..9ed0c5ee6d7 100644 --- a/pkg/api/render.go +++ b/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) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index c58ed12668f..4dfbfd80e31 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/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 } diff --git a/pkg/cmd/grafana-cli/commands/ls_command.go b/pkg/cmd/grafana-cli/commands/ls_command.go index 5a90df89d8a..05dd57bfd2a 100644 --- a/pkg/cmd/grafana-cli/commands/ls_command.go +++ b/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 } diff --git a/pkg/cmd/grafana-cli/commands/ls_command_test.go b/pkg/cmd/grafana-cli/commands/ls_command_test.go index fa49375234d..650c6270bf0 100644 --- a/pkg/cmd/grafana-cli/commands/ls_command_test.go +++ b/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") } diff --git a/pkg/cmd/grafana-cli/main.go b/pkg/cmd/grafana-cli/main.go index 12bebd46898..4e74578c604 100644 --- a/pkg/cmd/grafana-cli/main.go +++ b/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", diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go index c23c716b62f..fc77570b77c 100644 --- a/pkg/cmd/grafana-cli/services/services.go +++ b/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) diff --git a/pkg/components/renderer/renderer.go b/pkg/components/renderer/renderer.go index d72ceca9c3d..f81da43c295 100644 --- a/pkg/components/renderer/renderer.go +++ b/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) } diff --git a/pkg/plugins/dashboards_test.go b/pkg/plugins/dashboards_test.go index b8ab51940a1..58cbe2f4920 100644 --- a/pkg/plugins/dashboards_test.go +++ b/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, "") diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index b7f4b845fb5..f2dbc1e2e82 100644 --- a/pkg/plugins/plugins_test.go +++ b/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") }) } diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 4ccbefefdef..ce26a35be9b 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/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); diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 06c723e416a..704cbd41e72 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/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([]); }; diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 84c7a83394c..1136c9709bd 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/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); } diff --git a/public/dashboards/template_vars.json b/public/dashboards/template_vars.json index 53dd5e3cfbf..8ca81fbdff5 100644 --- a/public/dashboards/template_vars.json +++ b/public/dashboards/template_vars.json @@ -258,6 +258,6 @@ "annotations": { "enable": false }, - "refresh": "Never", + "refresh": 0, "version": 6 } diff --git a/examples/nginx-app/src/dashboards/connections.json b/tests/test-app/dashboards/connections.json similarity index 100% rename from examples/nginx-app/src/dashboards/connections.json rename to tests/test-app/dashboards/connections.json diff --git a/examples/nginx-app/src/dashboards/memory.json b/tests/test-app/dashboards/memory.json similarity index 100% rename from examples/nginx-app/src/dashboards/memory.json rename to tests/test-app/dashboards/memory.json diff --git a/examples/nginx-app/plugin.json b/tests/test-app/plugin.json similarity index 87% rename from examples/nginx-app/plugin.json rename to tests/test-app/plugin.json index 6de4fbd4daa..cc82141690c 100644 --- a/examples/nginx-app/plugin.json +++ b/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"