diff --git a/README.md b/README.md index c02e1c202..e96ecff7f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,43 @@ # grafana-zabbix Zabbix API datasource for Grafana dashboard + +![alt tag](https://cloud.githubusercontent.com/assets/4932851/7454206/34bf9f8c-f27a-11e4-8e96-a73829f188c4.png) + + +Query editor allows to add metric by step-by-step selection from host group, host, application dropdown menus. + +![alt tag](https://cloud.githubusercontent.com/assets/4932851/7441162/4f6af788-f0e4-11e4-887b-34d987d00c40.png) +![alt tag](https://cloud.githubusercontent.com/assets/4932851/7441163/56f28f16-f0e4-11e4-9d46-54181c2a2e7e.png) +![alt tag](https://cloud.githubusercontent.com/assets/4932851/7441167/5f29cc94-f0e4-11e4-8d39-7580f33201f6.png) + + +## Installation + +### Grafana 1.9.x + +Download latest release and unpack into `/plugins/datasource/`. Then edit Grafana config.js: + * Add dependencies + + ``` + plugins: { + panels: [], + dependencies: ['datasource/zabbix/datasource', 'datasource/zabbix/queryCtrl'], + } + ``` + * Add datasource and setup your Zabbix API url, username and password + + ``` + datasources: { + ... + }, + zabbix: { + type: 'ZabbixAPIDatasource', + url: 'http://www.zabbix.org/zabbix/api_jsonrpc.php', + username: 'guest', + password: '' + } + }, + ``` + +### Grafana 2.0.x +Now in development. diff --git a/zabbix/datasource.js b/zabbix/datasource.js index 20ed0437f..ad4ccc0eb 100644 --- a/zabbix/datasource.js +++ b/zabbix/datasource.js @@ -14,16 +14,19 @@ function (angular, _, kbn) { function ZabbixAPIDatasource(datasource) { this.name = datasource.name; this.type = 'ZabbixAPIDatasource'; - this.supportMetrics = true; + this.url = datasource.url; this.username = datasource.username; this.password = datasource.password; + // No datapoints limit by default this.limitmetrics = datasource.limitmetrics || 0; this.partials = datasource.partials || 'plugins/datasource/zabbix/partials'; this.editorSrc = this.partials + '/query.editor.html'; this.annotationEditorSrc = this.partials + '/annotations.editor.html'; + + this.supportMetrics = true; this.supportAnnotations = true; } @@ -32,9 +35,11 @@ function (angular, _, kbn) { // get from & to in seconds var from = kbn.parseDate(options.range.from).getTime(); var to = kbn.parseDate(options.range.to).getTime(); + // Need for find target alias var targets = options.targets; + // TODO: remove undefined targets from request // Check that all targets defined var targetsDefined = options.targets.every(function (target, index, array) { return target.item; @@ -43,7 +48,6 @@ function (angular, _, kbn) { // Extract zabbix api item objects from targets var target_items = _.map(options.targets, 'item'); } else { - // No valid targets, return the empty dataset var d = $q.defer(); d.resolve({ data: [] }); @@ -53,20 +57,7 @@ function (angular, _, kbn) { from = Math.ceil(from/1000); to = Math.ceil(to/1000); - var performedQuery; - - // Check authorization first - if (!this.auth) { - var self = this; - performedQuery = this.performZabbixAPILogin().then(function (response) { - self.auth = response; - return self.performTimeSeriesQuery(target_items, from, to); - }); - } else { - performedQuery = this.performTimeSeriesQuery(target_items, from, to); - } - - return performedQuery.then(function (response) { + return this.performTimeSeriesQuery(target_items, from, to).then(function (response) { /** * Response should be in the format: * data: [ @@ -82,9 +73,7 @@ function (angular, _, kbn) { */ // Index returned datapoints by item/metric id - var indexed_result = _.groupBy(response.data.result, function (history_item) { - return history_item.itemid; - }); + var indexed_result = _.groupBy(response, 'itemid'); // Reduce timeseries to the same size for stacking and tooltip work properly var min_length = _.min(_.map(indexed_result, function (history) { @@ -130,43 +119,109 @@ function (angular, _, kbn) { /////////////////////////////////////////////////////////////////////// + // Request data from Zabbix API + ZabbixAPIDatasource.prototype.doZabbixAPIRequest = function(request_data) { + var options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + url: this.url, + data: request_data + }; + + var performedQuery; + + // Check authorization first + if (!this.auth) { + var self = this; + performedQuery = this.performZabbixAPILogin().then(function (response) { + self.auth = response; + options.data.auth = response; + return $http(options); + }); + } else { + performedQuery = $http(options); + } + + // Handle response + return performedQuery.then(function (response) { + if (!response.data) { + return []; + } + return response.data.result; + }); + }; + + /** * Perform time series query to Zabbix API * * @param items: array of zabbix api item objects */ ZabbixAPIDatasource.prototype.performTimeSeriesQuery = function(items, start, end) { - var item_ids = items.map(function (item, index, array) { - return item.itemid; - }); - // TODO: if different value types passed? - // Perform multiple api request. - var hystory_type = items[0].value_type; - var options = { - method: 'POST', - url: this.url, - data: { + + // Group items by value type for separate requests + var items_by_value_type = _.groupBy(items, 'value_type'); + + var self = this; + var apiRequests = []; + + // Prepare requests for each value type + _.each(items_by_value_type, function (value, key, list) { + var item_ids = _.map(value, 'itemid'); + var history_type = key; + var data = { jsonrpc: '2.0', method: 'history.get', params: { - output: 'extend', - history: hystory_type, - itemids: item_ids, - sortfield: 'clock', - sortorder: 'ASC', - limit: this.limitmetrics, - time_from: start, + output: 'extend', + history: history_type, + itemids: item_ids, + sortfield: 'clock', + sortorder: 'ASC', + limit: self.limitmetrics, + time_from: start, }, - auth: this.auth, + auth: self.auth, id: 1 - }, - }; - // Relative queries (e.g. last hour) don't include an end time - if (end) { - options.data.params.time_till = end; - } + }; + + // Relative queries (e.g. last hour) don't include an end time + if (end) { + data.params.time_till = end; + } + + apiRequests.push(self.doZabbixAPIRequest(data)); + }); - return $http(options); + return this.handleMultipleRequest(apiRequests); + }; + + + // Handle multiple request + ZabbixAPIDatasource.prototype.handleMultipleRequest = function(apiRequests) { + var history = []; + var performedQuery = null; + + // Build chain of api requests and put all history data into single array + _.each(apiRequests, function (apiRequest) { + if(!performedQuery) { + performedQuery = apiRequest.then(function (response) { + history = history.concat(response); + return history; + }); + } else { + performedQuery = performedQuery.then(function () { + return apiRequest.then(function (response) { + history = history.concat(response); + return history; + }); + }); + } + }); + + return performedQuery; }; @@ -186,6 +241,7 @@ function (angular, _, kbn) { id: 1 }, }; + return $http(options).then(function (result) { if (!result.data) { return null; @@ -197,110 +253,83 @@ function (angular, _, kbn) { // Get the list of host groups ZabbixAPIDatasource.prototype.performHostGroupSuggestQuery = function() { - var options = { - url : this.url, - method : 'POST', - data: { - jsonrpc: '2.0', - method: 'hostgroup.get', - params: { - output: ['name'], - sortfield: 'name' - }, - auth: this.auth, - id: 1 + var data = { + jsonrpc: '2.0', + method: 'hostgroup.get', + params: { + output: ['name'], + real_hosts: true, //Return only host groups that contain hosts + sortfield: 'name' }, + auth: this.auth, + id: 1 }; - return $http(options).then(function (result) { - if (!result.data) { - return []; - } - return result.data.result; - }); + + return this.doZabbixAPIRequest(data); }; // Get the list of hosts ZabbixAPIDatasource.prototype.performHostSuggestQuery = function(groupid) { - var options = { - url : this.url, - method : 'POST', - data: { - jsonrpc: '2.0', - method: 'host.get', - params: { - output: ['name'], - sortfield: 'name' - }, - auth: this.auth, - id: 1 + var data = { + jsonrpc: '2.0', + method: 'host.get', + params: { + output: ['name'], + sortfield: 'name' }, + auth: this.auth, + id: 1 }; if (groupid) { - options.data.params.groupids = groupid; + data.params.groupids = groupid; } - return $http(options).then(function (result) { - if (!result.data) { - return []; - } - return result.data.result; - }); + + return this.doZabbixAPIRequest(data); }; // Get the list of applications ZabbixAPIDatasource.prototype.performAppSuggestQuery = function(hostid) { - var options = { - url : this.url, - method : 'POST', - data: { - jsonrpc: '2.0', - method: 'application.get', - params: { - output: ['name'], - sortfield: 'name', - hostids: hostid - }, - auth: this.auth, - id: 1 + var data = { + jsonrpc: '2.0', + method: 'application.get', + params: { + output: ['name'], + sortfield: 'name', + hostids: hostid }, + auth: this.auth, + id: 1 }; - return $http(options).then(function (result) { - if (!result.data) { - return []; - } - return result.data.result; - }); + + return this.doZabbixAPIRequest(data); }; // Get the list of host items ZabbixAPIDatasource.prototype.performItemSuggestQuery = function(hostid, applicationid) { - var options = { - url : this.url, - method : 'POST', - data: { - jsonrpc: '2.0', - method: 'item.get', - params: { - output: ['name', 'key_', 'value_type', 'delay'], - sortfield: 'name', - hostids: hostid - }, - auth: this.auth, - id: 1 + var data = { + jsonrpc: '2.0', + method: 'item.get', + params: { + output: ['name', 'key_', 'value_type', 'delay'], + sortfield: 'name', + hostids: hostid, + webitems: true, //Include web items in the result + filter: { + value_type: [0,3] + } }, + auth: this.auth, + id: 1 }; // If application selected return only relative items if (applicationid) { - options.data.params.applicationids = applicationid; + data.params.applicationids = applicationid; } - return $http(options).then(function (result) { - if (!result.data) { - return []; - } - return result.data.result; - }); + + return this.doZabbixAPIRequest(data); }; diff --git a/zabbix/partials/query.editor.html b/zabbix/partials/query.editor.html index faa2b26f6..35b4e8d66 100644 --- a/zabbix/partials/query.editor.html +++ b/zabbix/partials/query.editor.html @@ -1,133 +1,132 @@ -
-
+
- - +
    +
  • + {{targetLetters[$index]}} +
  • +
  • + + + +
  • +
-
+ + +
-
+
diff --git a/zabbix/queryCtrl.js b/zabbix/queryCtrl.js index f70c2bffb..e367f4937 100644 --- a/zabbix/queryCtrl.js +++ b/zabbix/queryCtrl.js @@ -8,11 +8,6 @@ function (angular, _) { var module = angular.module('grafana.controllers'); var targetLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - var hostGroupList = []; - var hostList = []; - var applicationList = []; - var itemList = []; - module.controller('ZabbixAPITargetCtrl', function($scope) { $scope.init = function() { @@ -25,8 +20,12 @@ function (angular, _) { }; // Update host group, host, application and item lists - //$scope.updateHostGroupList(); - $scope.updateHostList(); + $scope.updateHostGroupList(); + if ($scope.target.hostGroup) { + $scope.updateHostList($scope.target.hostGroup.groupid); + } else { + $scope.updateHostList(); + } if ($scope.target.host) { $scope.updateAppList($scope.target.host.hostid); if ($scope.target.application) { @@ -136,7 +135,6 @@ function (angular, _) { $scope.metric.hostGroupList = series; if ($scope.target.hostGroup) { $scope.target.hostGroup = $scope.metric.hostGroupList.filter(function (item, index, array) { - // Find selected host in metric.hostList return (item.groupid == $scope.target.hostGroup.groupid); }).pop(); @@ -151,11 +149,13 @@ function (angular, _) { $scope.updateHostList = function(groupid) { $scope.datasource.performHostSuggestQuery(groupid).then(function (series) { $scope.metric.hostList = series; - $scope.target.host = $scope.metric.hostList.filter(function (item, index, array) { - // Find selected host in metric.hostList - return (item.hostid == $scope.target.host.hostid); - }).pop(); + if ($scope.target.host) { + $scope.target.host = $scope.metric.hostList.filter(function (item, index, array) { + // Find selected host in metric.hostList + return (item.hostid == $scope.target.host.hostid); + }).pop(); + } }); }; @@ -168,7 +168,6 @@ function (angular, _) { $scope.metric.applicationList = series; if ($scope.target.application) { $scope.target.application = $scope.metric.applicationList.filter(function (item, index, array) { - // Find selected application in metric.hostList return (item.applicationid == $scope.target.application.applicationid); }).pop(); @@ -193,11 +192,12 @@ function (angular, _) { item.expandedName = expandItemName(item); } }); - $scope.target.item = $scope.metric.itemList.filter(function (item, index, array) { - - // Find selected item in metric.hostList - return (item.itemid == $scope.target.item.itemid); - }).pop(); + if ($scope.target.item) { + $scope.target.item = $scope.metric.itemList.filter(function (item, index, array) { + // Find selected item in metric.hostList + return (item.itemid == $scope.target.item.itemid); + }).pop(); + } }); } else { $scope.metric.itemList = [];