mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into cli_colors
This commit is contained in:
commit
740478344b
@ -64,9 +64,19 @@ Name | Description
|
|||||||
`metrics(namespace)` | Returns a list of metrics in the namespace.
|
`metrics(namespace)` | Returns a list of metrics in the namespace.
|
||||||
`dimension_keys(namespace)` | Returns a list of dimension keys 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`.
|
`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).
|
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
|
## Cost
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
## Example plugin implementations
|
## 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)
|
7
examples/nginx-app/.gitignore
vendored
7
examples/nginx-app/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
tmp/*
|
|
||||||
npm-debug.log
|
|
||||||
dist/*
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"disallowImplicitTypeConversion": ["string"],
|
|
||||||
"disallowKeywords": ["with"],
|
|
||||||
"disallowMultipleLineBreaks": true,
|
|
||||||
"disallowMixedSpacesAndTabs": true,
|
|
||||||
"disallowTrailingWhitespace": true,
|
|
||||||
"requireSpacesInFunctionExpression": {
|
|
||||||
"beforeOpeningCurlyBrace": true
|
|
||||||
},
|
|
||||||
"disallowSpacesInsideArrayBrackets": true,
|
|
||||||
"disallowSpacesInsideParentheses": true,
|
|
||||||
"validateIndentation": 2
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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']);
|
|
||||||
};
|
|
@ -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"
|
|
||||||
}
|
|
@ -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.
|
|
@ -1,3 +0,0 @@
|
|||||||
<h3>
|
|
||||||
Nginx config!
|
|
||||||
</h3>
|
|
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
export class NginxAppConfigCtrl {
|
|
||||||
}
|
|
||||||
NginxAppConfigCtrl.templateUrl = 'components/config.html';
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
<h3>
|
|
||||||
Logs page!
|
|
||||||
</h3>
|
|
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
export class LogsPageCtrl {
|
|
||||||
}
|
|
||||||
LogsPageCtrl.templateUrl = 'components/logs.html';
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
<h3>
|
|
||||||
Stream page!
|
|
||||||
</h3>
|
|
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
export class StreamPageCtrl {
|
|
||||||
}
|
|
||||||
StreamPageCtrl.templateUrl = 'components/stream.html';
|
|
||||||
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
require([
|
|
||||||
], function () {
|
|
||||||
|
|
||||||
function Dashboard() {
|
|
||||||
|
|
||||||
this.getInputs = function() {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.buildDashboard = function() {
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Dashboard;
|
|
||||||
});
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
export default class NginxDatasource {
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
query(options) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
testDatasource() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
import {Datasource} from './datasource';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Datasource
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "datasource",
|
|
||||||
"name": "Nginx Datasource",
|
|
||||||
"id": "nginx-datasource"
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.3 KiB |
@ -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
|
|
||||||
};
|
|
@ -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
|
|
||||||
};
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "panel",
|
|
||||||
"name": "Nginx Panel",
|
|
||||||
"id": "nginx-panel"
|
|
||||||
}
|
|
@ -126,10 +126,6 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||||
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
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)
|
}, reqOrgAdmin)
|
||||||
|
|
||||||
// create new org
|
// create new org
|
||||||
@ -176,6 +172,16 @@ func Register(r *macaron.Macaron) {
|
|||||||
|
|
||||||
r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
|
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.Get("/frontend/settings/", GetFrontendSettings)
|
||||||
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
|
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
|
||||||
r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)
|
r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)
|
||||||
|
@ -55,8 +55,10 @@ func init() {
|
|||||||
"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "CorruptBlocks", "TotalLoad", "MemoryTotalMB", "MemoryReservedMB", "MemoryAvailableMB", "MemoryAllocatedMB", "PendingDeletionBlocks", "UnderReplicatedBlocks", "DfsPendingReplicationBlocks", "CapacityRemainingGB",
|
"S3BytesWritten", "S3BytesRead", "HDFSUtilization", "HDFSBytesRead", "HDFSBytesWritten", "MissingBlocks", "CorruptBlocks", "TotalLoad", "MemoryTotalMB", "MemoryReservedMB", "MemoryAvailableMB", "MemoryAllocatedMB", "PendingDeletionBlocks", "UnderReplicatedBlocks", "DfsPendingReplicationBlocks", "CapacityRemainingGB",
|
||||||
"HbaseBackupFailed", "MostRecentBackupDuration", "TimeSinceLastSuccessfulBackup"},
|
"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/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/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/Lambda": {"Invocations", "Errors", "Duration", "Throttles"},
|
||||||
|
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
|
||||||
"AWS/ML": {"PredictCount", "PredictFailureCount"},
|
"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/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"},
|
"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/ELB": {"LoadBalancerName", "AvailabilityZone"},
|
||||||
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
"AWS/ElasticMapReduce": {"ClusterId", "JobFlowId", "JobId"},
|
||||||
"AWS/ES": {},
|
"AWS/ES": {},
|
||||||
|
"AWS/Events": {"RuleName"},
|
||||||
"AWS/Kinesis": {"StreamName"},
|
"AWS/Kinesis": {"StreamName"},
|
||||||
"AWS/Lambda": {"FunctionName"},
|
"AWS/Lambda": {"FunctionName"},
|
||||||
|
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
||||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||||
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
|
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
|
||||||
"AWS/Redshift": {"NodeID", "ClusterIdentifier"},
|
"AWS/Redshift": {"NodeID", "ClusterIdentifier"},
|
||||||
|
@ -25,3 +25,10 @@ type PluginListItem struct {
|
|||||||
Pinned bool `json:"pinned"`
|
Pinned bool `json:"pinned"`
|
||||||
Info *plugins.PluginInfo `json:"info"`
|
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"`
|
||||||
|
}
|
||||||
|
@ -107,3 +107,34 @@ func UpdatePluginSetting(c *middleware.Context, cmd m.UpdatePluginSettingCmd) Re
|
|||||||
|
|
||||||
return ApiSuccess("Plugin settings updated")
|
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)
|
||||||
|
}
|
@ -11,6 +11,7 @@ func runCommand(command func(commandLine CommandLine) error) func(context *cli.C
|
|||||||
|
|
||||||
cmd := &contextCommandLine{context}
|
cmd := &contextCommandLine{context}
|
||||||
if err := command(cmd); err != nil {
|
if err := command(cmd); err != nil {
|
||||||
|
log.Error("\nError: ")
|
||||||
log.Errorf("%s\n\n", err)
|
log.Errorf("%s\n\n", err)
|
||||||
|
|
||||||
cmd.ShowHelp()
|
cmd.ShowHelp()
|
||||||
|
@ -29,7 +29,15 @@ func validateInput(c CommandLine, pluginFolder string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileInfo, err := os.Stat(pluginDir)
|
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")
|
return errors.New("path is not a directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func main() {
|
|||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "repo",
|
Name: "repo",
|
||||||
Usage: "url to the plugin repository",
|
Usage: "url to the plugin repository",
|
||||||
Value: "",
|
Value: "https://grafana-net.raintank.io/api/plugins",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "debug, d",
|
Name: "debug, d",
|
||||||
|
@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/franela/goreq"
|
"github.com/franela/goreq"
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
|
||||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||||
@ -12,8 +13,12 @@ import (
|
|||||||
var IoHelper m.IoUtil = IoUtilImp{}
|
var IoHelper m.IoUtil = IoUtilImp{}
|
||||||
|
|
||||||
func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
|
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
|
var resp m.PluginRepo
|
||||||
err := res.Body.FromJsonTo(&resp)
|
err := res.Body.FromJsonTo(&resp)
|
||||||
|
@ -102,8 +102,11 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetString a
|
// GetString a
|
||||||
func (dash *Dashboard) GetString(prop string) string {
|
func (dash *Dashboard) GetString(prop string, defaultValue string) string {
|
||||||
return dash.Data[prop].(string)
|
if val, exists := dash.Data[prop]; exists {
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSlug updates the slug
|
// UpdateSlug updates the slug
|
||||||
|
57
pkg/plugins/dashboard_installer.go
Normal file
57
pkg/plugins/dashboard_installer.go
Normal file
@ -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
|
||||||
|
}
|
93
pkg/plugins/dashboards.go
Normal file
93
pkg/plugins/dashboards.go
Normal file
@ -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
|
||||||
|
}
|
53
pkg/plugins/dashboards_test.go
Normal file
53
pkg/plugins/dashboards_test.go
Normal file
@ -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, "")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
@ -3,12 +3,28 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"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 {
|
type PluginLoader interface {
|
||||||
Load(decoder *json.Decoder, pluginDir string) error
|
Load(decoder *json.Decoder, pluginDir string) error
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,15 @@ func TestPluginScans(t *testing.T) {
|
|||||||
|
|
||||||
Convey("When reading app plugin definition", t, func() {
|
Convey("When reading app plugin definition", t, func() {
|
||||||
setting.Cfg = ini.Empty()
|
setting.Cfg = ini.Empty()
|
||||||
sec, _ := setting.Cfg.NewSection("plugin.app-test")
|
sec, _ := setting.Cfg.NewSection("plugin.nginx-app")
|
||||||
sec.NewKey("path", "../../tests/app-plugin-json")
|
sec.NewKey("path", "../../tests/test-app")
|
||||||
err := Init()
|
err := Init()
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(Apps), ShouldBeGreaterThan, 0)
|
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")
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -148,12 +148,13 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
|||||||
}
|
}
|
||||||
// ConfigCtrl
|
// ConfigCtrl
|
||||||
case 'datasource-config-ctrl': {
|
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 {
|
return {
|
||||||
baseUrl: scope.datasourceMeta.baseUrl,
|
baseUrl: dsMeta.baseUrl,
|
||||||
name: 'ds-config-' + scope.datasourceMeta.id,
|
name: 'ds-config-' + dsMeta.id,
|
||||||
bindings: {meta: "=", current: "="},
|
bindings: {meta: "=", current: "="},
|
||||||
attrs: {meta: "datasourceMeta", current: "current"},
|
attrs: {meta: "ctrl.datasourceMeta", current: "ctrl.current"},
|
||||||
Component: dsModule.ConfigCtrl,
|
Component: dsModule.ConfigCtrl,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -49,20 +49,22 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
controller : 'DashboardImportCtrl',
|
controller : 'DashboardImportCtrl',
|
||||||
})
|
})
|
||||||
.when('/datasources', {
|
.when('/datasources', {
|
||||||
templateUrl: 'public/app/features/datasources/partials/list.html',
|
templateUrl: 'public/app/features/plugins/partials/ds_list.html',
|
||||||
controller : 'DataSourcesCtrl',
|
controller : 'DataSourcesCtrl',
|
||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
resolve: loadOrgBundle,
|
resolve: loadPluginsBundle,
|
||||||
})
|
})
|
||||||
.when('/datasources/edit/:id', {
|
.when('/datasources/edit/:id', {
|
||||||
templateUrl: 'public/app/features/datasources/partials/edit.html',
|
templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
|
||||||
controller : 'DataSourceEditCtrl',
|
controller : 'DataSourceEditCtrl',
|
||||||
resolve: loadOrgBundle,
|
controllerAs: 'ctrl',
|
||||||
|
resolve: loadPluginsBundle,
|
||||||
})
|
})
|
||||||
.when('/datasources/new', {
|
.when('/datasources/new', {
|
||||||
templateUrl: 'public/app/features/datasources/partials/edit.html',
|
templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
|
||||||
controller : 'DataSourceEditCtrl',
|
controller : 'DataSourceEditCtrl',
|
||||||
resolve: loadOrgBundle,
|
controllerAs: 'ctrl',
|
||||||
|
resolve: loadPluginsBundle,
|
||||||
})
|
})
|
||||||
.when('/org', {
|
.when('/org', {
|
||||||
templateUrl: 'public/app/features/org/partials/orgDetails.html',
|
templateUrl: 'public/app/features/org/partials/orgDetails.html',
|
||||||
@ -166,19 +168,19 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
})
|
})
|
||||||
.when('/plugins', {
|
.when('/plugins', {
|
||||||
templateUrl: 'public/app/features/plugins/partials/list.html',
|
templateUrl: 'public/app/features/plugins/partials/plugin_list.html',
|
||||||
controller: 'PluginListCtrl',
|
controller: 'PluginListCtrl',
|
||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
resolve: loadPluginsBundle,
|
resolve: loadPluginsBundle,
|
||||||
})
|
})
|
||||||
.when('/plugins/:pluginId/edit', {
|
.when('/plugins/:pluginId/edit', {
|
||||||
templateUrl: 'public/app/features/plugins/partials/edit.html',
|
templateUrl: 'public/app/features/plugins/partials/plugin_edit.html',
|
||||||
controller: 'PluginEditCtrl',
|
controller: 'PluginEditCtrl',
|
||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
resolve: loadPluginsBundle,
|
resolve: loadPluginsBundle,
|
||||||
})
|
})
|
||||||
.when('/plugins/:pluginId/page/:slug', {
|
.when('/plugins/:pluginId/page/:slug', {
|
||||||
templateUrl: 'public/app/features/plugins/partials/page.html',
|
templateUrl: 'public/app/features/plugins/partials/plugin_page.html',
|
||||||
controller: 'AppPageCtrl',
|
controller: 'AppPageCtrl',
|
||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
resolve: loadPluginsBundle,
|
resolve: loadPluginsBundle,
|
||||||
|
@ -13,7 +13,6 @@ define([
|
|||||||
'./timeSrv',
|
'./timeSrv',
|
||||||
'./unsavedChangesSrv',
|
'./unsavedChangesSrv',
|
||||||
'./timepicker/timepicker',
|
'./timepicker/timepicker',
|
||||||
'./import_list/import_list',
|
|
||||||
'./graphiteImportCtrl',
|
'./graphiteImportCtrl',
|
||||||
'./dynamicDashboardSrv',
|
'./dynamicDashboardSrv',
|
||||||
'./importCtrl',
|
'./importCtrl',
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
///<reference path="../../../headers/common.d.ts" />
|
|
||||||
|
|
||||||
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 = `
|
|
||||||
<h3 class="page-heading">Dashboards</h3>
|
|
||||||
<div class="gf-form-group">
|
|
||||||
<button class="btn btn-mini btn-inverse" ng-click="ctrl.import(dash)">Import</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function dashboardImportList() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
template: template,
|
|
||||||
controller: DashImportListCtrl,
|
|
||||||
bindToController: true,
|
|
||||||
controllerAs: 'ctrl',
|
|
||||||
scope: {
|
|
||||||
plugin: "="
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
coreModule.directive('dashboardImportList', dashboardImportList);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
define([
|
|
||||||
'./list_ctrl',
|
|
||||||
'./edit_ctrl',
|
|
||||||
], function () {});
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,63 +0,0 @@
|
|||||||
<navbar
|
|
||||||
title="Data Sources"
|
|
||||||
title-url="datasources"
|
|
||||||
icon="icon-gf icon-gf-datasources">
|
|
||||||
</navbar>
|
|
||||||
|
|
||||||
<div class="page-container">
|
|
||||||
<div class="page-header">
|
|
||||||
<h1 ng-show="isNew">Add data source</h1>
|
|
||||||
<h1 ng-show="!isNew">Edit data source</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form name="editForm">
|
|
||||||
<div class="gf-form-group">
|
|
||||||
<div class="gf-form">
|
|
||||||
<span class="gf-form-label width-7">Name</span>
|
|
||||||
<input class="gf-form-input max-width-21" type="text" ng-model="current.name" placeholder="My data source name" required>
|
|
||||||
<info-popover offset="0px -95px">
|
|
||||||
The name is used when you select the data source in panels.
|
|
||||||
The <code>Default</code> data source is preselected in new
|
|
||||||
panels.
|
|
||||||
</info-popover>
|
|
||||||
|
|
||||||
<editor-checkbox text="Default" model="current.isDefault"></editor-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form">
|
|
||||||
<span class="gf-form-label width-7">Type</span>
|
|
||||||
<div class="gf-form-select-wrapper">
|
|
||||||
<select class="gf-form-input gf-size-auto" ng-model="current.type" ng-options="v.id as v.name for v in types" ng-change="typeChanged()"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<rebuild-on-change property="datasourceMeta.id">
|
|
||||||
<plugin-component type="datasource-config-ctrl">
|
|
||||||
</plugin-component>
|
|
||||||
</rebuild-on-change>
|
|
||||||
|
|
||||||
<div ng-if="testing" style="margin-top: 25px">
|
|
||||||
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
|
||||||
<h5 ng-show="testing.done">Test results</h5>
|
|
||||||
<div class="alert-{{testing.status}} alert">
|
|
||||||
<div class="alert-title">{{testing.title}}</div>
|
|
||||||
<div ng-bind='testing.message'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <dashboard-import-list plugin="current"></dashboard-import-list> -->
|
|
||||||
|
|
||||||
<div class="gf-form-button-row">
|
|
||||||
<button type="submit" class="btn btn-success" ng-show="isNew" ng-click="saveChanges()">Add</button>
|
|
||||||
<button type="submit" class="btn btn-success" ng-show="!isNew" ng-click="saveChanges()">Save</button>
|
|
||||||
<button type="submit" class="btn btn-secondary" ng-show="!isNew" ng-click="saveChanges(true)">
|
|
||||||
Test Connection
|
|
||||||
</button>
|
|
||||||
<a class="btn btn-link" href="datasources">Cancel</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
@ -4,5 +4,4 @@ define([
|
|||||||
'./userInviteCtrl',
|
'./userInviteCtrl',
|
||||||
'./orgApiKeysCtrl',
|
'./orgApiKeysCtrl',
|
||||||
'./orgDetailsCtrl',
|
'./orgDetailsCtrl',
|
||||||
'../datasources/all',
|
|
||||||
], function () {});
|
], function () {});
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
import './edit_ctrl';
|
import './plugin_edit_ctrl';
|
||||||
import './page_ctrl';
|
import './plugin_page_ctrl';
|
||||||
import './list_ctrl';
|
import './plugin_list_ctrl';
|
||||||
|
import './import_list/import_list';
|
||||||
|
import './ds_edit_ctrl';
|
||||||
|
import './ds_list_ctrl';
|
||||||
|
145
public/app/features/plugins/ds_edit_ctrl.ts
Normal file
145
public/app/features/plugins/ds_edit_ctrl.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
///<reference path="../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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'
|
||||||
|
};
|
||||||
|
});
|
37
public/app/features/plugins/import_list/import_list.html
Normal file
37
public/app/features/plugins/import_list/import_list.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<div class="gf-form-group" ng-if="ctrl.dashboards.length">
|
||||||
|
<table class="filter-table">
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="dash in ctrl.dashboards">
|
||||||
|
<td class="width-1">
|
||||||
|
<i class="icon-gf icon-gf-dashboard"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="dashboard/{{dash.installedUri}}" ng-show="dash.installed">
|
||||||
|
{{dash.title}}
|
||||||
|
</a>
|
||||||
|
<span ng-show="!dash.installed">
|
||||||
|
{{dash.title}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
v{{dash.revision}}
|
||||||
|
</td>
|
||||||
|
<td ng-if="dash.installed">
|
||||||
|
Installed v{{dash.installedRevision}}
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
|
||||||
|
Re-Install
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
|
||||||
|
Un-install
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
58
public/app/features/plugins/import_list/import_list.ts
Normal file
58
public/app/features/plugins/import_list/import_list.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
///<reference path="../../../headers/common.d.ts" />
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
81
public/app/features/plugins/partials/ds_edit.html
Normal file
81
public/app/features/plugins/partials/ds_edit.html
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<navbar title="Data Sources" title-url="datasources" icon="icon-gf icon-gf-datasources">
|
||||||
|
</navbar>
|
||||||
|
|
||||||
|
<div class="page-container">
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1 ng-show="isNew">Add data source</h1>
|
||||||
|
<h1 ng-show="!isNew">Edit data source</h1>
|
||||||
|
|
||||||
|
<div class="page-header-tabs" ng-show="ctrl.hasDashboards">
|
||||||
|
<ul class="gf-tabs">
|
||||||
|
<li class="gf-tabs-item">
|
||||||
|
<a class="gf-tabs-link" ng-click="ctrl.tabIndex = 0" ng-class="{active: ctrl.tabIndex === 0}">
|
||||||
|
Config
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="gf-tabs-item">
|
||||||
|
<a class="gf-tabs-link" ng-click="ctrl.tabIndex = 1" ng-class="{active: ctrl.tabIndex === 1}">
|
||||||
|
Dashboards
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="ctrl.tabIndex === 0" class="tab-content">
|
||||||
|
|
||||||
|
<form name="editForm">
|
||||||
|
<div class="gf-form-group">
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-7">Name</span>
|
||||||
|
<input class="gf-form-input max-width-21" type="text" ng-model="ctrl.current.name" placeholder="My data source name" required>
|
||||||
|
<info-popover offset="0px -95px">
|
||||||
|
The name is used when you select the data source in panels.
|
||||||
|
The <code>Default</code> data source is preselected in new
|
||||||
|
panels.
|
||||||
|
</info-popover>
|
||||||
|
|
||||||
|
<editor-checkbox text="Default" model="ctrl.current.isDefault"></editor-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-7">Type</span>
|
||||||
|
<div class="gf-form-select-wrapper">
|
||||||
|
<select class="gf-form-input gf-size-auto" ng-model="ctrl.current.type" ng-options="v.id as v.name for v in ctrl.types" ng-change="ctrl.typeChanged()"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<rebuild-on-change property="ctrl.datasourceMeta.id">
|
||||||
|
<plugin-component type="datasource-config-ctrl">
|
||||||
|
</plugin-component>
|
||||||
|
</rebuild-on-change>
|
||||||
|
|
||||||
|
<div ng-if="testing" style="margin-top: 25px">
|
||||||
|
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
|
||||||
|
<h5 ng-show="testing.done">Test results</h5>
|
||||||
|
<div class="alert-{{testing.status}} alert">
|
||||||
|
<div class="alert-title">{{testing.title}}</div>
|
||||||
|
<div ng-bind='testing.message'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form-button-row">
|
||||||
|
<button type="submit" class="btn btn-success" ng-show="ctrl.isNew" ng-click="ctrl.saveChanges()">Add</button>
|
||||||
|
<button type="submit" class="btn btn-success" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges()">Save</button>
|
||||||
|
<button type="submit" class="btn btn-secondary" ng-show="!ctrl.isNew" ng-click="ctrl.saveChanges(true)">
|
||||||
|
Test Connection
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-link" href="datasources">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="ctrl.tabIndex === 1" class="tab-content">
|
||||||
|
<dashboard-import-list plugin="ctrl.datasourceMeta"></dashboard-import-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -22,7 +22,7 @@ export class PluginEditCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
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.model = result;
|
||||||
this.pluginIcon = this.getPluginIcon(this.model.type);
|
this.pluginIcon = this.getPluginIcon(this.model.type);
|
||||||
|
|
@ -8,7 +8,7 @@ export class PluginListCtrl {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv: any) {
|
constructor(private backendSrv: any) {
|
||||||
|
|
||||||
this.backendSrv.get('api/org/plugins').then(plugins => {
|
this.backendSrv.get('api/plugins', {embedded: 0}).then(plugins => {
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -143,7 +143,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
|
|||||||
return this.awsRequest({
|
return this.awsRequest({
|
||||||
region: region,
|
region: region,
|
||||||
action: 'DescribeInstances',
|
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([]);
|
return $q.when([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"title": "Carbon stats",
|
"title": "Carbon Cache Stats",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"rows": [
|
"rows": [
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"id": "graphite",
|
"id": "graphite",
|
||||||
|
|
||||||
"includes": [
|
"includes": [
|
||||||
{"type": "dashboard", "name": "Carbon Stats", "path": "dashboards/carbon_stats.json"}
|
{"type": "dashboard", "name": "Carbon Cache Stats", "path": "dashboards/carbon_stats.json"}
|
||||||
],
|
],
|
||||||
|
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
|
@ -104,7 +104,7 @@ export function InfluxDatasource(instanceSettings, $q, backendSrv, templateSrv)
|
|||||||
this.metricFindQuery = function (query) {
|
this.metricFindQuery = function (query) {
|
||||||
var interpolated;
|
var interpolated;
|
||||||
try {
|
try {
|
||||||
interpolated = templateSrv.replace(query);
|
interpolated = templateSrv.replace(query, null, 'regex');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return $q.reject(err);
|
return $q.reject(err);
|
||||||
}
|
}
|
||||||
|
@ -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"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
5
tests/test-app/dashboards/connections.json
Normal file
5
tests/test-app/dashboards/connections.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Nginx Connections",
|
||||||
|
"revision": "1.5",
|
||||||
|
"schemaVersion": 11
|
||||||
|
}
|
5
tests/test-app/dashboards/memory.json
Normal file
5
tests/test-app/dashboards/memory.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Nginx Memory",
|
||||||
|
"revision": "2.0",
|
||||||
|
"schemaVersion": 11
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"name": "Nginx",
|
"name": "Test App",
|
||||||
"id": "nginx-app",
|
"id": "test-app",
|
||||||
|
|
||||||
"staticRoot": ".",
|
"staticRoot": ".",
|
||||||
|
|
||||||
@ -16,16 +16,20 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"info": {
|
"info": {
|
||||||
"description": "Official Grafana Nginx App & Dashboard bundle",
|
"description": "Official Grafana Test App & Dashboard bundle",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Nginx Inc.",
|
"name": "Test Inc.",
|
||||||
"url": "http://nginx.com"
|
"url": "http://test.com"
|
||||||
},
|
},
|
||||||
"keywords": ["nginx"],
|
"keywords": ["test"],
|
||||||
"logos": {
|
"logos": {
|
||||||
"small": "img/logo_small.png",
|
"small": "img/logo_small.png",
|
||||||
"large": "img/logo_large.png"
|
"large": "img/logo_large.png"
|
||||||
},
|
},
|
||||||
|
"screenshots": [
|
||||||
|
{"name": "img1", "path": "img/screenshot1.png"},
|
||||||
|
{"name": "img2", "path": "img/screenshot2.png"}
|
||||||
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{"name": "Project site", "url": "http://project.com"},
|
{"name": "Project site", "url": "http://project.com"},
|
||||||
{"name": "License & Terms", "url": "http://license.com"}
|
{"name": "License & Terms", "url": "http://license.com"}
|
||||||
@ -35,7 +39,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"includes": [
|
"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": "panel", "name": "Nginx Panel"},
|
||||||
{"type": "datasource", "name": "Nginx Datasource"}
|
{"type": "datasource", "name": "Nginx Datasource"}
|
||||||
],
|
],
|
Loading…
Reference in New Issue
Block a user