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" ] })
+

## Cost
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 32c070f96b0..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/dashboards/dashboard.js b/examples/nginx-app/src/dashboards/dashboard.js
deleted file mode 100644
index 794e2c5217b..00000000000
--- a/examples/nginx-app/src/dashboards/dashboard.js
+++ /dev/null
@@ -1,17 +0,0 @@
-require([
-], function () {
-
- function Dashboard() {
-
- this.getInputs = function() {
-
- };
-
- this.buildDashboard = function() {
-
- };
- }
-
- return Dashboard;
-});
-
diff --git a/examples/nginx-app/src/dashboards/nginx_connection_stats.json b/examples/nginx-app/src/dashboards/nginx_connection_stats.json
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 793703153e1..d4bb22149f8 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -126,10 +126,6 @@ func Register(r *macaron.Macaron) {
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
- // apps
- r.Get("/plugins", wrap(GetPluginList))
- r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
- r.Post("/plugins/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
}, reqOrgAdmin)
// create new org
@@ -176,6 +172,16 @@ func Register(r *macaron.Macaron) {
r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
+ r.Group("/plugins", func() {
+ r.Get("/", wrap(GetPluginList))
+
+ r.Get("/dashboards/:pluginId", wrap(GetPluginDashboards))
+ r.Post("/dashboards/install", bind(dtos.InstallPluginDashboardCmd{}), wrap(InstallPluginDashboard))
+
+ r.Get("/:pluginId/settings", wrap(GetPluginSettingById))
+ r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
+ }, reqOrgAdmin)
+
r.Get("/frontend/settings/", GetFrontendSettings)
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)
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/dtos/plugins.go b/pkg/api/dtos/plugins.go
index 61b149d73e4..5f3dac76296 100644
--- a/pkg/api/dtos/plugins.go
+++ b/pkg/api/dtos/plugins.go
@@ -25,3 +25,10 @@ type PluginListItem struct {
Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"`
}
+
+type InstallPluginDashboardCmd struct {
+ PluginId string `json:"pluginId"`
+ Path string `json:"path"`
+ Reinstall bool `json:"reinstall"`
+ Inputs map[string]interface{} `json:"inputs"`
+}
diff --git a/pkg/api/plugin_setting.go b/pkg/api/plugins.go
similarity index 77%
rename from pkg/api/plugin_setting.go
rename to pkg/api/plugins.go
index 1ceecdfac18..1ba0dde6c9f 100644
--- a/pkg/api/plugin_setting.go
+++ b/pkg/api/plugins.go
@@ -107,3 +107,34 @@ func UpdatePluginSetting(c *middleware.Context, cmd m.UpdatePluginSettingCmd) Re
return ApiSuccess("Plugin settings updated")
}
+
+func GetPluginDashboards(c *middleware.Context) Response {
+ pluginId := c.Params(":pluginId")
+
+ if list, err := plugins.GetPluginDashboards(c.OrgId, pluginId); err != nil {
+ if notfound, ok := err.(plugins.PluginNotFoundError); ok {
+ return ApiError(404, notfound.Error(), nil)
+ }
+
+ return ApiError(500, "Failed to get plugin dashboards", err)
+ } else {
+ return Json(200, list)
+ }
+}
+
+func InstallPluginDashboard(c *middleware.Context, apiCmd dtos.InstallPluginDashboardCmd) Response {
+
+ cmd := plugins.InstallPluginDashboardCommand{
+ OrgId: c.OrgId,
+ UserId: c.UserId,
+ PluginId: apiCmd.PluginId,
+ Path: apiCmd.Path,
+ Inputs: apiCmd.Inputs,
+ }
+
+ if err := bus.Dispatch(&cmd); err != nil {
+ return ApiError(500, "Failed to install dashboard", err)
+ }
+
+ return Json(200, cmd.Result)
+}
diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go
index 4993d8aa045..f1b36c90ef2 100644
--- a/pkg/cmd/grafana-cli/commands/commands.go
+++ b/pkg/cmd/grafana-cli/commands/commands.go
@@ -11,6 +11,7 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
cmd := &contextCommandLine{context}
if err := command(cmd); err != nil {
+ log.Error("\nError: ")
log.Errorf("%s\n\n", err)
cmd.ShowHelp()
diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go
index f98ff7b8a94..7ed93e825d3 100644
--- a/pkg/cmd/grafana-cli/commands/install_command.go
+++ b/pkg/cmd/grafana-cli/commands/install_command.go
@@ -29,7 +29,15 @@ func validateInput(c CommandLine, pluginFolder string) error {
}
fileInfo, err := os.Stat(pluginDir)
- if err != nil && !fileInfo.IsDir() {
+ if err != nil {
+ if err = os.MkdirAll(pluginDir, os.ModePerm); err != nil {
+ return errors.New("path is not a directory")
+ }
+
+ return nil
+ }
+
+ if !fileInfo.IsDir() {
return errors.New("path is not a directory")
}
diff --git a/pkg/cmd/grafana-cli/main.go b/pkg/cmd/grafana-cli/main.go
index 4e74578c604..b277714fe9b 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: "",
+ Value: "https://grafana-net.raintank.io/api/plugins",
},
cli.BoolFlag{
Name: "debug, d",
diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go
index fc77570b77c..cd03f755075 100644
--- a/pkg/cmd/grafana-cli/services/services.go
+++ b/pkg/cmd/grafana-cli/services/services.go
@@ -3,6 +3,7 @@ package services
import (
"encoding/json"
"errors"
+ "fmt"
"github.com/franela/goreq"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
@@ -12,8 +13,12 @@ import (
var IoHelper m.IoUtil = IoUtilImp{}
func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
+ fullUrl := repoUrl + "/repo"
+ res, _ := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do()
- res, _ := goreq.Request{Uri: repoUrl + "/repo", MaxRedirects: 3}.Do()
+ if res.StatusCode != 200 {
+ return m.PluginRepo{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode)
+ }
var resp m.PluginRepo
err := res.Body.FromJsonTo(&resp)
diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go
index 1015dfbe28c..3e87f504a77 100644
--- a/pkg/models/dashboards.go
+++ b/pkg/models/dashboards.go
@@ -102,8 +102,11 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
}
// GetString a
-func (dash *Dashboard) GetString(prop string) string {
- return dash.Data[prop].(string)
+func (dash *Dashboard) GetString(prop string, defaultValue string) string {
+ if val, exists := dash.Data[prop]; exists {
+ return val.(string)
+ }
+ return defaultValue
}
// UpdateSlug updates the slug
diff --git a/pkg/models/plugin_setting.go b/pkg/models/plugin_settings.go
similarity index 100%
rename from pkg/models/plugin_setting.go
rename to pkg/models/plugin_settings.go
diff --git a/pkg/plugins/dashboard_installer.go b/pkg/plugins/dashboard_installer.go
new file mode 100644
index 00000000000..279ab209989
--- /dev/null
+++ b/pkg/plugins/dashboard_installer.go
@@ -0,0 +1,57 @@
+package plugins
+
+import (
+ "github.com/grafana/grafana/pkg/bus"
+ m "github.com/grafana/grafana/pkg/models"
+)
+
+type InstallPluginDashboardCommand struct {
+ Path string `json:"string"`
+ Inputs map[string]interface{} `json:"inputs"`
+
+ OrgId int64 `json:"-"`
+ UserId int64 `json:"-"`
+ PluginId string `json:"-"`
+ Result *PluginDashboardInfoDTO
+}
+
+func init() {
+ bus.AddHandler("plugins", InstallPluginDashboard)
+}
+
+func InstallPluginDashboard(cmd *InstallPluginDashboardCommand) error {
+ plugin, exists := Plugins[cmd.PluginId]
+
+ if !exists {
+ return PluginNotFoundError{cmd.PluginId}
+ }
+
+ var dashboard *m.Dashboard
+ var err error
+
+ if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil {
+ return err
+ }
+
+ saveCmd := m.SaveDashboardCommand{
+ Dashboard: dashboard.Data,
+ OrgId: cmd.OrgId,
+ UserId: cmd.UserId,
+ }
+
+ if err := bus.Dispatch(&saveCmd); err != nil {
+ return err
+ }
+
+ cmd.Result = &PluginDashboardInfoDTO{
+ PluginId: cmd.PluginId,
+ Title: dashboard.Title,
+ Path: cmd.Path,
+ Revision: dashboard.GetString("revision", "1.0"),
+ InstalledUri: "db/" + saveCmd.Result.Slug,
+ InstalledRevision: dashboard.GetString("revision", "1.0"),
+ Installed: true,
+ }
+
+ return nil
+}
diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go
new file mode 100644
index 00000000000..1697ad808f2
--- /dev/null
+++ b/pkg/plugins/dashboards.go
@@ -0,0 +1,93 @@
+package plugins
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+
+ "github.com/grafana/grafana/pkg/bus"
+ m "github.com/grafana/grafana/pkg/models"
+)
+
+type PluginDashboardInfoDTO struct {
+ PluginId string `json:"pluginId"`
+ Title string `json:"title"`
+ Installed bool `json:"installed"`
+ InstalledUri string `json:"installedUri"`
+ InstalledRevision string `json:"installedRevision"`
+ Revision string `json:"revision"`
+ Description string `json:"description"`
+ Path string `json:"path"`
+}
+
+func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDTO, error) {
+ plugin, exists := Plugins[pluginId]
+
+ if !exists {
+ return nil, PluginNotFoundError{pluginId}
+ }
+
+ result := make([]*PluginDashboardInfoDTO, 0)
+
+ for _, include := range plugin.Includes {
+ if include.Type == PluginTypeDashboard {
+ if dashInfo, err := getDashboardImportStatus(orgId, plugin, include.Path); err != nil {
+ return nil, err
+ } else {
+ result = append(result, dashInfo)
+ }
+ }
+ }
+
+ return result, nil
+}
+
+func loadPluginDashboard(plugin *PluginBase, path string) (*m.Dashboard, error) {
+
+ dashboardFilePath := filepath.Join(plugin.PluginDir, path)
+ reader, err := os.Open(dashboardFilePath)
+ if err != nil {
+ return nil, err
+ }
+
+ defer reader.Close()
+
+ jsonParser := json.NewDecoder(reader)
+ var data map[string]interface{}
+
+ if err := jsonParser.Decode(&data); err != nil {
+ return nil, err
+ }
+
+ return m.NewDashboardFromJson(data), nil
+}
+
+func getDashboardImportStatus(orgId int64, plugin *PluginBase, path string) (*PluginDashboardInfoDTO, error) {
+ res := &PluginDashboardInfoDTO{}
+
+ var dashboard *m.Dashboard
+ var err error
+
+ if dashboard, err = loadPluginDashboard(plugin, path); err != nil {
+ return nil, err
+ }
+
+ res.Path = path
+ res.PluginId = plugin.Id
+ res.Title = dashboard.Title
+ res.Revision = dashboard.GetString("revision", "1.0")
+
+ query := m.GetDashboardQuery{OrgId: orgId, Slug: dashboard.Slug}
+
+ if err := bus.Dispatch(&query); err != nil {
+ if err != m.ErrDashboardNotFound {
+ return nil, err
+ }
+ } else {
+ res.Installed = true
+ res.InstalledUri = "db/" + query.Result.Slug
+ res.InstalledRevision = query.Result.GetString("revision", "1.0")
+ }
+
+ return res, nil
+}
diff --git a/pkg/plugins/dashboards_test.go b/pkg/plugins/dashboards_test.go
new file mode 100644
index 00000000000..58cbe2f4920
--- /dev/null
+++ b/pkg/plugins/dashboards_test.go
@@ -0,0 +1,53 @@
+package plugins
+
+import (
+ "testing"
+
+ "github.com/grafana/grafana/pkg/bus"
+ m "github.com/grafana/grafana/pkg/models"
+ "github.com/grafana/grafana/pkg/setting"
+ . "github.com/smartystreets/goconvey/convey"
+ "gopkg.in/ini.v1"
+)
+
+func TestPluginDashboards(t *testing.T) {
+
+ Convey("When asking plugin dashboard info", t, func() {
+ setting.Cfg = ini.Empty()
+ sec, _ := setting.Cfg.NewSection("plugin.test-app")
+ sec.NewKey("path", "../../tests/test-app")
+ err := Init()
+
+ So(err, ShouldBeNil)
+
+ bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
+ if query.Slug == "nginx-connections" {
+ dash := m.NewDashboard("Nginx Connections")
+ dash.Data["revision"] = "1.1"
+ query.Result = dash
+ return nil
+ }
+
+ return m.ErrDashboardNotFound
+ })
+
+ dashboards, err := GetPluginDashboards(1, "test-app")
+
+ So(err, ShouldBeNil)
+
+ Convey("should return 2 dashboarrd", func() {
+ So(len(dashboards), ShouldEqual, 2)
+ })
+
+ Convey("should include installed version info", func() {
+ 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[1].Revision, ShouldEqual, "2.0")
+ So(dashboards[1].InstalledRevision, ShouldEqual, "")
+ })
+ })
+
+}
diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go
index 0addaac9bb7..502c355eda8 100644
--- a/pkg/plugins/models.go
+++ b/pkg/plugins/models.go
@@ -3,12 +3,28 @@ package plugins
import (
"encoding/json"
"errors"
+ "fmt"
"strings"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
+var (
+ PluginTypeApp = "app"
+ PluginTypeDatasource = "datasource"
+ PluginTypePanel = "panel"
+ PluginTypeDashboard = "dashboard"
+)
+
+type PluginNotFoundError struct {
+ PluginId string
+}
+
+func (e PluginNotFoundError) Error() string {
+ return fmt.Sprintf("Plugin with id %s not found", e.PluginId)
+}
+
type PluginLoader interface {
Load(decoder *json.Decoder, pluginDir string) error
}
diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go
index 08ff3cbdfdd..f2dbc1e2e82 100644
--- a/pkg/plugins/plugins_test.go
+++ b/pkg/plugins/plugins_test.go
@@ -27,14 +27,15 @@ func TestPluginScans(t *testing.T) {
Convey("When reading app plugin definition", t, func() {
setting.Cfg = ini.Empty()
- sec, _ := setting.Cfg.NewSection("plugin.app-test")
- sec.NewKey("path", "../../tests/app-plugin-json")
+ sec, _ := setting.Cfg.NewSection("plugin.nginx-app")
+ sec.NewKey("path", "../../tests/test-app")
err := Init()
So(err, ShouldBeNil)
So(len(Apps), ShouldBeGreaterThan, 0)
- So(Apps["app-example"].Info.Logos.Large, ShouldEqual, "public/plugins/app-example/img/logo_large.png")
- So(Apps["app-example"].Info.Screenshots[1].Path, ShouldEqual, "public/plugins/app-example/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/core/directives/plugin_component.ts b/public/app/core/directives/plugin_component.ts
index 858aab02035..69535327cc8 100644
--- a/public/app/core/directives/plugin_component.ts
+++ b/public/app/core/directives/plugin_component.ts
@@ -148,12 +148,13 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
}
// ConfigCtrl
case 'datasource-config-ctrl': {
- return System.import(scope.datasourceMeta.module).then(function(dsModule) {
+ var dsMeta = scope.ctrl.datasourceMeta;
+ return System.import(dsMeta.module).then(function(dsModule) {
return {
- baseUrl: scope.datasourceMeta.baseUrl,
- name: 'ds-config-' + scope.datasourceMeta.id,
+ baseUrl: dsMeta.baseUrl,
+ name: 'ds-config-' + dsMeta.id,
bindings: {meta: "=", current: "="},
- attrs: {meta: "datasourceMeta", current: "current"},
+ attrs: {meta: "ctrl.datasourceMeta", current: "ctrl.current"},
Component: dsModule.ConfigCtrl,
};
});
diff --git a/public/app/core/routes/routes.ts b/public/app/core/routes/routes.ts
index 6e3af683c82..92cf32448a1 100644
--- a/public/app/core/routes/routes.ts
+++ b/public/app/core/routes/routes.ts
@@ -49,20 +49,22 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controller : 'DashboardImportCtrl',
})
.when('/datasources', {
- templateUrl: 'public/app/features/datasources/partials/list.html',
+ templateUrl: 'public/app/features/plugins/partials/ds_list.html',
controller : 'DataSourcesCtrl',
controllerAs: 'ctrl',
- resolve: loadOrgBundle,
+ resolve: loadPluginsBundle,
})
.when('/datasources/edit/:id', {
- templateUrl: 'public/app/features/datasources/partials/edit.html',
+ templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
controller : 'DataSourceEditCtrl',
- resolve: loadOrgBundle,
+ controllerAs: 'ctrl',
+ resolve: loadPluginsBundle,
})
.when('/datasources/new', {
- templateUrl: 'public/app/features/datasources/partials/edit.html',
+ templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
controller : 'DataSourceEditCtrl',
- resolve: loadOrgBundle,
+ controllerAs: 'ctrl',
+ resolve: loadPluginsBundle,
})
.when('/org', {
templateUrl: 'public/app/features/org/partials/orgDetails.html',
@@ -166,19 +168,19 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs: 'ctrl',
})
.when('/plugins', {
- templateUrl: 'public/app/features/plugins/partials/list.html',
+ templateUrl: 'public/app/features/plugins/partials/plugin_list.html',
controller: 'PluginListCtrl',
controllerAs: 'ctrl',
resolve: loadPluginsBundle,
})
.when('/plugins/:pluginId/edit', {
- templateUrl: 'public/app/features/plugins/partials/edit.html',
+ templateUrl: 'public/app/features/plugins/partials/plugin_edit.html',
controller: 'PluginEditCtrl',
controllerAs: 'ctrl',
resolve: loadPluginsBundle,
})
.when('/plugins/:pluginId/page/:slug', {
- templateUrl: 'public/app/features/plugins/partials/page.html',
+ templateUrl: 'public/app/features/plugins/partials/plugin_page.html',
controller: 'AppPageCtrl',
controllerAs: 'ctrl',
resolve: loadPluginsBundle,
diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js
index b96f6693324..d110019add6 100644
--- a/public/app/features/dashboard/all.js
+++ b/public/app/features/dashboard/all.js
@@ -13,7 +13,6 @@ define([
'./timeSrv',
'./unsavedChangesSrv',
'./timepicker/timepicker',
- './import_list/import_list',
'./graphiteImportCtrl',
'./dynamicDashboardSrv',
'./importCtrl',
diff --git a/public/app/features/dashboard/import_list/import_list.ts b/public/app/features/dashboard/import_list/import_list.ts
deleted file mode 100644
index 1d3aa42611b..00000000000
--- a/public/app/features/dashboard/import_list/import_list.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-///
-
-import angular from 'angular';
-import coreModule from 'app/core/core_module';
-
-class DashboardScriptLoader {
-
-}
-
-export class DashImportListCtrl {
- constructor(private $http) {
- console.log('importList', this);
- }
-
- load(json) {
- var model = angular.fromJson(json);
- console.log(model);
- }
-
- import() {
- var url = 'public/app/plugins/datasource/graphite/dashboards/carbon_stats.json';
- this.$http.get(url).then(res => {
- this.load(res.data);
- });
- }
-}
-
-var template = `
-Dashboards
-
-
-
-`;
-
-export function dashboardImportList() {
- return {
- restrict: 'E',
- template: template,
- controller: DashImportListCtrl,
- bindToController: true,
- controllerAs: 'ctrl',
- scope: {
- plugin: "="
- }
- };
-}
-
-
-coreModule.directive('dashboardImportList', dashboardImportList);
-
-
-
-
diff --git a/public/app/features/datasources/all.js b/public/app/features/datasources/all.js
deleted file mode 100644
index b181fd475c2..00000000000
--- a/public/app/features/datasources/all.js
+++ /dev/null
@@ -1,4 +0,0 @@
-define([
- './list_ctrl',
- './edit_ctrl',
-], function () {});
diff --git a/public/app/features/datasources/edit_ctrl.js b/public/app/features/datasources/edit_ctrl.js
deleted file mode 100644
index 062cec6e5e9..00000000000
--- a/public/app/features/datasources/edit_ctrl.js
+++ /dev/null
@@ -1,123 +0,0 @@
-define([
- 'angular',
- 'lodash',
- 'app/core/config',
-],
-function (angular, _, config) {
- 'use strict';
-
- var module = angular.module('grafana.controllers');
- var datasourceTypes = [];
-
- module.directive('datasourceHttpSettings', function() {
- return {
- scope: {current: "="},
- templateUrl: 'public/app/features/datasources/partials/http_settings.html'
- };
- });
-
- module.controller('DataSourceEditCtrl', function($scope, $q, backendSrv, $routeParams, $location, datasourceSrv) {
-
- var defaults = {name: '', type: 'graphite', url: '', access: 'proxy', jsonData: {}};
-
- $scope.init = function() {
- $scope.isNew = true;
- $scope.datasources = [];
-
- $scope.loadDatasourceTypes().then(function() {
- if ($routeParams.id) {
- $scope.getDatasourceById($routeParams.id);
- } else {
- $scope.current = angular.copy(defaults);
- $scope.typeChanged();
- }
- });
- };
-
- $scope.loadDatasourceTypes = function() {
- if (datasourceTypes.length > 0) {
- $scope.types = datasourceTypes;
- return $q.when(null);
- }
-
- return backendSrv.get('/api/org/plugins', {enabled: 1, type: 'datasource'}).then(function(plugins) {
- datasourceTypes = plugins;
- $scope.types = plugins;
- });
- };
-
- $scope.getDatasourceById = function(id) {
- backendSrv.get('/api/datasources/' + id).then(function(ds) {
- $scope.isNew = false;
- $scope.current = ds;
- return $scope.typeChanged();
- });
- };
-
- $scope.typeChanged = function() {
- return backendSrv.get('/api/org/plugins/' + $scope.current.type + '/settings').then(function(pluginInfo) {
- $scope.datasourceMeta = pluginInfo;
- });
- };
-
- $scope.updateFrontendSettings = function() {
- return backendSrv.get('/api/frontend/settings').then(function(settings) {
- config.datasources = settings.datasources;
- config.defaultDatasource = settings.defaultDatasource;
- datasourceSrv.init();
- });
- };
-
- $scope.testDatasource = function() {
- $scope.testing = { done: false };
-
- datasourceSrv.get($scope.current.name).then(function(datasource) {
- if (!datasource.testDatasource) {
- $scope.testing.message = 'Data source does not support test connection feature.';
- $scope.testing.status = 'warning';
- $scope.testing.title = 'Unknown';
- return;
- }
-
- return datasource.testDatasource().then(function(result) {
- $scope.testing.message = result.message;
- $scope.testing.status = result.status;
- $scope.testing.title = result.title;
- }, function(err) {
- if (err.statusText) {
- $scope.testing.message = err.statusText;
- $scope.testing.title = "HTTP Error";
- } else {
- $scope.testing.message = err.message;
- $scope.testing.title = "Unknown error";
- }
- });
- }).finally(function() {
- $scope.testing.done = true;
- });
- };
-
- $scope.saveChanges = function(test) {
- if (!$scope.editForm.$valid) {
- return;
- }
-
- if ($scope.current.id) {
- return backendSrv.put('/api/datasources/' + $scope.current.id, $scope.current).then(function() {
- $scope.updateFrontendSettings().then(function() {
- if (test) {
- $scope.testDatasource();
- }
- });
- });
- } else {
- return backendSrv.post('/api/datasources', $scope.current).then(function(result) {
- $scope.updateFrontendSettings();
- $location.path('datasources/edit/' + result.id);
- });
- }
- };
-
- $scope.init();
- });
-});
diff --git a/public/app/features/datasources/partials/edit.html b/public/app/features/datasources/partials/edit.html
deleted file mode 100644
index 39639206c23..00000000000
--- a/public/app/features/datasources/partials/edit.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
diff --git a/public/app/features/org/all.js b/public/app/features/org/all.js
index cebd0dd1def..e04634d709a 100644
--- a/public/app/features/org/all.js
+++ b/public/app/features/org/all.js
@@ -4,5 +4,4 @@ define([
'./userInviteCtrl',
'./orgApiKeysCtrl',
'./orgDetailsCtrl',
- '../datasources/all',
], function () {});
diff --git a/public/app/features/plugins/all.ts b/public/app/features/plugins/all.ts
index 9d54165e56b..346fb2b30ef 100644
--- a/public/app/features/plugins/all.ts
+++ b/public/app/features/plugins/all.ts
@@ -1,3 +1,6 @@
-import './edit_ctrl';
-import './page_ctrl';
-import './list_ctrl';
+import './plugin_edit_ctrl';
+import './plugin_page_ctrl';
+import './plugin_list_ctrl';
+import './import_list/import_list';
+import './ds_edit_ctrl';
+import './ds_list_ctrl';
diff --git a/public/app/features/plugins/ds_edit_ctrl.ts b/public/app/features/plugins/ds_edit_ctrl.ts
new file mode 100644
index 00000000000..1e07b3a814a
--- /dev/null
+++ b/public/app/features/plugins/ds_edit_ctrl.ts
@@ -0,0 +1,145 @@
+///
+
+import angular from 'angular';
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import config from 'app/core/config';
+
+var datasourceTypes = [];
+
+var defaults = {
+ name: '',
+ type: 'graphite',
+ url: '',
+ access: 'proxy',
+ jsonData: {}
+};
+
+export class DataSourceEditCtrl {
+ isNew: boolean;
+ datasources: any[];
+ current: any;
+ types: any;
+ testing: any;
+ datasourceMeta: any;
+ tabIndex: number;
+ hasDashboards: boolean;
+
+ /** @ngInject */
+ constructor(
+ private $scope,
+ private $q,
+ private backendSrv,
+ private $routeParams,
+ private $location,
+ private datasourceSrv) {
+
+ this.isNew = true;
+ this.datasources = [];
+ this.tabIndex = 0;
+
+ this.loadDatasourceTypes().then(() => {
+ if (this.$routeParams.id) {
+ this.getDatasourceById(this.$routeParams.id);
+ } else {
+ this.current = angular.copy(defaults);
+ this.typeChanged();
+ }
+ });
+ }
+
+ loadDatasourceTypes() {
+ if (datasourceTypes.length > 0) {
+ this.types = datasourceTypes;
+ return this.$q.when(null);
+ }
+
+ return this.backendSrv.get('/api/plugins', {enabled: 1, type: 'datasource'}).then(plugins => {
+ datasourceTypes = plugins;
+ this.types = plugins;
+ });
+ }
+
+ getDatasourceById(id) {
+ this.backendSrv.get('/api/datasources/' + id).then(ds => {
+ this.isNew = false;
+ this.current = ds;
+ return this.typeChanged();
+ });
+ }
+
+ typeChanged() {
+ this.hasDashboards = false;
+ return this.backendSrv.get('/api/plugins/' + this.current.type + '/settings').then(pluginInfo => {
+ this.datasourceMeta = pluginInfo;
+ this.hasDashboards = _.findWhere(pluginInfo.includes, {type: 'dashboard'});
+ });
+ }
+
+ updateFrontendSettings() {
+ return this.backendSrv.get('/api/frontend/settings').then(settings => {
+ config.datasources = settings.datasources;
+ config.defaultDatasource = settings.defaultDatasource;
+ this.datasourceSrv.init();
+ });
+ }
+
+ testDatasource() {
+ this.testing = { done: false };
+
+ this.datasourceSrv.get(this.current.name).then(datasource => {
+ if (!datasource.testDatasource) {
+ this.testing.message = 'Data source does not support test connection feature.';
+ this.testing.status = 'warning';
+ this.testing.title = 'Unknown';
+ return;
+ }
+
+ return datasource.testDatasource().then(result => {
+ this.testing.message = result.message;
+ this.testing.status = result.status;
+ this.testing.title = result.title;
+ }).catch(err => {
+ if (err.statusText) {
+ this.testing.message = err.statusText;
+ this.testing.title = "HTTP Error";
+ } else {
+ this.testing.message = err.message;
+ this.testing.title = "Unknown error";
+ }
+ });
+ }).finally(() => {
+ this.testing.done = true;
+ });
+ }
+
+ saveChanges(test) {
+ if (!this.$scope.editForm.$valid) {
+ return;
+ }
+
+ if (this.current.id) {
+ return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
+ this.updateFrontendSettings().then(() => {
+ if (test) {
+ this.testDatasource();
+ }
+ });
+ });
+ } else {
+ return this.backendSrv.post('/api/datasources', this.current).then(result => {
+ this.updateFrontendSettings();
+ this.$location.path('datasources/edit/' + result.id);
+ });
+ }
+ };
+}
+
+coreModule.controller('DataSourceEditCtrl', DataSourceEditCtrl);
+
+coreModule.directive('datasourceHttpSettings', function() {
+ return {
+ scope: {current: "="},
+ templateUrl: 'public/app/features/plugins/partials/ds_http_settings.html'
+ };
+});
diff --git a/public/app/features/datasources/list_ctrl.ts b/public/app/features/plugins/ds_list_ctrl.ts
similarity index 100%
rename from public/app/features/datasources/list_ctrl.ts
rename to public/app/features/plugins/ds_list_ctrl.ts
diff --git a/public/app/features/plugins/import_list/import_list.html b/public/app/features/plugins/import_list/import_list.html
new file mode 100644
index 00000000000..d6c1bb914ce
--- /dev/null
+++ b/public/app/features/plugins/import_list/import_list.html
@@ -0,0 +1,37 @@
+
+
diff --git a/public/app/features/plugins/import_list/import_list.ts b/public/app/features/plugins/import_list/import_list.ts
new file mode 100644
index 00000000000..62145623fa2
--- /dev/null
+++ b/public/app/features/plugins/import_list/import_list.ts
@@ -0,0 +1,58 @@
+///
+
+import angular from 'angular';
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+
+export class DashImportListCtrl {
+ dashboards: any[];
+ plugin: any;
+
+ constructor(private $http, private backendSrv, private $rootScope) {
+ this.dashboards = [];
+
+ backendSrv.get(`/api/plugins/dashboards/${this.plugin.id}`).then(dashboards => {
+ this.dashboards = dashboards;
+ });
+ }
+
+ import(dash, reinstall) {
+ var installCmd = {
+ pluginId: this.plugin.id,
+ path: dash.path,
+ reinstall: reinstall,
+ inputs: {}
+ };
+
+ this.backendSrv.post(`/api/plugins/dashboards/install`, installCmd).then(res => {
+ this.$rootScope.appEvent('alert-success', ['Dashboard Installed', dash.title]);
+ _.extend(dash, res);
+ });
+ }
+
+ remove(dash) {
+ this.backendSrv.delete('/api/dashboards/' + dash.installedUri).then(() => {
+ this.$rootScope.appEvent('alert-success', ['Dashboard Deleted', dash.title]);
+ dash.installed = false;
+ });
+ }
+}
+
+export function dashboardImportList() {
+ return {
+ restrict: 'E',
+ templateUrl: 'public/app/features/plugins/import_list/import_list.html',
+ controller: DashImportListCtrl,
+ bindToController: true,
+ controllerAs: 'ctrl',
+ scope: {
+ plugin: "="
+ }
+ };
+}
+
+coreModule.directive('dashboardImportList', dashboardImportList);
+
+
+
+
diff --git a/public/app/features/plugins/partials/ds_edit.html b/public/app/features/plugins/partials/ds_edit.html
new file mode 100644
index 00000000000..46177d998cc
--- /dev/null
+++ b/public/app/features/plugins/partials/ds_edit.html
@@ -0,0 +1,81 @@
+
+
+
+
+
diff --git a/public/app/features/datasources/partials/http_settings.html b/public/app/features/plugins/partials/ds_http_settings.html
similarity index 100%
rename from public/app/features/datasources/partials/http_settings.html
rename to public/app/features/plugins/partials/ds_http_settings.html
diff --git a/public/app/features/datasources/partials/list.html b/public/app/features/plugins/partials/ds_list.html
similarity index 100%
rename from public/app/features/datasources/partials/list.html
rename to public/app/features/plugins/partials/ds_list.html
diff --git a/public/app/features/plugins/partials/edit.html b/public/app/features/plugins/partials/plugin_edit.html
similarity index 100%
rename from public/app/features/plugins/partials/edit.html
rename to public/app/features/plugins/partials/plugin_edit.html
diff --git a/public/app/features/plugins/partials/list.html b/public/app/features/plugins/partials/plugin_list.html
similarity index 100%
rename from public/app/features/plugins/partials/list.html
rename to public/app/features/plugins/partials/plugin_list.html
diff --git a/public/app/features/plugins/partials/page.html b/public/app/features/plugins/partials/plugin_page.html
similarity index 100%
rename from public/app/features/plugins/partials/page.html
rename to public/app/features/plugins/partials/plugin_page.html
diff --git a/public/app/features/plugins/edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts
similarity index 97%
rename from public/app/features/plugins/edit_ctrl.ts
rename to public/app/features/plugins/plugin_edit_ctrl.ts
index f8e7c96792c..2d4362835d4 100644
--- a/public/app/features/plugins/edit_ctrl.ts
+++ b/public/app/features/plugins/plugin_edit_ctrl.ts
@@ -22,7 +22,7 @@ export class PluginEditCtrl {
}
init() {
- return this.backendSrv.get(`/api/org/plugins/${this.pluginId}/settings`).then(result => {
+ return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => {
this.model = result;
this.pluginIcon = this.getPluginIcon(this.model.type);
diff --git a/public/app/features/plugins/list_ctrl.ts b/public/app/features/plugins/plugin_list_ctrl.ts
similarity index 81%
rename from public/app/features/plugins/list_ctrl.ts
rename to public/app/features/plugins/plugin_list_ctrl.ts
index a1bacc09c14..2693a4d8755 100644
--- a/public/app/features/plugins/list_ctrl.ts
+++ b/public/app/features/plugins/plugin_list_ctrl.ts
@@ -8,7 +8,7 @@ export class PluginListCtrl {
/** @ngInject */
constructor(private backendSrv: any) {
- this.backendSrv.get('api/org/plugins').then(plugins => {
+ this.backendSrv.get('api/plugins', {embedded: 0}).then(plugins => {
this.plugins = plugins;
});
}
diff --git a/public/app/features/plugins/page_ctrl.ts b/public/app/features/plugins/plugin_page_ctrl.ts
similarity index 100%
rename from public/app/features/plugins/page_ctrl.ts
rename to public/app/features/plugins/plugin_page_ctrl.ts
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/graphite/dashboards/carbon_stats.json b/public/app/plugins/datasource/graphite/dashboards/carbon_stats.json
index 642f32b6e6a..ad2c32be759 100644
--- a/public/app/plugins/datasource/graphite/dashboards/carbon_stats.json
+++ b/public/app/plugins/datasource/graphite/dashboards/carbon_stats.json
@@ -6,7 +6,7 @@
}
},
- "title": "Carbon stats",
+ "title": "Carbon Cache Stats",
"version": 1,
"rows": [
{
diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json
index b079ec320f2..f7715f9c5b9 100644
--- a/public/app/plugins/datasource/graphite/plugin.json
+++ b/public/app/plugins/datasource/graphite/plugin.json
@@ -4,7 +4,7 @@
"id": "graphite",
"includes": [
- {"type": "dashboard", "name": "Carbon Stats", "path": "dashboards/carbon_stats.json"}
+ {"type": "dashboard", "name": "Carbon Cache Stats", "path": "dashboards/carbon_stats.json"}
],
"metrics": true,
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/tests/app-plugin-json/plugin.json b/tests/app-plugin-json/plugin.json
deleted file mode 100644
index 59f41766eda..00000000000
--- a/tests/app-plugin-json/plugin.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "type": "app",
- "name": "App Example",
- "id": "app-example",
-
- "staticRoot": ".",
- "module": "app",
-
- "pages": [
- {"name": "Example1", "url": "/app-example", "reqRole": "Editor"}
- ],
-
- "css": {
- "light": "css/plugin.dark.css",
- "dark": "css/plugin.light.css"
- },
-
- "info": {
- "description": "Example Grafana App",
- "author": {
- "name": "Raintank Inc.",
- "url": "http://raintank.io"
- },
- "keywords": ["example"],
- "logos": {
- "small": "img/logo_small.png",
- "large": "img/logo_large.png"
- },
- "screenshots": [
- {"name": "img1", "path": "img/screenshot1.png"},
- {"name": "img2", "path": "img/screenshot2.png"}
- ],
- "links": [
- {"name": "Project site", "url": "http://project.com"},
- {"name": "License & Terms", "url": "http://license.com"}
- ],
- "version": "1.0.0",
- "updated": "2015-02-10"
- },
-
- "dependencies": {
- "grafanaVersion": "2.6.x",
- "plugins": [
- {"type": "datasource", "id": "graphite", "name": "Graphite", "version": "1.0.0"},
- {"type": "panel", "id": "graph", "name": "Graph", "version": "1.0.0"}
- ]
- }
-}
diff --git a/tests/test-app/dashboards/connections.json b/tests/test-app/dashboards/connections.json
new file mode 100644
index 00000000000..f1f62826f73
--- /dev/null
+++ b/tests/test-app/dashboards/connections.json
@@ -0,0 +1,5 @@
+{
+ "title": "Nginx Connections",
+ "revision": "1.5",
+ "schemaVersion": 11
+}
diff --git a/tests/test-app/dashboards/memory.json b/tests/test-app/dashboards/memory.json
new file mode 100644
index 00000000000..b79cb4d7dba
--- /dev/null
+++ b/tests/test-app/dashboards/memory.json
@@ -0,0 +1,5 @@
+{
+ "title": "Nginx Memory",
+ "revision": "2.0",
+ "schemaVersion": 11
+}
diff --git a/examples/nginx-app/plugin.json b/tests/test-app/plugin.json
similarity index 66%
rename from examples/nginx-app/plugin.json
rename to tests/test-app/plugin.json
index 65f01ad62fc..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,16 +16,20 @@
},
"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"
},
+ "screenshots": [
+ {"name": "img1", "path": "img/screenshot1.png"},
+ {"name": "img2", "path": "img/screenshot2.png"}
+ ],
"links": [
{"name": "Project site", "url": "http://project.com"},
{"name": "License & Terms", "url": "http://license.com"}
@@ -35,7 +39,8 @@
},
"includes": [
- {"type": "dashboard", "name": "Nginx Connection stats", "path": "dashboards/nginx_connection_stats.json"},
+ {"type": "dashboard", "name": "Nginx Connections", "path": "dashboards/connections.json"},
+ {"type": "dashboard", "name": "Nginx Memory", "path": "dashboards/memory.json"},
{"type": "panel", "name": "Nginx Panel"},
{"type": "datasource", "name": "Nginx Datasource"}
],