Merge branch 'master' into metric-functions
This commit is contained in:
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,15 +1,33 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- `consolidateBy` function, which allows to specify aggregation function for time series data.
|
||||||
|
- Direct DB Connection, which allows to use existing SQL data source for querying history data directly from Zabbix database.
|
||||||
|
- **Docs**: Direct DB Connection reference and configuration.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- IT Services query editor. Now user able to select multiple services by using regex, [#415](https://github.com/alexanderzobnin/grafana-zabbix/issues/415)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Item name expanding when key contains commas in quoted params, like my_key["a=1,b=2",c,d]
|
- Template variables support in annotations and triggers panel (trigger name field), [#428](https://github.com/alexanderzobnin/grafana-zabbix/issues/428)
|
||||||
- Incorrect points order when trends are used [#202](https://github.com/alexanderzobnin/grafana-zabbix/issues/202)
|
- Parsing of template variable query with braces, [#432](https://github.com/alexanderzobnin/grafana-zabbix/issues/432)
|
||||||
- Triggers panel styles for light theme
|
|
||||||
- Bug with text metrics when singlestat or table shows NaN, [#325](https://github.com/alexanderzobnin/grafana-zabbix/issues/325)
|
|
||||||
|
## [3.5.1] - 2017-07-10
|
||||||
|
### Fixed
|
||||||
|
- Bug with alerting when template queries are used, [#424](https://github.com/alexanderzobnin/grafana-zabbix/issues/424)
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.0] - 2017-07-05
|
||||||
|
### Added
|
||||||
|
- rate() function, which calculates per-second rate for growing counters.
|
||||||
|
- Benchmarks for time series functions. Used [Benchmark.js](https://github.com/bestiejs/benchmark.js) library.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Template query format. New format is `{group}{host}{app}{item}`. It allows to use names with dot. Updated
|
- Template query format. New format is `{group}{host}{app}{item}`. It allows to use names with dot. Updated
|
||||||
@@ -19,9 +37,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Improved performance of groupBy() functions (at 6-10x faster than old).
|
- Improved performance of groupBy() functions (at 6-10x faster than old).
|
||||||
- Fill empty intervals by _null_ when aggregations are used, [#388](https://github.com/alexanderzobnin/grafana-zabbix/issues/388)
|
- Fill empty intervals by _null_ when aggregations are used, [#388](https://github.com/alexanderzobnin/grafana-zabbix/issues/388)
|
||||||
|
|
||||||
### Added
|
### Fixed
|
||||||
- rate() function, which calculates per-second rate for growing counters.
|
- Item name expanding when key contains commas in quoted params, like my_key["a=1,b=2",c,d]
|
||||||
- Benchmarks for time series functions. Used [Benchmark.js](https://github.com/bestiejs/benchmark.js) library.
|
- Incorrect points order when trends are used [#202](https://github.com/alexanderzobnin/grafana-zabbix/issues/202)
|
||||||
|
- Triggers panel styles for light theme
|
||||||
|
- Bug with text metrics when singlestat or table shows NaN, [#325](https://github.com/alexanderzobnin/grafana-zabbix/issues/325)
|
||||||
|
|
||||||
|
|
||||||
## [3.4.0] - 2017-05-17
|
## [3.4.0] - 2017-05-17
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Zabbix plugin for Grafana
|
# Zabbix plugin for Grafana
|
||||||
|
|
||||||
[](https://github.com/alexanderzobnin/grafana-zabbix/releases)
|
[](https://github.com/alexanderzobnin/grafana-zabbix/releases)
|
||||||
|
[](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/CHANGELOG.md)
|
||||||
[](http://docs.grafana-zabbix.org)
|
[](http://docs.grafana-zabbix.org)
|
||||||
[](https://twitter.com/alexanderzobnin)
|
[](https://twitter.com/alexanderzobnin)
|
||||||
[](https://paypal.me/alexanderzobnin/10)
|
[](https://paypal.me/alexanderzobnin/10)
|
||||||
|
|||||||
32
dist/README.md
vendored
32
dist/README.md
vendored
@@ -1,28 +1,22 @@
|
|||||||
## Zabbix plugin for Grafana
|
# Zabbix plugin for Grafana
|
||||||
|
|
||||||
|
[](https://github.com/alexanderzobnin/grafana-zabbix/releases)
|
||||||
|
[](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/CHANGELOG.md)
|
||||||
[](http://docs.grafana-zabbix.org)
|
[](http://docs.grafana-zabbix.org)
|
||||||
[](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/LICENSE)
|
|
||||||
|
|
||||||
Zabbix plugin allows to show different type of data from [Zabbix](http://www.zabbix.com/)
|
Visualize your Zabbix metrics with the leading open source software for time series analytics.
|
||||||
monitoring system.
|
|
||||||
|
|
||||||
### Live Demo
|
### Live Demo
|
||||||
|
|
||||||
Check out the [live demo](http://play.grafana-zabbix.org/) with dashboard examples.
|
See all features overview and dashboards examples at Grafana-Zabbix [Live demo](http://play.grafana-zabbix.org) site.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
#### Flexible metric editor
|
- Select multiple metrics [by using Regex](http://docs.grafana-zabbix.org/guides/gettingstarted/#multiple-items-on-one-graph)
|
||||||
* Regex-based metric filtering
|
- Create interactive and reusable dashboards with [template variables](http://docs.grafana-zabbix.org/guides/templating/)
|
||||||
* Client-side data processing functions
|
- Show events on graphs with [Annotations](http://docs.grafana.org/reference/annotations/)
|
||||||
* Template variables support
|
- Display active problems with Triggers panel
|
||||||
|
- Transform and shape your data with [metric processing functions](http://docs.grafana-zabbix.org/reference/functions/) (Avg, Median, Min, Max, Multiply, Summarize, Time shift, Alias)
|
||||||
#### Templated dashboards support
|
- Find problems faster with [Alerting](http://docs.grafana-zabbix.org/reference/alerting/) feature
|
||||||
Group, host, application or item names can be replaced with a template variable. This allows you to create generic dashboards that can quickly be changed to show stats for a specific cluster, server or application.
|
- Mix metrics from multiple data sources in the same dashboard or even graph
|
||||||
|
- Discover and share [dashboards](https://grafana.com/dashboards) in the official library
|
||||||
#### Annotations support
|
|
||||||
* Display zabbix events on graphs
|
|
||||||
* Show acknowledges for problems
|
|
||||||
|
|
||||||
#### Triggers panel
|
|
||||||
Panel for showing Zabbix triggers (like Last 20 issues) with some customizable features.
|
|
||||||
|
|||||||
74
dist/datasource-zabbix/config.controller.js
vendored
Normal file
74
dist/datasource-zabbix/config.controller.js
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
System.register(['lodash'], function (_export, _context) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _, _createClass, SUPPORTED_SQL_DS, defaultConfig, ZabbixDSConfigController;
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) {
|
||||||
|
if (!(instance instanceof Constructor)) {
|
||||||
|
throw new TypeError("Cannot call a class as a function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setters: [function (_lodash) {
|
||||||
|
_ = _lodash.default;
|
||||||
|
}],
|
||||||
|
execute: function () {
|
||||||
|
_createClass = function () {
|
||||||
|
function defineProperties(target, props) {
|
||||||
|
for (var i = 0; i < props.length; i++) {
|
||||||
|
var descriptor = props[i];
|
||||||
|
descriptor.enumerable = descriptor.enumerable || false;
|
||||||
|
descriptor.configurable = true;
|
||||||
|
if ("value" in descriptor) descriptor.writable = true;
|
||||||
|
Object.defineProperty(target, descriptor.key, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (Constructor, protoProps, staticProps) {
|
||||||
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||||
|
if (staticProps) defineProperties(Constructor, staticProps);
|
||||||
|
return Constructor;
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
SUPPORTED_SQL_DS = ['mysql'];
|
||||||
|
defaultConfig = {
|
||||||
|
dbConnection: {
|
||||||
|
enable: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_export('ZabbixDSConfigController', ZabbixDSConfigController = function () {
|
||||||
|
/** @ngInject */
|
||||||
|
function ZabbixDSConfigController($scope, $injector, datasourceSrv) {
|
||||||
|
_classCallCheck(this, ZabbixDSConfigController);
|
||||||
|
|
||||||
|
this.datasourceSrv = datasourceSrv;
|
||||||
|
|
||||||
|
_.defaults(this.current.jsonData, defaultConfig);
|
||||||
|
this.sqlDataSources = this.getSupportedSQLDataSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
_createClass(ZabbixDSConfigController, [{
|
||||||
|
key: 'getSupportedSQLDataSources',
|
||||||
|
value: function getSupportedSQLDataSources() {
|
||||||
|
var datasources = this.datasourceSrv.getAll();
|
||||||
|
return _.filter(datasources, function (ds) {
|
||||||
|
return _.includes(SUPPORTED_SQL_DS, ds.type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return ZabbixDSConfigController;
|
||||||
|
}());
|
||||||
|
|
||||||
|
_export('ZabbixDSConfigController', ZabbixDSConfigController);
|
||||||
|
|
||||||
|
ZabbixDSConfigController.templateUrl = 'datasource-zabbix/partials/config.html';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=config.controller.js.map
|
||||||
1
dist/datasource-zabbix/config.controller.js.map
vendored
Normal file
1
dist/datasource-zabbix/config.controller.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["../../src/datasource-zabbix/config.controller.js"],"names":["_","SUPPORTED_SQL_DS","defaultConfig","dbConnection","enable","ZabbixDSConfigController","$scope","$injector","datasourceSrv","defaults","current","jsonData","sqlDataSources","getSupportedSQLDataSources","datasources","getAll","filter","includes","ds","type","templateUrl"],"mappings":";;;;;;;;;;;;;;;AAAOA,O;;;;;;;;;;;;;;;;;;;;;AAEDC,sB,GAAmB,CAAC,OAAD,C;AAEnBC,mB,GAAgB;AACpBC,sBAAc;AACZC,kBAAQ;AADI;AADM,O;;0CAMTC,wB;AACX;AACA,0CAAYC,MAAZ,EAAoBC,SAApB,EAA+BC,aAA/B,EAA8C;AAAA;;AAC5C,eAAKA,aAAL,GAAqBA,aAArB;;AAEAR,YAAES,QAAF,CAAW,KAAKC,OAAL,CAAaC,QAAxB,EAAkCT,aAAlC;AACA,eAAKU,cAAL,GAAsB,KAAKC,0BAAL,EAAtB;AACD;;;;uDAE4B;AAC3B,gBAAIC,cAAc,KAAKN,aAAL,CAAmBO,MAAnB,EAAlB;AACA,mBAAOf,EAAEgB,MAAF,CAASF,WAAT,EAAsB,cAAM;AACjC,qBAAOd,EAAEiB,QAAF,CAAWhB,gBAAX,EAA6BiB,GAAGC,IAAhC,CAAP;AACD,aAFM,CAAP;AAGD;;;;;;;;AAGHd,+BAAyBe,WAAzB,GAAuC,wCAAvC","file":"config.controller.js","sourcesContent":["import _ from 'lodash';\n\nconst SUPPORTED_SQL_DS = ['mysql'];\n\nconst defaultConfig = {\n dbConnection: {\n enable: false,\n }\n};\n\nexport class ZabbixDSConfigController {\n /** @ngInject */\n constructor($scope, $injector, datasourceSrv) {\n this.datasourceSrv = datasourceSrv;\n\n _.defaults(this.current.jsonData, defaultConfig);\n this.sqlDataSources = this.getSupportedSQLDataSources();\n }\n\n getSupportedSQLDataSources() {\n let datasources = this.datasourceSrv.getAll();\n return _.filter(datasources, ds => {\n return _.includes(SUPPORTED_SQL_DS, ds.type);\n });\n }\n}\n\nZabbixDSConfigController.templateUrl = 'datasource-zabbix/partials/config.html';\n"]}
|
||||||
10
dist/datasource-zabbix/constants.js
vendored
10
dist/datasource-zabbix/constants.js
vendored
@@ -3,7 +3,7 @@
|
|||||||
System.register([], function (_export, _context) {
|
System.register([], function (_export, _context) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var MODE_METRICS, MODE_TEXT, MODE_ITSERVICE, SEV_NOT_CLASSIFIED, SEV_INFORMATION, SEV_WARNING, SEV_AVERAGE, SEV_HIGH, SEV_DISASTER, SHOW_ALL_TRIGGERS, SHOW_ALL_EVENTS, SHOW_OK_EVENTS, DATAPOINT_VALUE, DATAPOINT_TS;
|
var MODE_METRICS, MODE_ITSERVICE, MODE_TEXT, MODE_ITEMID, SEV_NOT_CLASSIFIED, SEV_INFORMATION, SEV_WARNING, SEV_AVERAGE, SEV_HIGH, SEV_DISASTER, SHOW_ALL_TRIGGERS, SHOW_ALL_EVENTS, SHOW_OK_EVENTS, DATAPOINT_VALUE, DATAPOINT_TS;
|
||||||
return {
|
return {
|
||||||
setters: [],
|
setters: [],
|
||||||
execute: function () {
|
execute: function () {
|
||||||
@@ -11,13 +11,17 @@ System.register([], function (_export, _context) {
|
|||||||
|
|
||||||
_export("MODE_METRICS", MODE_METRICS);
|
_export("MODE_METRICS", MODE_METRICS);
|
||||||
|
|
||||||
|
_export("MODE_ITSERVICE", MODE_ITSERVICE = 1);
|
||||||
|
|
||||||
|
_export("MODE_ITSERVICE", MODE_ITSERVICE);
|
||||||
|
|
||||||
_export("MODE_TEXT", MODE_TEXT = 2);
|
_export("MODE_TEXT", MODE_TEXT = 2);
|
||||||
|
|
||||||
_export("MODE_TEXT", MODE_TEXT);
|
_export("MODE_TEXT", MODE_TEXT);
|
||||||
|
|
||||||
_export("MODE_ITSERVICE", MODE_ITSERVICE = 1);
|
_export("MODE_ITEMID", MODE_ITEMID = 3);
|
||||||
|
|
||||||
_export("MODE_ITSERVICE", MODE_ITSERVICE);
|
_export("MODE_ITEMID", MODE_ITEMID);
|
||||||
|
|
||||||
_export("SEV_NOT_CLASSIFIED", SEV_NOT_CLASSIFIED = 0);
|
_export("SEV_NOT_CLASSIFIED", SEV_NOT_CLASSIFIED = 0);
|
||||||
|
|
||||||
|
|||||||
2
dist/datasource-zabbix/constants.js.map
vendored
2
dist/datasource-zabbix/constants.js.map
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["../../src/datasource-zabbix/constants.js"],"names":["MODE_METRICS","MODE_TEXT","MODE_ITSERVICE","SEV_NOT_CLASSIFIED","SEV_INFORMATION","SEV_WARNING","SEV_AVERAGE","SEV_HIGH","SEV_DISASTER","SHOW_ALL_TRIGGERS","SHOW_ALL_EVENTS","SHOW_OK_EVENTS","DATAPOINT_VALUE","DATAPOINT_TS"],"mappings":";;;;;;;;;8BACaA,Y,GAAe,C;;;;2BACfC,S,GAAY,C;;;;gCACZC,c,GAAiB,C;;;;oCAGjBC,kB,GAAqB,C;;;;iCACrBC,e,GAAkB,C;;;;6BAClBC,W,GAAc,C;;;;6BACdC,W,GAAc,C;;;;0BACdC,Q,GAAW,C;;;;8BACXC,Y,GAAe,C;;;;mCAEfC,iB,GAAoB,CAAC,CAAD,EAAI,CAAJ,C;;;;iCACpBC,e,GAAkB,CAAC,CAAD,EAAI,CAAJ,C;;;;gCAClBC,c,GAAiB,C;;;;iCAGjBC,e,GAAkB,C;;;;8BAClBC,Y,GAAe,C","file":"constants.js","sourcesContent":["// Editor modes\nexport const MODE_METRICS = 0;\nexport const MODE_TEXT = 2;\nexport const MODE_ITSERVICE = 1;\n\n// Triggers severity\nexport const SEV_NOT_CLASSIFIED = 0;\nexport const SEV_INFORMATION = 1;\nexport const SEV_WARNING = 2;\nexport const SEV_AVERAGE = 3;\nexport const SEV_HIGH = 4;\nexport const SEV_DISASTER = 5;\n\nexport const SHOW_ALL_TRIGGERS = [0, 1];\nexport const SHOW_ALL_EVENTS = [0, 1];\nexport const SHOW_OK_EVENTS = 1;\n\n// Data point\nexport const DATAPOINT_VALUE = 0;\nexport const DATAPOINT_TS = 1;\n"]}
|
{"version":3,"sources":["../../src/datasource-zabbix/constants.js"],"names":["MODE_METRICS","MODE_ITSERVICE","MODE_TEXT","MODE_ITEMID","SEV_NOT_CLASSIFIED","SEV_INFORMATION","SEV_WARNING","SEV_AVERAGE","SEV_HIGH","SEV_DISASTER","SHOW_ALL_TRIGGERS","SHOW_ALL_EVENTS","SHOW_OK_EVENTS","DATAPOINT_VALUE","DATAPOINT_TS"],"mappings":";;;;;;;;;8BACaA,Y,GAAe,C;;;;gCACfC,c,GAAiB,C;;;;2BACjBC,S,GAAY,C;;;;6BACZC,W,GAAc,C;;;;oCAGdC,kB,GAAqB,C;;;;iCACrBC,e,GAAkB,C;;;;6BAClBC,W,GAAc,C;;;;6BACdC,W,GAAc,C;;;;0BACdC,Q,GAAW,C;;;;8BACXC,Y,GAAe,C;;;;mCAEfC,iB,GAAoB,CAAC,CAAD,EAAI,CAAJ,C;;;;iCACpBC,e,GAAkB,CAAC,CAAD,EAAI,CAAJ,C;;;;gCAClBC,c,GAAiB,C;;;;iCAGjBC,e,GAAkB,C;;;;8BAClBC,Y,GAAe,C","file":"constants.js","sourcesContent":["// Editor modes\nexport const MODE_METRICS = 0;\nexport const MODE_ITSERVICE = 1;\nexport const MODE_TEXT = 2;\nexport const MODE_ITEMID = 3;\n\n// Triggers severity\nexport const SEV_NOT_CLASSIFIED = 0;\nexport const SEV_INFORMATION = 1;\nexport const SEV_WARNING = 2;\nexport const SEV_AVERAGE = 3;\nexport const SEV_HIGH = 4;\nexport const SEV_DISASTER = 5;\n\nexport const SHOW_ALL_TRIGGERS = [0, 1];\nexport const SHOW_ALL_EVENTS = [0, 1];\nexport const SHOW_OK_EVENTS = 1;\n\n// Data point\nexport const DATAPOINT_VALUE = 0;\nexport const DATAPOINT_TS = 1;\n"]}
|
||||||
220
dist/datasource-zabbix/datasource.js
vendored
220
dist/datasource-zabbix/datasource.js
vendored
@@ -23,10 +23,23 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConsolidateBy(target) {
|
||||||
|
var consolidateBy = 'avg';
|
||||||
|
var funcDef = _.find(target.functions, function (func) {
|
||||||
|
return func.def.name === 'consolidateBy';
|
||||||
|
});
|
||||||
|
if (funcDef && funcDef.params && funcDef.params.length) {
|
||||||
|
consolidateBy = funcDef.params[0];
|
||||||
|
}
|
||||||
|
return consolidateBy;
|
||||||
|
}
|
||||||
|
|
||||||
function downsampleSeries(timeseries_data, options) {
|
function downsampleSeries(timeseries_data, options) {
|
||||||
|
var defaultAgg = dataProcessor.aggregationFunctions['avg'];
|
||||||
|
var consolidateByFunc = dataProcessor.aggregationFunctions[options.consolidateBy] || defaultAgg;
|
||||||
return _.map(timeseries_data, function (timeseries) {
|
return _.map(timeseries_data, function (timeseries) {
|
||||||
if (timeseries.datapoints.length > options.maxDataPoints) {
|
if (timeseries.datapoints.length > options.maxDataPoints) {
|
||||||
timeseries.datapoints = dataProcessor.groupBy(options.interval, dataProcessor.AVERAGE, timeseries.datapoints);
|
timeseries.datapoints = dataProcessor.groupBy(options.interval, consolidateByFunc, timeseries.datapoints);
|
||||||
}
|
}
|
||||||
return timeseries;
|
return timeseries;
|
||||||
});
|
});
|
||||||
@@ -58,6 +71,13 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
return '(' + escapedValues.join('|') + ')';
|
return '(' + escapedValues.join('|') + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zabbixItemIdsTemplateFormat(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If template variables are used in request, replace it using regex format
|
* If template variables are used in request, replace it using regex format
|
||||||
* and wrap with '/' for proper multi-value work. Example:
|
* and wrap with '/' for proper multi-value work. Example:
|
||||||
@@ -191,6 +211,9 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
this.dashboardSrv = dashboardSrv;
|
this.dashboardSrv = dashboardSrv;
|
||||||
this.zabbixAlertingSrv = zabbixAlertingSrv;
|
this.zabbixAlertingSrv = zabbixAlertingSrv;
|
||||||
|
|
||||||
|
// Use custom format for template variables
|
||||||
|
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
||||||
|
|
||||||
// General data source settings
|
// General data source settings
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
this.url = instanceSettings.url;
|
this.url = instanceSettings.url;
|
||||||
@@ -215,10 +238,21 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
this.addThresholds = instanceSettings.jsonData.addThresholds;
|
this.addThresholds = instanceSettings.jsonData.addThresholds;
|
||||||
this.alertingMinSeverity = instanceSettings.jsonData.alertingMinSeverity || c.SEV_WARNING;
|
this.alertingMinSeverity = instanceSettings.jsonData.alertingMinSeverity || c.SEV_WARNING;
|
||||||
|
|
||||||
this.zabbix = new Zabbix(this.url, this.username, this.password, this.basicAuth, this.withCredentials, this.cacheTTL);
|
// Direct DB Connection options
|
||||||
|
this.enableDirectDBConnection = instanceSettings.jsonData.dbConnection.enable;
|
||||||
|
this.sqlDatasourceId = instanceSettings.jsonData.dbConnection.datasourceId;
|
||||||
|
|
||||||
// Use custom format for template variables
|
var zabbixOptions = {
|
||||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
basicAuth: this.basicAuth,
|
||||||
|
withCredentials: this.withCredentials,
|
||||||
|
cacheTTL: this.cacheTTL,
|
||||||
|
enableDirectDBConnection: this.enableDirectDBConnection,
|
||||||
|
sqlDatasourceId: this.sqlDatasourceId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.zabbix = new Zabbix(this.url, zabbixOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
@@ -253,6 +287,11 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
|
|
||||||
// Create request for each target
|
// Create request for each target
|
||||||
var promises = _.map(options.targets, function (t) {
|
var promises = _.map(options.targets, function (t) {
|
||||||
|
// Don't request undefined and hidden targets
|
||||||
|
if (t.hide) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
|
var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
|
||||||
var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);
|
var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);
|
||||||
|
|
||||||
@@ -276,7 +315,7 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
var useTrends = _this.isUseTrends(timeRange);
|
var useTrends = _this.isUseTrends(timeRange);
|
||||||
|
|
||||||
// Metrics or Text query mode
|
// Metrics or Text query mode
|
||||||
if (target.mode !== c.MODE_ITSERVICE) {
|
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT || target.mode === c.MODE_ITEMID) {
|
||||||
// Migrate old targets
|
// Migrate old targets
|
||||||
target = migrations.migrate(target);
|
target = migrations.migrate(target);
|
||||||
|
|
||||||
@@ -289,19 +328,12 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
return _this.queryNumericData(target, timeRange, useTrends, options);
|
return _this.queryNumericData(target, timeRange, useTrends, options);
|
||||||
} else if (target.mode === c.MODE_TEXT) {
|
} else if (target.mode === c.MODE_TEXT) {
|
||||||
return _this.queryTextData(target, timeRange);
|
return _this.queryTextData(target, timeRange);
|
||||||
|
} else if (target.mode === c.MODE_ITEMID) {
|
||||||
|
return _this.queryItemIdData(target, timeRange, useTrends, options);
|
||||||
}
|
}
|
||||||
}
|
} else if (target.mode === c.MODE_ITSERVICE) {
|
||||||
|
|
||||||
// IT services mode
|
// IT services mode
|
||||||
else if (target.mode === c.MODE_ITSERVICE) {
|
return _this.queryITServiceData(target, timeRange, options);
|
||||||
// Don't show undefined and hidden targets
|
|
||||||
if (target.hide || !target.itservice || !target.slaProperty) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return _this.zabbix.getSLA(target.itservice.serviceid, timeRange).then(function (slaObject) {
|
|
||||||
return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -315,19 +347,33 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
value: function queryNumericData(target, timeRange, useTrends, options) {
|
value: function queryNumericData(target, timeRange, useTrends, options) {
|
||||||
var _this2 = this;
|
var _this2 = this;
|
||||||
|
|
||||||
var _timeRange = _slicedToArray(timeRange, 2),
|
|
||||||
timeFrom = _timeRange[0],
|
|
||||||
timeTo = _timeRange[1];
|
|
||||||
|
|
||||||
var getItemOptions = {
|
var getItemOptions = {
|
||||||
itemtype: 'num'
|
itemtype: 'num'
|
||||||
};
|
};
|
||||||
return this.zabbix.getItemsFromTarget(target, getItemOptions).then(function (items) {
|
return this.zabbix.getItemsFromTarget(target, getItemOptions).then(function (items) {
|
||||||
|
return _this2.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'queryNumericDataForItems',
|
||||||
|
value: function queryNumericDataForItems(items, target, timeRange, useTrends, options) {
|
||||||
|
var _this3 = this;
|
||||||
|
|
||||||
|
var _timeRange = _slicedToArray(timeRange, 2),
|
||||||
|
timeFrom = _timeRange[0],
|
||||||
|
timeTo = _timeRange[1];
|
||||||
|
|
||||||
var getHistoryPromise = void 0;
|
var getHistoryPromise = void 0;
|
||||||
|
options.consolidateBy = getConsolidateBy(target);
|
||||||
|
|
||||||
if (useTrends) {
|
if (useTrends) {
|
||||||
var valueType = _this2.getTrendValueType(target);
|
if (this.enableDirectDBConnection) {
|
||||||
getHistoryPromise = _this2.zabbix.getTrend(items, timeFrom, timeTo).then(function (history) {
|
getHistoryPromise = this.zabbix.getTrendsDB(items, timeFrom, timeTo, options).then(function (history) {
|
||||||
|
return _this3.zabbix.dbConnector.handleGrafanaTSResponse(history, items);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var valueType = this.getTrendValueType(target);
|
||||||
|
getHistoryPromise = this.zabbix.getTrend(items, timeFrom, timeTo).then(function (history) {
|
||||||
return responseHandler.handleTrends(history, items, valueType);
|
return responseHandler.handleTrends(history, items, valueType);
|
||||||
}).then(function (timeseries) {
|
}).then(function (timeseries) {
|
||||||
// Sort trend data, issue #202
|
// Sort trend data, issue #202
|
||||||
@@ -336,19 +382,24 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
return point[c.DATAPOINT_TS];
|
return point[c.DATAPOINT_TS];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return timeseries;
|
return timeseries;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use history
|
// Use history
|
||||||
getHistoryPromise = _this2.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
if (this.enableDirectDBConnection) {
|
||||||
|
getHistoryPromise = this.zabbix.getHistoryDB(items, timeFrom, timeTo, options).then(function (history) {
|
||||||
|
return _this3.zabbix.dbConnector.handleGrafanaTSResponse(history, items);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getHistoryPromise = this.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
||||||
return responseHandler.handleHistory(history, items);
|
return responseHandler.handleHistory(history, items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return getHistoryPromise;
|
return getHistoryPromise.then(function (timeseries) {
|
||||||
}).then(function (timeseries) {
|
return _this3.applyDataProcessingFunctions(timeseries, target);
|
||||||
return _this2.applyDataProcessingFunctions(timeseries, target);
|
|
||||||
}).then(function (timeseries) {
|
}).then(function (timeseries) {
|
||||||
return downsampleSeries(timeseries, options);
|
return downsampleSeries(timeseries, options);
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
@@ -427,7 +478,7 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
}, {
|
}, {
|
||||||
key: 'queryTextData',
|
key: 'queryTextData',
|
||||||
value: function queryTextData(target, timeRange) {
|
value: function queryTextData(target, timeRange) {
|
||||||
var _this3 = this;
|
var _this4 = this;
|
||||||
|
|
||||||
var _timeRange2 = _slicedToArray(timeRange, 2),
|
var _timeRange2 = _slicedToArray(timeRange, 2),
|
||||||
timeFrom = _timeRange2[0],
|
timeFrom = _timeRange2[0],
|
||||||
@@ -438,7 +489,7 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
};
|
};
|
||||||
return this.zabbix.getItemsFromTarget(target, options).then(function (items) {
|
return this.zabbix.getItemsFromTarget(target, options).then(function (items) {
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
return _this3.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
return _this4.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
||||||
return responseHandler.handleText(history, items, target);
|
return responseHandler.handleText(history, items, target);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -446,15 +497,79 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'queryItemIdData',
|
||||||
|
value: function queryItemIdData(target, timeRange, useTrends, options) {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
var itemids = target.itemids;
|
||||||
|
itemids = this.templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||||
|
itemids = _.map(itemids.split(','), function (itemid) {
|
||||||
|
return itemid.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!itemids) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.zabbix.getItemsByIDs(itemids).then(function (items) {
|
||||||
|
return _this5.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'queryITServiceData',
|
||||||
|
value: function queryITServiceData(target, timeRange, options) {
|
||||||
|
var _this6 = this;
|
||||||
|
|
||||||
|
// Don't show undefined and hidden targets
|
||||||
|
if (target.hide || !target.itservice && !target.itServiceFilter || !target.slaProperty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var itServiceIds = [];
|
||||||
|
var itServices = [];
|
||||||
|
var itServiceFilter = void 0;
|
||||||
|
var isOldVersion = target.itservice && !target.itServiceFilter;
|
||||||
|
|
||||||
|
if (isOldVersion) {
|
||||||
|
// Backward compatibility
|
||||||
|
itServiceFilter = '/.*/';
|
||||||
|
} else {
|
||||||
|
itServiceFilter = this.replaceTemplateVars(target.itServiceFilter, options.scopedVars);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.zabbix.getITServices(itServiceFilter).then(function (itservices) {
|
||||||
|
itServices = itservices;
|
||||||
|
if (isOldVersion) {
|
||||||
|
itServices = _.filter(itServices, { 'serviceid': target.itservice.serviceid });
|
||||||
|
}
|
||||||
|
|
||||||
|
itServiceIds = _.map(itServices, 'serviceid');
|
||||||
|
return itServiceIds;
|
||||||
|
}).then(function (serviceids) {
|
||||||
|
return _this6.zabbix.getSLA(serviceids, timeRange);
|
||||||
|
}).then(function (slaResponse) {
|
||||||
|
return _.map(itServiceIds, function (serviceid) {
|
||||||
|
var itservice = _.find(itServices, { 'serviceid': serviceid });
|
||||||
|
return responseHandler.handleSLAResponse(itservice, target.slaProperty, slaResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'testDatasource',
|
key: 'testDatasource',
|
||||||
value: function testDatasource() {
|
value: function testDatasource() {
|
||||||
var _this4 = this;
|
var _this7 = this;
|
||||||
|
|
||||||
var zabbixVersion = void 0;
|
var zabbixVersion = void 0;
|
||||||
return this.zabbix.getVersion().then(function (version) {
|
return this.zabbix.getVersion().then(function (version) {
|
||||||
zabbixVersion = version;
|
zabbixVersion = version;
|
||||||
return _this4.zabbix.login();
|
return _this7.zabbix.login();
|
||||||
|
}).then(function () {
|
||||||
|
if (_this7.enableDirectDBConnection) {
|
||||||
|
return _this7.zabbix.dbConnector.testSQLDataSource();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
@@ -468,6 +583,12 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
title: error.message,
|
title: error.message,
|
||||||
message: error.data
|
message: error.data
|
||||||
};
|
};
|
||||||
|
} else if (error.data && error.data.message) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
title: "Connection failed",
|
||||||
|
message: error.data.message
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
@@ -480,14 +601,14 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
}, {
|
}, {
|
||||||
key: 'metricFindQuery',
|
key: 'metricFindQuery',
|
||||||
value: function metricFindQuery(query) {
|
value: function metricFindQuery(query) {
|
||||||
var _this5 = this;
|
var _this8 = this;
|
||||||
|
|
||||||
var result = void 0;
|
var result = void 0;
|
||||||
var parts = [];
|
var parts = [];
|
||||||
|
|
||||||
// Split query. Query structure: group.host.app.item
|
// Split query. Query structure: group.host.app.item
|
||||||
_.each(utils.splitTemplateQuery(query), function (part) {
|
_.each(utils.splitTemplateQuery(query), function (part) {
|
||||||
part = _this5.replaceTemplateVars(part, {});
|
part = _this8.replaceTemplateVars(part, {});
|
||||||
|
|
||||||
// Replace wildcard to regex
|
// Replace wildcard to regex
|
||||||
if (part === '*') {
|
if (part === '*') {
|
||||||
@@ -524,7 +645,7 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
}, {
|
}, {
|
||||||
key: 'annotationQuery',
|
key: 'annotationQuery',
|
||||||
value: function annotationQuery(options) {
|
value: function annotationQuery(options) {
|
||||||
var _this6 = this;
|
var _this9 = this;
|
||||||
|
|
||||||
var timeFrom = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000);
|
var timeFrom = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000);
|
||||||
var timeTo = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000);
|
var timeTo = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000);
|
||||||
@@ -542,13 +663,14 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
return getTriggers.then(function (triggers) {
|
return getTriggers.then(function (triggers) {
|
||||||
|
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
if (utils.isRegex(annotation.trigger)) {
|
var triggerName = _this9.replaceTemplateVars(annotation.trigger, {});
|
||||||
|
if (utils.isRegex(triggerName)) {
|
||||||
triggers = _.filter(triggers, function (trigger) {
|
triggers = _.filter(triggers, function (trigger) {
|
||||||
return utils.buildRegex(annotation.trigger).test(trigger.description);
|
return utils.buildRegex(triggerName).test(trigger.description);
|
||||||
});
|
});
|
||||||
} else if (annotation.trigger) {
|
} else if (triggerName) {
|
||||||
triggers = _.filter(triggers, function (trigger) {
|
triggers = _.filter(triggers, function (trigger) {
|
||||||
return trigger.description === annotation.trigger;
|
return trigger.description === triggerName;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +680,7 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
});
|
});
|
||||||
|
|
||||||
var objectids = _.map(triggers, 'triggerid');
|
var objectids = _.map(triggers, 'triggerid');
|
||||||
return _this6.zabbix.getEvents(objectids, timeFrom, timeTo, showOkEvents).then(function (events) {
|
return _this9.zabbix.getEvents(objectids, timeFrom, timeTo, showOkEvents).then(function (events) {
|
||||||
var indexedTriggers = _.keyBy(triggers, 'triggerid');
|
var indexedTriggers = _.keyBy(triggers, 'triggerid');
|
||||||
|
|
||||||
// Hide acknowledged events if option enabled
|
// Hide acknowledged events if option enabled
|
||||||
@@ -592,21 +714,23 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
}, {
|
}, {
|
||||||
key: 'alertQuery',
|
key: 'alertQuery',
|
||||||
value: function alertQuery(options) {
|
value: function alertQuery(options) {
|
||||||
var _this7 = this;
|
var _this10 = this;
|
||||||
|
|
||||||
var enabled_targets = filterEnabledTargets(options.targets);
|
var enabled_targets = filterEnabledTargets(options.targets);
|
||||||
var getPanelItems = _.map(enabled_targets, function (target) {
|
var getPanelItems = _.map(enabled_targets, function (t) {
|
||||||
return _this7.zabbix.getItemsFromTarget(target, { itemtype: 'num' });
|
var target = _.cloneDeep(t);
|
||||||
|
_this10.replaceTargetVariables(target, options);
|
||||||
|
return _this10.zabbix.getItemsFromTarget(target, { itemtype: 'num' });
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(getPanelItems).then(function (results) {
|
return Promise.all(getPanelItems).then(function (results) {
|
||||||
var items = _.flatten(results);
|
var items = _.flatten(results);
|
||||||
var itemids = _.map(items, 'itemid');
|
var itemids = _.map(items, 'itemid');
|
||||||
|
|
||||||
return _this7.zabbix.getAlerts(itemids);
|
return _this10.zabbix.getAlerts(itemids);
|
||||||
}).then(function (triggers) {
|
}).then(function (triggers) {
|
||||||
triggers = _.filter(triggers, function (trigger) {
|
triggers = _.filter(triggers, function (trigger) {
|
||||||
return trigger.priority >= _this7.alertingMinSeverity;
|
return trigger.priority >= _this10.alertingMinSeverity;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!triggers || triggers.length === 0) {
|
if (!triggers || triggers.length === 0) {
|
||||||
@@ -634,12 +758,12 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
}, {
|
}, {
|
||||||
key: 'replaceTargetVariables',
|
key: 'replaceTargetVariables',
|
||||||
value: function replaceTargetVariables(target, options) {
|
value: function replaceTargetVariables(target, options) {
|
||||||
var _this8 = this;
|
var _this11 = this;
|
||||||
|
|
||||||
var parts = ['group', 'host', 'application', 'item'];
|
var parts = ['group', 'host', 'application', 'item'];
|
||||||
_.forEach(parts, function (p) {
|
_.forEach(parts, function (p) {
|
||||||
if (target[p] && target[p].filter) {
|
if (target[p] && target[p].filter) {
|
||||||
target[p].filter = _this8.replaceTemplateVars(target[p].filter, options.scopedVars);
|
target[p].filter = _this11.replaceTemplateVars(target[p].filter, options.scopedVars);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
||||||
@@ -647,9 +771,9 @@ System.register(['lodash', 'app/core/utils/datemath', './utils', './migrations',
|
|||||||
_.forEach(target.functions, function (func) {
|
_.forEach(target.functions, function (func) {
|
||||||
func.params = _.map(func.params, function (param) {
|
func.params = _.map(func.params, function (param) {
|
||||||
if (typeof param === 'number') {
|
if (typeof param === 'number') {
|
||||||
return +_this8.templateSrv.replace(param.toString(), options.scopedVars);
|
return +_this11.templateSrv.replace(param.toString(), options.scopedVars);
|
||||||
} else {
|
} else {
|
||||||
return _this8.templateSrv.replace(param, options.scopedVars);
|
return _this11.templateSrv.replace(param, options.scopedVars);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
2
dist/datasource-zabbix/datasource.js.map
vendored
2
dist/datasource-zabbix/datasource.js.map
vendored
File diff suppressed because one or more lines are too long
14
dist/datasource-zabbix/module.js
vendored
14
dist/datasource-zabbix/module.js
vendored
@@ -1,9 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
System.register(['./datasource', './query.controller'], function (_export, _context) {
|
System.register(['./datasource', './query.controller', './config.controller'], function (_export, _context) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ZabbixAPIDatasource, ZabbixQueryController, ZabbixConfigController, ZabbixQueryOptionsController, ZabbixAnnotationsQueryController;
|
var ZabbixAPIDatasource, ZabbixQueryController, ZabbixDSConfigController, ZabbixQueryOptionsController, ZabbixAnnotationsQueryController;
|
||||||
|
|
||||||
function _classCallCheck(instance, Constructor) {
|
function _classCallCheck(instance, Constructor) {
|
||||||
if (!(instance instanceof Constructor)) {
|
if (!(instance instanceof Constructor)) {
|
||||||
@@ -16,14 +16,10 @@ System.register(['./datasource', './query.controller'], function (_export, _cont
|
|||||||
ZabbixAPIDatasource = _datasource.ZabbixAPIDatasource;
|
ZabbixAPIDatasource = _datasource.ZabbixAPIDatasource;
|
||||||
}, function (_queryController) {
|
}, function (_queryController) {
|
||||||
ZabbixQueryController = _queryController.ZabbixQueryController;
|
ZabbixQueryController = _queryController.ZabbixQueryController;
|
||||||
|
}, function (_configController) {
|
||||||
|
ZabbixDSConfigController = _configController.ZabbixDSConfigController;
|
||||||
}],
|
}],
|
||||||
execute: function () {
|
execute: function () {
|
||||||
_export('ConfigCtrl', ZabbixConfigController = function ZabbixConfigController() {
|
|
||||||
_classCallCheck(this, ZabbixConfigController);
|
|
||||||
});
|
|
||||||
|
|
||||||
ZabbixConfigController.templateUrl = 'datasource-zabbix/partials/config.html';
|
|
||||||
|
|
||||||
_export('QueryOptionsCtrl', ZabbixQueryOptionsController = function ZabbixQueryOptionsController() {
|
_export('QueryOptionsCtrl', ZabbixQueryOptionsController = function ZabbixQueryOptionsController() {
|
||||||
_classCallCheck(this, ZabbixQueryOptionsController);
|
_classCallCheck(this, ZabbixQueryOptionsController);
|
||||||
});
|
});
|
||||||
@@ -38,7 +34,7 @@ System.register(['./datasource', './query.controller'], function (_export, _cont
|
|||||||
|
|
||||||
_export('Datasource', ZabbixAPIDatasource);
|
_export('Datasource', ZabbixAPIDatasource);
|
||||||
|
|
||||||
_export('ConfigCtrl', ZabbixConfigController);
|
_export('ConfigCtrl', ZabbixDSConfigController);
|
||||||
|
|
||||||
_export('QueryCtrl', ZabbixQueryController);
|
_export('QueryCtrl', ZabbixQueryController);
|
||||||
|
|
||||||
|
|||||||
2
dist/datasource-zabbix/module.js.map
vendored
2
dist/datasource-zabbix/module.js.map
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["../../src/datasource-zabbix/module.js"],"names":["ZabbixAPIDatasource","ZabbixQueryController","ZabbixConfigController","templateUrl","ZabbixQueryOptionsController","ZabbixAnnotationsQueryController"],"mappings":";;;;;;;;;;;;;;;AAAQA,yB,eAAAA,mB;;AACAC,2B,oBAAAA,qB;;;4BAEFC,sB;;;;AACNA,6BAAuBC,WAAvB,GAAqC,wCAArC;;kCAEMC,4B;;;;AACNA,mCAA6BD,WAA7B,GAA2C,+CAA3C;;sCAEME,gC;;;;AACNA,uCAAiCF,WAAjC,GAA+C,oDAA/C;;4BAGEH,mB;;4BACAE,sB;;2BACAD,qB;;kCACAG,4B;;sCACAC,gC","file":"module.js","sourcesContent":["import {ZabbixAPIDatasource} from './datasource';\nimport {ZabbixQueryController} from './query.controller';\n\nclass ZabbixConfigController {}\nZabbixConfigController.templateUrl = 'datasource-zabbix/partials/config.html';\n\nclass ZabbixQueryOptionsController {}\nZabbixQueryOptionsController.templateUrl = 'datasource-zabbix/partials/query.options.html';\n\nclass ZabbixAnnotationsQueryController {}\nZabbixAnnotationsQueryController.templateUrl = 'datasource-zabbix/partials/annotations.editor.html';\n\nexport {\n ZabbixAPIDatasource as Datasource,\n ZabbixConfigController as ConfigCtrl,\n ZabbixQueryController as QueryCtrl,\n ZabbixQueryOptionsController as QueryOptionsCtrl,\n ZabbixAnnotationsQueryController as AnnotationsQueryCtrl\n};\n"]}
|
{"version":3,"sources":["../../src/datasource-zabbix/module.js"],"names":["ZabbixAPIDatasource","ZabbixQueryController","ZabbixDSConfigController","ZabbixQueryOptionsController","templateUrl","ZabbixAnnotationsQueryController"],"mappings":";;;;;;;;;;;;;;;AAAQA,yB,eAAAA,mB;;AACAC,2B,oBAAAA,qB;;AACAC,8B,qBAAAA,wB;;;kCAEFC,4B;;;;AACNA,mCAA6BC,WAA7B,GAA2C,+CAA3C;;sCAEMC,gC;;;;AACNA,uCAAiCD,WAAjC,GAA+C,oDAA/C;;4BAGEJ,mB;;4BACAE,wB;;2BACAD,qB;;kCACAE,4B;;sCACAE,gC","file":"module.js","sourcesContent":["import {ZabbixAPIDatasource} from './datasource';\nimport {ZabbixQueryController} from './query.controller';\nimport {ZabbixDSConfigController} from './config.controller';\n\nclass ZabbixQueryOptionsController {}\nZabbixQueryOptionsController.templateUrl = 'datasource-zabbix/partials/query.options.html';\n\nclass ZabbixAnnotationsQueryController {}\nZabbixAnnotationsQueryController.templateUrl = 'datasource-zabbix/partials/annotations.editor.html';\n\nexport {\n ZabbixAPIDatasource as Datasource,\n ZabbixDSConfigController as ConfigCtrl,\n ZabbixQueryController as QueryCtrl,\n ZabbixQueryOptionsController as QueryOptionsCtrl,\n ZabbixAnnotationsQueryController as AnnotationsQueryCtrl\n};\n"]}
|
||||||
35
dist/datasource-zabbix/partials/config.html
vendored
35
dist/datasource-zabbix/partials/config.html
vendored
@@ -75,18 +75,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form-group">
|
||||||
|
<h3 class="page-heading">Direct DB Connection</h3>
|
||||||
|
<gf-form-switch class="gf-form" label-class="width-12"
|
||||||
|
label="Enable"
|
||||||
|
checked="ctrl.current.jsonData.dbConnection.enable">
|
||||||
|
</gf-form-switch>
|
||||||
|
<div ng-if="ctrl.current.jsonData.dbConnection.enable">
|
||||||
|
<div class="gf-form max-width-20">
|
||||||
|
<span class="gf-form-label width-12">
|
||||||
|
SQL Data Source
|
||||||
|
<info-popover mode="right-normal">
|
||||||
|
Select SQL Data Source for Zabbix database.
|
||||||
|
In order to use this feature you should <a href="/datasources/new" target="_blank">create</a> and
|
||||||
|
configure it first. Zabbix plugin uses this data source for querying history data directly from database.
|
||||||
|
This way usually faster than pulling data from Zabbix API, especially on the wide time ranges, and reduces
|
||||||
|
amount of data transfered.
|
||||||
|
</info-popover>
|
||||||
|
</span>
|
||||||
|
<div class="gf-form-select-wrapper max-width-16">
|
||||||
|
<select class="gf-form-input" ng-model="ctrl.current.jsonData.dbConnection.datasourceId"
|
||||||
|
ng-options="ds.id as ds.name for ds in ctrl.sqlDataSources">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<h3 class="page-heading">Alerting</h3>
|
<h3 class="page-heading">Alerting</h3>
|
||||||
<gf-form-switch class="gf-form" label-class="width-9"
|
<gf-form-switch class="gf-form" label-class="width-12"
|
||||||
label="Enable alerting"
|
label="Enable alerting"
|
||||||
checked="ctrl.current.jsonData.alerting">
|
checked="ctrl.current.jsonData.alerting">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
<gf-form-switch class="gf-form" label-class="width-9"
|
<div ng-if="ctrl.current.jsonData.alerting">
|
||||||
|
<gf-form-switch class="gf-form" label-class="width-12"
|
||||||
label="Add thresholds"
|
label="Add thresholds"
|
||||||
checked="ctrl.current.jsonData.addThresholds">
|
checked="ctrl.current.jsonData.addThresholds">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<span class="gf-form-label width-9">Min severity</span>
|
<span class="gf-form-label width-12">Min severity</span>
|
||||||
<div class="gf-form-select-wrapper max-width-16">
|
<div class="gf-form-select-wrapper max-width-16">
|
||||||
<select class="gf-form-input" ng-model="ctrl.current.jsonData.alertingMinSeverity"
|
<select class="gf-form-input" ng-model="ctrl.current.jsonData.alertingMinSeverity"
|
||||||
ng-options="s.val as s.text for s in [
|
ng-options="s.val as s.text for s in [
|
||||||
@@ -97,3 +125,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<select class="gf-form-input"
|
<select class="gf-form-input"
|
||||||
ng-change="ctrl.switchEditorMode(ctrl.target.mode)"
|
ng-change="ctrl.switchEditorMode(ctrl.target.mode)"
|
||||||
ng-model="ctrl.target.mode"
|
ng-model="ctrl.target.mode"
|
||||||
ng-options="v.mode as v.text for (k, v) in ctrl.editorModes">
|
ng-options="m.mode as m.text for m in ctrl.editorModes">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -17,27 +17,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- IT Service editor -->
|
<!-- IT Service editor -->
|
||||||
<div class="gf-form-inline" ng-show="ctrl.target.mode == 1">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.ITSERVICE">
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">IT Service</label>
|
<label class="gf-form-label query-keyword width-7">IT Service</label>
|
||||||
<div class="gf-form-select-wrapper max-width-20">
|
<input type="text"
|
||||||
<select class="gf-form-input"
|
ng-model="ctrl.target.itServiceFilter"
|
||||||
ng-change="ctrl.selectITService()"
|
bs-typeahead="ctrl.getITServices"
|
||||||
ng-model="ctrl.target.itservice"
|
ng-blur="ctrl.onTargetBlur()"
|
||||||
bs-tooltip="ctrl.target.itservice.name.length > 25 ? ctrl.target.itservice.name : ''"
|
data-min-length=0
|
||||||
ng-options="itservice.name for itservice in ctrl.itserviceList track by itservice.name">
|
data-items=100
|
||||||
<option value="">-- Select IT service --</option>
|
class="gf-form-input"
|
||||||
</select>
|
ng-class="{
|
||||||
</div>
|
'zbx-variable': ctrl.isVariable(ctrl.target.itServiceFilter),
|
||||||
|
'zbx-regex': ctrl.isRegex(ctrl.target.itServiceFilter)
|
||||||
|
}">
|
||||||
|
</input>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword">IT service property</label>
|
<label class="gf-form-label query-keyword">Property</label>
|
||||||
<div class="gf-form-select-wrapper">
|
<div class="gf-form-select-wrapper">
|
||||||
<select class="gf-form-input"
|
<select class="gf-form-input"
|
||||||
ng-change="ctrl.selectITService()"
|
ng-change="ctrl.onTargetBlur()"
|
||||||
ng-model="ctrl.target.slaProperty"
|
ng-model="ctrl.target.slaProperty"
|
||||||
ng-options="slaProperty.name for slaProperty in ctrl.slaPropertyList track by slaProperty.name">
|
ng-options="slaProperty.name for slaProperty in ctrl.slaPropertyList track by slaProperty.name">
|
||||||
<option value="">-- Property --</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-hide="ctrl.target.mode == 1">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.METRICS || ctrl.target.mode == editorMode.TEXT">
|
||||||
<!-- Select Group -->
|
<!-- Select Group -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Group</label>
|
<label class="gf-form-label query-keyword width-7">Group</label>
|
||||||
@@ -83,7 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-hide="ctrl.target.mode == 1">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.METRICS || ctrl.target.mode == editorMode.TEXT">
|
||||||
<!-- Select Application -->
|
<!-- Select Application -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Application</label>
|
<label class="gf-form-label query-keyword width-7">Application</label>
|
||||||
@@ -129,7 +131,7 @@
|
|||||||
<!-- Query options -->
|
<!-- Query options -->
|
||||||
<div class="gf-form-group" ng-if="ctrl.showQueryOptions">
|
<div class="gf-form-group" ng-if="ctrl.showQueryOptions">
|
||||||
<div class="gf-form offset-width-7">
|
<div class="gf-form offset-width-7">
|
||||||
<gf-form-switch class="gf-form" ng-hide="ctrl.target.mode == 2"
|
<gf-form-switch class="gf-form"
|
||||||
label="Show disabled items"
|
label="Show disabled items"
|
||||||
checked="ctrl.target.options.showDisabledItems"
|
checked="ctrl.target.options.showDisabledItems"
|
||||||
on-change="ctrl.onQueryOptionChange()">
|
on-change="ctrl.onQueryOptionChange()">
|
||||||
@@ -137,8 +139,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Item IDs editor mode -->
|
||||||
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.ITEMID">
|
||||||
|
<div class="gf-form max-width-20">
|
||||||
|
<label class="gf-form-label query-keyword width-7">Item IDs</label>
|
||||||
|
<input type="text"
|
||||||
|
ng-model="ctrl.target.itemids"
|
||||||
|
bs-typeahead="ctrl.getVariables"
|
||||||
|
ng-blur="ctrl.onTargetBlur()"
|
||||||
|
data-min-length=0
|
||||||
|
data-items=100
|
||||||
|
class="gf-form-input"
|
||||||
|
ng-class="{
|
||||||
|
'zbx-variable': ctrl.isVariable(ctrl.target.itServiceFilter),
|
||||||
|
'zbx-regex': ctrl.isRegex(ctrl.target.itServiceFilter)
|
||||||
|
}">
|
||||||
|
</input>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Metric processing functions -->
|
<!-- Metric processing functions -->
|
||||||
<div class="gf-form-inline" ng-hide="ctrl.target.mode">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.METRICS || ctrl.target.mode == editorMode.ITEMID">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword width-7">Functions</label>
|
<label class="gf-form-label query-keyword width-7">Functions</label>
|
||||||
<div ng-repeat="func in ctrl.target.functions" class="gf-form-label query-part" metric-function-editor></div>
|
<div ng-repeat="func in ctrl.target.functions" class="gf-form-label query-part" metric-function-editor></div>
|
||||||
@@ -151,7 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Text mode options -->
|
<!-- Text mode options -->
|
||||||
<div class="gf-form-inline" ng-show="ctrl.target.mode == 2">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.TEXT">
|
||||||
<!-- Text metric regex -->
|
<!-- Text metric regex -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Text filter</label>
|
<label class="gf-form-label query-keyword width-7">Text filter</label>
|
||||||
@@ -165,6 +189,8 @@
|
|||||||
|
|
||||||
<gf-form-switch class="gf-form" label="Use capture groups" checked="ctrl.target.useCaptureGroups" on-change="ctrl.onTargetBlur()">
|
<gf-form-switch class="gf-form" label="Use capture groups" checked="ctrl.target.useCaptureGroups" on-change="ctrl.onTargetBlur()">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</query-editor-row>
|
</query-editor-row>
|
||||||
|
|||||||
89
dist/datasource-zabbix/query.controller.js
vendored
89
dist/datasource-zabbix/query.controller.js
vendored
@@ -1,9 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils', './metricFunctions', './migrations', './add-metric-function.directive', './metric-function-editor.directive', './css/query-editor.css!'], function (_export, _context) {
|
System.register(['app/plugins/sdk', 'lodash', './constants', './utils', './metricFunctions', './migrations', './add-metric-function.directive', './metric-function-editor.directive', './css/query-editor.css!'], function (_export, _context) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var QueryCtrl, angular, _, c, utils, metricFunctions, migrations, _createClass, ZabbixQueryController;
|
var QueryCtrl, _, c, utils, metricFunctions, migrations, _createClass, ZabbixQueryController;
|
||||||
|
|
||||||
function _classCallCheck(instance, Constructor) {
|
function _classCallCheck(instance, Constructor) {
|
||||||
if (!(instance instanceof Constructor)) {
|
if (!(instance instanceof Constructor)) {
|
||||||
@@ -38,8 +38,6 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
return {
|
return {
|
||||||
setters: [function (_appPluginsSdk) {
|
setters: [function (_appPluginsSdk) {
|
||||||
QueryCtrl = _appPluginsSdk.QueryCtrl;
|
QueryCtrl = _appPluginsSdk.QueryCtrl;
|
||||||
}, function (_angular) {
|
|
||||||
angular = _angular.default;
|
|
||||||
}, function (_lodash) {
|
}, function (_lodash) {
|
||||||
_ = _lodash.default;
|
_ = _lodash.default;
|
||||||
}, function (_constants) {
|
}, function (_constants) {
|
||||||
@@ -85,17 +83,24 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
_this.replaceTemplateVars = _this.datasource.replaceTemplateVars;
|
_this.replaceTemplateVars = _this.datasource.replaceTemplateVars;
|
||||||
_this.templateSrv = templateSrv;
|
_this.templateSrv = templateSrv;
|
||||||
|
|
||||||
_this.editorModes = {
|
_this.editorModes = [{ value: 'num', text: 'Metrics', mode: c.MODE_METRICS }, { value: 'text', text: 'Text', mode: c.MODE_TEXT }, { value: 'itservice', text: 'IT Services', mode: c.MODE_ITSERVICE }, { value: 'itemid', text: 'Item ID', mode: c.MODE_ITEMID }];
|
||||||
0: { value: 'num', text: 'Metrics', mode: c.MODE_METRICS },
|
|
||||||
1: { value: 'itservice', text: 'IT Services', mode: c.MODE_ITSERVICE },
|
_this.$scope.editorMode = {
|
||||||
2: { value: 'text', text: 'Text', mode: c.MODE_TEXT }
|
METRICS: c.MODE_METRICS,
|
||||||
|
TEXT: c.MODE_TEXT,
|
||||||
|
ITSERVICE: c.MODE_ITSERVICE,
|
||||||
|
ITEMID: c.MODE_ITEMID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_this.slaPropertyList = [{ name: "Status", property: "status" }, { name: "SLA", property: "sla" }, { name: "OK time", property: "okTime" }, { name: "Problem time", property: "problemTime" }, { name: "Down time", property: "downtimeTime" }];
|
||||||
|
|
||||||
// Map functions for bs-typeahead
|
// Map functions for bs-typeahead
|
||||||
_this.getGroupNames = _.bind(_this.getMetricNames, _this, 'groupList');
|
_this.getGroupNames = _.bind(_this.getMetricNames, _this, 'groupList');
|
||||||
_this.getHostNames = _.bind(_this.getMetricNames, _this, 'hostList', true);
|
_this.getHostNames = _.bind(_this.getMetricNames, _this, 'hostList', true);
|
||||||
_this.getApplicationNames = _.bind(_this.getMetricNames, _this, 'appList');
|
_this.getApplicationNames = _.bind(_this.getMetricNames, _this, 'appList');
|
||||||
_this.getItemNames = _.bind(_this.getMetricNames, _this, 'itemList');
|
_this.getItemNames = _.bind(_this.getMetricNames, _this, 'itemList');
|
||||||
|
_this.getITServices = _.bind(_this.getMetricNames, _this, 'itServiceList');
|
||||||
|
_this.getVariables = _.bind(_this.getTemplateVariables, _this);
|
||||||
|
|
||||||
// Update metric suggestion when template variable was changed
|
// Update metric suggestion when template variable was changed
|
||||||
$rootScope.$on('template-variable-value-updated', function () {
|
$rootScope.$on('template-variable-value-updated', function () {
|
||||||
@@ -122,14 +127,14 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
|
|
||||||
// Load default values
|
// Load default values
|
||||||
var targetDefaults = {
|
var targetDefaults = {
|
||||||
mode: c.MODE_METRICS,
|
'mode': c.MODE_METRICS,
|
||||||
group: { filter: "" },
|
'group': { 'filter': "" },
|
||||||
host: { filter: "" },
|
'host': { 'filter': "" },
|
||||||
application: { filter: "" },
|
'application': { 'filter': "" },
|
||||||
item: { filter: "" },
|
'item': { 'filter': "" },
|
||||||
functions: [],
|
'functions': [],
|
||||||
options: {
|
'options': {
|
||||||
showDisabledItems: false
|
'showDisabledItems': false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_.defaults(target, targetDefaults);
|
_.defaults(target, targetDefaults);
|
||||||
@@ -141,13 +146,10 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
|
|
||||||
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT) {
|
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT) {
|
||||||
|
|
||||||
this.downsampleFunctionList = [{ name: "avg", value: "avg" }, { name: "min", value: "min" }, { name: "max", value: "max" }, { name: "sum", value: "sum" }, { name: "count", value: "count" }];
|
|
||||||
|
|
||||||
this.initFilters();
|
this.initFilters();
|
||||||
} else if (target.mode === c.MODE_ITSERVICE) {
|
} else if (target.mode === c.MODE_ITSERVICE) {
|
||||||
this.slaPropertyList = [{ name: "Status", property: "status" }, { name: "SLA", property: "sla" }, { name: "OK time", property: "okTime" }, { name: "Problem time", property: "problemTime" }, { name: "Down time", property: "downtimeTime" }];
|
_.defaults(target, { slaProperty: { name: "SLA", property: "sla" } });
|
||||||
this.itserviceList = [{ name: "test" }];
|
this.suggestITServices();
|
||||||
this.updateITServiceList();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,7 +160,8 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
_createClass(ZabbixQueryController, [{
|
_createClass(ZabbixQueryController, [{
|
||||||
key: 'initFilters',
|
key: 'initFilters',
|
||||||
value: function initFilters() {
|
value: function initFilters() {
|
||||||
var itemtype = this.editorModes[this.target.mode].value;
|
var itemtype = _.find(this.editorModes, { 'mode': this.target.mode });
|
||||||
|
itemtype = itemtype ? itemtype.value : null;
|
||||||
return Promise.all([this.suggestGroups(), this.suggestHosts(), this.suggestApps(), this.suggestItems(itemtype)]);
|
return Promise.all([this.suggestGroups(), this.suggestHosts(), this.suggestApps(), this.suggestItems(itemtype)]);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -177,6 +180,13 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
|
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getTemplateVariables',
|
||||||
|
value: function getTemplateVariables() {
|
||||||
|
return _.map(this.templateSrv.variables, function (variable) {
|
||||||
|
return '$' + variable.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'suggestGroups',
|
key: 'suggestGroups',
|
||||||
value: function suggestGroups() {
|
value: function suggestGroups() {
|
||||||
@@ -230,6 +240,16 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
return items;
|
return items;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'suggestITServices',
|
||||||
|
value: function suggestITServices() {
|
||||||
|
var _this6 = this;
|
||||||
|
|
||||||
|
return this.zabbix.getITService().then(function (itservices) {
|
||||||
|
_this6.metric.itServiceList = itservices;
|
||||||
|
return itservices;
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'isRegex',
|
key: 'isRegex',
|
||||||
value: function isRegex(str) {
|
value: function isRegex(str) {
|
||||||
@@ -259,11 +279,11 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
}, {
|
}, {
|
||||||
key: 'isContainsVariables',
|
key: 'isContainsVariables',
|
||||||
value: function isContainsVariables() {
|
value: function isContainsVariables() {
|
||||||
var _this6 = this;
|
var _this7 = this;
|
||||||
|
|
||||||
return _.some(['group', 'host', 'application'], function (field) {
|
return _.some(['group', 'host', 'application'], function (field) {
|
||||||
if (_this6.target[field] && _this6.target[field].filter) {
|
if (_this7.target[field] && _this7.target[field].filter) {
|
||||||
return utils.isTemplateVariable(_this6.target[field].filter, _this6.templateSrv.variables);
|
return utils.isTemplateVariable(_this7.target[field].filter, _this7.templateSrv.variables);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -356,24 +376,7 @@ System.register(['app/plugins/sdk', 'angular', 'lodash', './constants', './utils
|
|||||||
value: function switchEditorMode(mode) {
|
value: function switchEditorMode(mode) {
|
||||||
this.target.mode = mode;
|
this.target.mode = mode;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
this.targetChanged();
|
||||||
}, {
|
|
||||||
key: 'updateITServiceList',
|
|
||||||
value: function updateITServiceList() {
|
|
||||||
var _this7 = this;
|
|
||||||
|
|
||||||
this.zabbix.getITService().then(function (iteservices) {
|
|
||||||
_this7.itserviceList = [];
|
|
||||||
_this7.itserviceList = _this7.itserviceList.concat(iteservices);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: 'selectITService',
|
|
||||||
value: function selectITService() {
|
|
||||||
if (!_.isEqual(this.oldTarget, this.target) && _.isEmpty(this.target.errors)) {
|
|
||||||
this.oldTarget = angular.copy(this.target);
|
|
||||||
this.panelCtrl.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -15,7 +15,10 @@ describe('ZabbixDatasource', () => {
|
|||||||
password: 'zabbix',
|
password: 'zabbix',
|
||||||
trends: true,
|
trends: true,
|
||||||
trendsFrom: '14d',
|
trendsFrom: '14d',
|
||||||
trendsRange: '7d'
|
trendsRange: '7d',
|
||||||
|
dbConnection: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ctx.templateSrv = {};
|
ctx.templateSrv = {};
|
||||||
|
|||||||
50
dist/datasource-zabbix/specs/utils_specs.js
vendored
50
dist/datasource-zabbix/specs/utils_specs.js
vendored
@@ -88,4 +88,54 @@ describe('Utils', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('splitTemplateQuery()', () => {
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
it('should properly split query in old format', (done) => {
|
||||||
|
let test_cases = [
|
||||||
|
{
|
||||||
|
query: `/alu/./tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9{2}/`,
|
||||||
|
expected: ['/alu/', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9{2}/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `a.b.c.d`,
|
||||||
|
expected: ['a', 'b', 'c', 'd']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
_.each(test_cases, test_case => {
|
||||||
|
let splitQuery = utils.splitTemplateQuery(test_case.query);
|
||||||
|
expect(splitQuery).to.eql(test_case.expected);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly split query', (done) => {
|
||||||
|
let test_cases = [
|
||||||
|
{
|
||||||
|
query: `{alu}{/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]*/}`,
|
||||||
|
expected: ['alu', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]*/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `{alu}{/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]{2}/}`,
|
||||||
|
expected: ['alu', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]{2}/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `{a}{b}{c}{d}`,
|
||||||
|
expected: ['a', 'b', 'c', 'd']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `{a}{b.c.d}`,
|
||||||
|
expected: ['a', 'b.c.d']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
_.each(test_cases, test_case => {
|
||||||
|
let splitQuery = utils.splitTemplateQuery(test_case.query);
|
||||||
|
expect(splitQuery).to.eql(test_case.expected);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
19
dist/datasource-zabbix/utils.js
vendored
19
dist/datasource-zabbix/utils.js
vendored
@@ -28,6 +28,16 @@ System.register(['lodash', 'moment'], function (_export, _context) {
|
|||||||
|
|
||||||
_export('expandItemName', expandItemName);
|
_export('expandItemName', expandItemName);
|
||||||
|
|
||||||
|
function expandItems(items) {
|
||||||
|
_.forEach(items, function (item) {
|
||||||
|
item.item = item.name;
|
||||||
|
item.name = expandItemName(item.item, item.key_);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
_export('expandItems', expandItems);
|
||||||
|
|
||||||
function splitKeyParams(paramStr) {
|
function splitKeyParams(paramStr) {
|
||||||
var params = [];
|
var params = [];
|
||||||
var quoted = false;
|
var quoted = false;
|
||||||
@@ -56,7 +66,9 @@ System.register(['lodash', 'moment'], function (_export, _context) {
|
|||||||
|
|
||||||
params.push(param);
|
params.push(param);
|
||||||
return params;
|
return params;
|
||||||
}function containsMacro(itemName) {
|
}
|
||||||
|
|
||||||
|
function containsMacro(itemName) {
|
||||||
return MACRO_PATTERN.test(itemName);
|
return MACRO_PATTERN.test(itemName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +111,7 @@ System.register(['lodash', 'moment'], function (_export, _context) {
|
|||||||
* {group}{host.com} -> [group, host.com]
|
* {group}{host.com} -> [group, host.com]
|
||||||
*/
|
*/
|
||||||
function splitTemplateQuery(query) {
|
function splitTemplateQuery(query) {
|
||||||
var splitPattern = /{[^{}]*}/g;
|
var splitPattern = /\{[^\{\}]*\}|\{\/.*\/\}/g;
|
||||||
var split = void 0;
|
var split = void 0;
|
||||||
|
|
||||||
if (isContainsBraces(query)) {
|
if (isContainsBraces(query)) {
|
||||||
@@ -117,7 +129,8 @@ System.register(['lodash', 'moment'], function (_export, _context) {
|
|||||||
_export('splitTemplateQuery', splitTemplateQuery);
|
_export('splitTemplateQuery', splitTemplateQuery);
|
||||||
|
|
||||||
function isContainsBraces(query) {
|
function isContainsBraces(query) {
|
||||||
return query.includes('{') && query.includes('}');
|
var bracesPattern = /^\{.+\}$/;
|
||||||
|
return bracesPattern.test(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern for testing regex
|
// Pattern for testing regex
|
||||||
|
|||||||
2
dist/datasource-zabbix/utils.js.map
vendored
2
dist/datasource-zabbix/utils.js.map
vendored
File diff suppressed because one or more lines are too long
36
dist/datasource-zabbix/zabbix.js
vendored
36
dist/datasource-zabbix/zabbix.js
vendored
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
System.register(['angular', 'lodash', './utils', './zabbixAPI.service.js', './zabbixCachingProxy.service.js'], function (_export, _context) {
|
System.register(['angular', 'lodash', './utils', './zabbixAPI.service.js', './zabbixCachingProxy.service.js', './zabbixDBConnector'], function (_export, _context) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var angular, _, utils, _createClass;
|
var angular, _, utils, _createClass;
|
||||||
@@ -27,25 +27,44 @@ System.register(['angular', 'lodash', './utils', './zabbixAPI.service.js', './za
|
|||||||
// Each Zabbix data source instance should initialize its own API instance.
|
// Each Zabbix data source instance should initialize its own API instance.
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) {
|
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy, ZabbixDBConnector) {
|
||||||
var Zabbix = function () {
|
var Zabbix = function () {
|
||||||
function Zabbix(url, username, password, basicAuth, withCredentials, cacheTTL) {
|
function Zabbix(url, options) {
|
||||||
_classCallCheck(this, Zabbix);
|
_classCallCheck(this, Zabbix);
|
||||||
|
|
||||||
|
var username = options.username,
|
||||||
|
password = options.password,
|
||||||
|
basicAuth = options.basicAuth,
|
||||||
|
withCredentials = options.withCredentials,
|
||||||
|
cacheTTL = options.cacheTTL,
|
||||||
|
enableDirectDBConnection = options.enableDirectDBConnection,
|
||||||
|
sqlDatasourceId = options.sqlDatasourceId;
|
||||||
|
|
||||||
|
|
||||||
// Initialize Zabbix API
|
// Initialize Zabbix API
|
||||||
var ZabbixAPI = zabbixAPIService;
|
var ZabbixAPI = zabbixAPIService;
|
||||||
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
||||||
|
|
||||||
|
if (enableDirectDBConnection) {
|
||||||
|
this.dbConnector = new ZabbixDBConnector(sqlDatasourceId);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize caching proxy for requests
|
// Initialize caching proxy for requests
|
||||||
var cacheOptions = {
|
var cacheOptions = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ttl: cacheTTL
|
ttl: cacheTTL
|
||||||
};
|
};
|
||||||
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheOptions);
|
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, this.dbConnector, cacheOptions);
|
||||||
|
|
||||||
// Proxy methods
|
// Proxy methods
|
||||||
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
||||||
this.getMacros = this.cachingProxy.getMacros.bind(this.cachingProxy);
|
this.getMacros = this.cachingProxy.getMacros.bind(this.cachingProxy);
|
||||||
|
this.getItemsByIDs = this.cachingProxy.getItemsByIDs.bind(this.cachingProxy);
|
||||||
|
|
||||||
|
if (enableDirectDBConnection) {
|
||||||
|
this.getHistoryDB = this.cachingProxy.getHistoryDB.bind(this.cachingProxy);
|
||||||
|
this.getTrendsDB = this.cachingProxy.getTrendsDB.bind(this.cachingProxy);
|
||||||
|
}
|
||||||
|
|
||||||
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
||||||
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
||||||
@@ -168,6 +187,13 @@ System.register(['angular', 'lodash', './utils', './zabbixAPI.service.js', './za
|
|||||||
return filterByQuery(items, itemFilter);
|
return filterByQuery(items, itemFilter);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getITServices',
|
||||||
|
value: function getITServices(itServiceFilter) {
|
||||||
|
return this.cachingProxy.getITServices().then(function (itServices) {
|
||||||
|
return findByFilter(itServices, itServiceFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getTriggers',
|
key: 'getTriggers',
|
||||||
value: function getTriggers(groupFilter, hostFilter, appFilter, options) {
|
value: function getTriggers(groupFilter, hostFilter, appFilter, options) {
|
||||||
@@ -274,7 +300,7 @@ System.register(['angular', 'lodash', './utils', './zabbixAPI.service.js', './za
|
|||||||
_ = _lodash.default;
|
_ = _lodash.default;
|
||||||
}, function (_utils) {
|
}, function (_utils) {
|
||||||
utils = _utils;
|
utils = _utils;
|
||||||
}, function (_zabbixAPIServiceJs) {}, function (_zabbixCachingProxyServiceJs) {}],
|
}, function (_zabbixAPIServiceJs) {}, function (_zabbixCachingProxyServiceJs) {}, function (_zabbixDBConnector) {}],
|
||||||
execute: function () {
|
execute: function () {
|
||||||
_createClass = function () {
|
_createClass = function () {
|
||||||
function defineProperties(target, props) {
|
function defineProperties(target, props) {
|
||||||
|
|||||||
2
dist/datasource-zabbix/zabbix.js.map
vendored
2
dist/datasource-zabbix/zabbix.js.map
vendored
File diff suppressed because one or more lines are too long
21
dist/datasource-zabbix/zabbixAPI.service.js
vendored
21
dist/datasource-zabbix/zabbixAPI.service.js
vendored
@@ -166,16 +166,19 @@ System.register(['angular', 'lodash', './utils', './zabbixAPICore.service'], fun
|
|||||||
params.filter.value_type = [1, 2, 4];
|
params.filter.value_type = [1, 2, 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.request('item.get', params).then(expandItems);
|
return this.request('item.get', params).then(utils.expandItems);
|
||||||
|
|
||||||
function expandItems(items) {
|
|
||||||
_.forEach(items, function (item) {
|
|
||||||
item.item = item.name;
|
|
||||||
item.name = utils.expandItemName(item.item, item.key_);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getItemsByIDs',
|
||||||
|
value: function getItemsByIDs(itemids) {
|
||||||
|
var params = {
|
||||||
|
itemids: itemids,
|
||||||
|
output: ['name', 'key_', 'value_type', 'hostid', 'status', 'state'],
|
||||||
|
webitems: true,
|
||||||
|
selectHosts: ['hostid', 'name']
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.request('item.get', params).then(utils.expandItems);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getMacros',
|
key: 'getMacros',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -29,10 +29,11 @@ System.register(['angular', 'lodash'], function (_export, _context) {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ZabbixCachingProxyFactory() {
|
function ZabbixCachingProxyFactory() {
|
||||||
var ZabbixCachingProxy = function () {
|
var ZabbixCachingProxy = function () {
|
||||||
function ZabbixCachingProxy(zabbixAPI, cacheOptions) {
|
function ZabbixCachingProxy(zabbixAPI, zabbixDBConnector, cacheOptions) {
|
||||||
_classCallCheck(this, ZabbixCachingProxy);
|
_classCallCheck(this, ZabbixCachingProxy);
|
||||||
|
|
||||||
this.zabbixAPI = zabbixAPI;
|
this.zabbixAPI = zabbixAPI;
|
||||||
|
this.dbConnector = zabbixDBConnector;
|
||||||
this.cacheEnabled = cacheOptions.enabled;
|
this.cacheEnabled = cacheOptions.enabled;
|
||||||
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
||||||
|
|
||||||
@@ -45,7 +46,8 @@ System.register(['angular', 'lodash'], function (_export, _context) {
|
|||||||
history: {},
|
history: {},
|
||||||
trends: {},
|
trends: {},
|
||||||
macros: {},
|
macros: {},
|
||||||
globalMacros: {}
|
globalMacros: {},
|
||||||
|
itServices: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.historyPromises = {};
|
this.historyPromises = {};
|
||||||
@@ -53,6 +55,11 @@ System.register(['angular', 'lodash'], function (_export, _context) {
|
|||||||
// Don't run duplicated history requests
|
// Don't run duplicated history requests
|
||||||
this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI), this.historyPromises, getHistoryRequestHash);
|
this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI), this.historyPromises, getHistoryRequestHash);
|
||||||
|
|
||||||
|
if (this.dbConnector) {
|
||||||
|
this.getHistoryDB = callAPIRequestOnce(_.bind(this.dbConnector.getHistory, this.dbConnector), this.historyPromises, getDBQueryHash);
|
||||||
|
this.getTrendsDB = callAPIRequestOnce(_.bind(this.dbConnector.getTrends, this.dbConnector), this.historyPromises, getDBQueryHash);
|
||||||
|
}
|
||||||
|
|
||||||
// Don't run duplicated requests
|
// Don't run duplicated requests
|
||||||
this.groupPromises = {};
|
this.groupPromises = {};
|
||||||
this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI), this.groupPromises, getRequestHash);
|
this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI), this.groupPromises, getRequestHash);
|
||||||
@@ -66,6 +73,12 @@ System.register(['angular', 'lodash'], function (_export, _context) {
|
|||||||
this.itemPromises = {};
|
this.itemPromises = {};
|
||||||
this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI), this.itemPromises, getRequestHash);
|
this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI), this.itemPromises, getRequestHash);
|
||||||
|
|
||||||
|
this.itemByIdPromises = {};
|
||||||
|
this.getItemsByIdOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItemsByIDs, this.zabbixAPI), this.itemPromises, getRequestHash);
|
||||||
|
|
||||||
|
this.itServicesPromises = {};
|
||||||
|
this.getITServicesOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getITService, this.zabbixAPI), this.itServicesPromises, getRequestHash);
|
||||||
|
|
||||||
this.macroPromises = {};
|
this.macroPromises = {};
|
||||||
this.getMacrosOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getMacros, this.zabbixAPI), this.macroPromises, getRequestHash);
|
this.getMacrosOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getMacros, this.zabbixAPI), this.macroPromises, getRequestHash);
|
||||||
|
|
||||||
@@ -120,6 +133,17 @@ System.register(['angular', 'lodash'], function (_export, _context) {
|
|||||||
var params = [hostids, appids, itemtype];
|
var params = [hostids, appids, itemtype];
|
||||||
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getItemsByIDs',
|
||||||
|
value: function getItemsByIDs(itemids) {
|
||||||
|
var params = [itemids];
|
||||||
|
return this.proxyRequest(this.getItemsByIdOnce, params, this.cache.items);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getITServices',
|
||||||
|
value: function getITServices() {
|
||||||
|
return this.proxyRequest(this.getITServicesOnce, [], this.cache.itServices);
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getMacros',
|
key: 'getMacros',
|
||||||
value: function getMacros(hostids) {
|
value: function getMacros(hostids) {
|
||||||
@@ -209,6 +233,14 @@ System.register(['angular', 'lodash'], function (_export, _context) {
|
|||||||
return stamp.getHash();
|
return stamp.getHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDBQueryHash(args) {
|
||||||
|
var itemids = _.map(args[0], 'itemid');
|
||||||
|
var consolidateBy = args[3].consolidateBy;
|
||||||
|
var intervalMs = args[3].intervalMs;
|
||||||
|
var stamp = itemids.join() + args[1] + args[2] + consolidateBy + intervalMs;
|
||||||
|
return stamp.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setters: [function (_angular) {
|
setters: [function (_angular) {
|
||||||
angular = _angular.default;
|
angular = _angular.default;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
233
dist/datasource-zabbix/zabbixDBConnector.js
vendored
Normal file
233
dist/datasource-zabbix/zabbixDBConnector.js
vendored
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
System.register(['angular', 'lodash'], function (_export, _context) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var angular, _, _createClass, DEFAULT_QUERY_LIMIT, HISTORY_TO_TABLE_MAP, TREND_TO_TABLE_MAP, consolidateByFunc, consolidateByTrendColumns;
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) {
|
||||||
|
if (!(instance instanceof Constructor)) {
|
||||||
|
throw new TypeError("Cannot call a class as a function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
function ZabbixDBConnectorFactory(datasourceSrv, backendSrv) {
|
||||||
|
var ZabbixDBConnector = function () {
|
||||||
|
function ZabbixDBConnector(sqlDataSourceId) {
|
||||||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
|
||||||
|
_classCallCheck(this, ZabbixDBConnector);
|
||||||
|
|
||||||
|
var limit = options.limit;
|
||||||
|
|
||||||
|
|
||||||
|
this.sqlDataSourceId = sqlDataSourceId;
|
||||||
|
this.limit = limit || DEFAULT_QUERY_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to load DS with given id to check it's exist.
|
||||||
|
* @param {*} datasourceId ID of SQL data source
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(ZabbixDBConnector, [{
|
||||||
|
key: 'loadSQLDataSource',
|
||||||
|
value: function loadSQLDataSource(datasourceId) {
|
||||||
|
var ds = _.find(datasourceSrv.getAll(), { 'id': datasourceId });
|
||||||
|
if (ds) {
|
||||||
|
return datasourceSrv.loadDatasource(ds.name).then(function (ds) {
|
||||||
|
console.log('SQL data source loaded', ds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.reject('SQL Data Source with ID ' + datasourceId + ' not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'testSQLDataSource',
|
||||||
|
value: function testSQLDataSource() {
|
||||||
|
var testQuery = 'SELECT itemid AS metric, clock AS time_sec, value_avg AS value FROM trends_uint LIMIT 1';
|
||||||
|
return this.invokeSQLQuery(testQuery);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getHistory',
|
||||||
|
value: function getHistory(items, timeFrom, timeTill, options) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
var intervalMs = options.intervalMs,
|
||||||
|
consolidateBy = options.consolidateBy;
|
||||||
|
|
||||||
|
var intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
var aggFunction = consolidateByFunc[consolidateBy];
|
||||||
|
|
||||||
|
// Group items by value type and perform request for each value type
|
||||||
|
var grouped_items = _.groupBy(items, 'value_type');
|
||||||
|
var promises = _.map(grouped_items, function (items, value_type) {
|
||||||
|
var itemids = _.map(items, 'itemid').join(', ');
|
||||||
|
var table = HISTORY_TO_TABLE_MAP[value_type];
|
||||||
|
|
||||||
|
var query = '\n SELECT itemid AS metric, clock AS time_sec, ' + aggFunction + '(value) AS value\n FROM ' + table + '\n WHERE itemid IN (' + itemids + ')\n AND clock > ' + timeFrom + ' AND clock < ' + timeTill + '\n GROUP BY time_sec DIV ' + intervalSec + ', metric\n ';
|
||||||
|
|
||||||
|
query = compactSQLQuery(query);
|
||||||
|
return _this.invokeSQLQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function (results) {
|
||||||
|
return _.flatten(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getTrends',
|
||||||
|
value: function getTrends(items, timeFrom, timeTill, options) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
var intervalMs = options.intervalMs,
|
||||||
|
consolidateBy = options.consolidateBy;
|
||||||
|
|
||||||
|
var intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
var aggFunction = consolidateByFunc[consolidateBy];
|
||||||
|
|
||||||
|
// Group items by value type and perform request for each value type
|
||||||
|
var grouped_items = _.groupBy(items, 'value_type');
|
||||||
|
var promises = _.map(grouped_items, function (items, value_type) {
|
||||||
|
var itemids = _.map(items, 'itemid').join(', ');
|
||||||
|
var table = TREND_TO_TABLE_MAP[value_type];
|
||||||
|
var valueColumn = _.includes(['avg', 'min', 'max'], consolidateBy) ? consolidateBy : 'avg';
|
||||||
|
valueColumn = consolidateByTrendColumns[valueColumn];
|
||||||
|
|
||||||
|
var query = '\n SELECT itemid AS metric, clock AS time_sec, ' + aggFunction + '(' + valueColumn + ') AS value\n FROM ' + table + '\n WHERE itemid IN (' + itemids + ')\n AND clock > ' + timeFrom + ' AND clock < ' + timeTill + '\n GROUP BY time_sec DIV ' + intervalSec + ', metric\n ';
|
||||||
|
|
||||||
|
query = compactSQLQuery(query);
|
||||||
|
return _this2.invokeSQLQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function (results) {
|
||||||
|
return _.flatten(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'handleGrafanaTSResponse',
|
||||||
|
value: function handleGrafanaTSResponse(history, items) {
|
||||||
|
var addHostName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
||||||
|
|
||||||
|
return convertGrafanaTSResponse(history, items, addHostName);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'invokeSQLQuery',
|
||||||
|
value: function invokeSQLQuery(query) {
|
||||||
|
var queryDef = {
|
||||||
|
refId: 'A',
|
||||||
|
format: 'time_series',
|
||||||
|
datasourceId: this.sqlDataSourceId,
|
||||||
|
rawSql: query,
|
||||||
|
maxDataPoints: this.limit
|
||||||
|
};
|
||||||
|
|
||||||
|
return backendSrv.datasourceRequest({
|
||||||
|
url: '/api/tsdb/query',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
queries: [queryDef]
|
||||||
|
}
|
||||||
|
}).then(function (response) {
|
||||||
|
var results = response.data.results;
|
||||||
|
if (results['A']) {
|
||||||
|
return results['A'].series;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return ZabbixDBConnector;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return ZabbixDBConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function convertGrafanaTSResponse(time_series, items, addHostName) {
|
||||||
|
var hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate
|
||||||
|
var grafanaSeries = _.map(time_series, function (series) {
|
||||||
|
var itemid = series.name;
|
||||||
|
var datapoints = series.points;
|
||||||
|
var item = _.find(items, { 'itemid': itemid });
|
||||||
|
var alias = item.name;
|
||||||
|
if (_.keys(hosts).length > 1 && addHostName) {
|
||||||
|
//only when actual multi hosts selected
|
||||||
|
var host = _.find(hosts, { 'hostid': item.hostid });
|
||||||
|
alias = host.name + ": " + alias;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
target: alias,
|
||||||
|
datapoints: datapoints
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return _.sortBy(grafanaSeries, 'target');
|
||||||
|
}
|
||||||
|
|
||||||
|
function compactSQLQuery(query) {
|
||||||
|
return query.replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
setters: [function (_angular) {
|
||||||
|
angular = _angular.default;
|
||||||
|
}, function (_lodash) {
|
||||||
|
_ = _lodash.default;
|
||||||
|
}],
|
||||||
|
execute: function () {
|
||||||
|
_createClass = function () {
|
||||||
|
function defineProperties(target, props) {
|
||||||
|
for (var i = 0; i < props.length; i++) {
|
||||||
|
var descriptor = props[i];
|
||||||
|
descriptor.enumerable = descriptor.enumerable || false;
|
||||||
|
descriptor.configurable = true;
|
||||||
|
if ("value" in descriptor) descriptor.writable = true;
|
||||||
|
Object.defineProperty(target, descriptor.key, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (Constructor, protoProps, staticProps) {
|
||||||
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||||
|
if (staticProps) defineProperties(Constructor, staticProps);
|
||||||
|
return Constructor;
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
DEFAULT_QUERY_LIMIT = 10000;
|
||||||
|
HISTORY_TO_TABLE_MAP = {
|
||||||
|
'0': 'history',
|
||||||
|
'1': 'history_str',
|
||||||
|
'2': 'history_log',
|
||||||
|
'3': 'history_uint',
|
||||||
|
'4': 'history_text'
|
||||||
|
};
|
||||||
|
TREND_TO_TABLE_MAP = {
|
||||||
|
'0': 'trends',
|
||||||
|
'3': 'trends_uint'
|
||||||
|
};
|
||||||
|
consolidateByFunc = {
|
||||||
|
'avg': 'AVG',
|
||||||
|
'min': 'MIN',
|
||||||
|
'max': 'MAX',
|
||||||
|
'sum': 'SUM',
|
||||||
|
'count': 'COUNT'
|
||||||
|
};
|
||||||
|
consolidateByTrendColumns = {
|
||||||
|
'avg': 'value_avg',
|
||||||
|
'min': 'value_min',
|
||||||
|
'max': 'value_max'
|
||||||
|
};
|
||||||
|
angular.module('grafana.services').factory('ZabbixDBConnector', ZabbixDBConnectorFactory);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=zabbixDBConnector.js.map
|
||||||
1
dist/datasource-zabbix/zabbixDBConnector.js.map
vendored
Normal file
1
dist/datasource-zabbix/zabbixDBConnector.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/panel-triggers/editor.html
vendored
4
dist/panel-triggers/editor.html
vendored
@@ -55,6 +55,10 @@
|
|||||||
placeholder="trigger name"
|
placeholder="trigger name"
|
||||||
class="gf-form-input"
|
class="gf-form-input"
|
||||||
ng-style="editor.panel.triggers.trigger.style"
|
ng-style="editor.panel.triggers.trigger.style"
|
||||||
|
ng-class="{
|
||||||
|
'zbx-variable': editor.isVariable(editor.panel.triggers.trigger.filter),
|
||||||
|
'zbx-regex': editor.isRegex(editor.panel.triggers.trigger.filter)
|
||||||
|
}"
|
||||||
empty-to-null>
|
empty-to-null>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
8
dist/panel-triggers/module.js
vendored
8
dist/panel-triggers/module.js
vendored
@@ -206,6 +206,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/plugins/sdk', '../datasource
|
|||||||
return this.datasourceSrv.get(this.panel.datasource).then(function (datasource) {
|
return this.datasourceSrv.get(this.panel.datasource).then(function (datasource) {
|
||||||
var zabbix = datasource.zabbix;
|
var zabbix = datasource.zabbix;
|
||||||
_this3.zabbix = zabbix;
|
_this3.zabbix = zabbix;
|
||||||
|
_this3.datasource = datasource;
|
||||||
var showEvents = _this3.panel.showEvents.value;
|
var showEvents = _this3.panel.showEvents.value;
|
||||||
var triggerFilter = _this3.panel.triggers;
|
var triggerFilter = _this3.panel.triggers;
|
||||||
var hideHostsInMaintenance = _this3.panel.hideHostsInMaintenance;
|
var hideHostsInMaintenance = _this3.panel.hideHostsInMaintenance;
|
||||||
@@ -219,11 +220,11 @@ System.register(['lodash', 'jquery', 'moment', 'app/plugins/sdk', '../datasource
|
|||||||
showTriggers: showEvents,
|
showTriggers: showEvents,
|
||||||
hideHostsInMaintenance: hideHostsInMaintenance
|
hideHostsInMaintenance: hideHostsInMaintenance
|
||||||
};
|
};
|
||||||
var getTriggers = zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions);
|
|
||||||
return getTriggers.then(function (triggers) {
|
return zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions);
|
||||||
|
}).then(function (triggers) {
|
||||||
return _.map(triggers, _this3.formatTrigger.bind(_this3));
|
return _.map(triggers, _this3.formatTrigger.bind(_this3));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getAcknowledges',
|
key: 'getAcknowledges',
|
||||||
@@ -272,6 +273,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/plugins/sdk', '../datasource
|
|||||||
|
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
var triggerFilter = this.panel.triggers.trigger.filter;
|
var triggerFilter = this.panel.triggers.trigger.filter;
|
||||||
|
triggerFilter = this.datasource.replaceTemplateVars(triggerFilter);
|
||||||
if (triggerFilter) {
|
if (triggerFilter) {
|
||||||
triggerList = _filterTriggers(triggerList, triggerFilter);
|
triggerList = _filterTriggers(triggerList, triggerFilter);
|
||||||
}
|
}
|
||||||
|
|||||||
2
dist/panel-triggers/module.js.map
vendored
2
dist/panel-triggers/module.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/plugin.json
vendored
4
dist/plugin.json
vendored
@@ -26,8 +26,8 @@
|
|||||||
{"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"},
|
{"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"},
|
||||||
{"name": "Triggers", "path": "img/screenshot-triggers.png"}
|
{"name": "Triggers", "path": "img/screenshot-triggers.png"}
|
||||||
],
|
],
|
||||||
"version": "3.4.0",
|
"version": "3.5.1",
|
||||||
"updated": "2017-05-17"
|
"updated": "2017-07-10"
|
||||||
},
|
},
|
||||||
|
|
||||||
"includes": [
|
"includes": [
|
||||||
|
|||||||
50
dist/test/datasource-zabbix/config.controller.js
vendored
Normal file
50
dist/test/datasource-zabbix/config.controller.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
exports.ZabbixDSConfigController = undefined;
|
||||||
|
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
var _lodash = require('lodash');
|
||||||
|
|
||||||
|
var _lodash2 = _interopRequireDefault(_lodash);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
|
var SUPPORTED_SQL_DS = ['mysql'];
|
||||||
|
|
||||||
|
var defaultConfig = {
|
||||||
|
dbConnection: {
|
||||||
|
enable: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var ZabbixDSConfigController = exports.ZabbixDSConfigController = function () {
|
||||||
|
/** @ngInject */
|
||||||
|
function ZabbixDSConfigController($scope, $injector, datasourceSrv) {
|
||||||
|
_classCallCheck(this, ZabbixDSConfigController);
|
||||||
|
|
||||||
|
this.datasourceSrv = datasourceSrv;
|
||||||
|
|
||||||
|
_lodash2.default.defaults(this.current.jsonData, defaultConfig);
|
||||||
|
this.sqlDataSources = this.getSupportedSQLDataSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
_createClass(ZabbixDSConfigController, [{
|
||||||
|
key: 'getSupportedSQLDataSources',
|
||||||
|
value: function getSupportedSQLDataSources() {
|
||||||
|
var datasources = this.datasourceSrv.getAll();
|
||||||
|
return _lodash2.default.filter(datasources, function (ds) {
|
||||||
|
return _lodash2.default.includes(SUPPORTED_SQL_DS, ds.type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return ZabbixDSConfigController;
|
||||||
|
}();
|
||||||
|
|
||||||
|
ZabbixDSConfigController.templateUrl = 'datasource-zabbix/partials/config.html';
|
||||||
3
dist/test/datasource-zabbix/constants.js
vendored
3
dist/test/datasource-zabbix/constants.js
vendored
@@ -5,8 +5,9 @@ Object.defineProperty(exports, "__esModule", {
|
|||||||
});
|
});
|
||||||
// Editor modes
|
// Editor modes
|
||||||
var MODE_METRICS = exports.MODE_METRICS = 0;
|
var MODE_METRICS = exports.MODE_METRICS = 0;
|
||||||
var MODE_TEXT = exports.MODE_TEXT = 2;
|
|
||||||
var MODE_ITSERVICE = exports.MODE_ITSERVICE = 1;
|
var MODE_ITSERVICE = exports.MODE_ITSERVICE = 1;
|
||||||
|
var MODE_TEXT = exports.MODE_TEXT = 2;
|
||||||
|
var MODE_ITEMID = exports.MODE_ITEMID = 3;
|
||||||
|
|
||||||
// Triggers severity
|
// Triggers severity
|
||||||
var SEV_NOT_CLASSIFIED = exports.SEV_NOT_CLASSIFIED = 0;
|
var SEV_NOT_CLASSIFIED = exports.SEV_NOT_CLASSIFIED = 0;
|
||||||
|
|||||||
245
dist/test/datasource-zabbix/datasource.js
vendored
245
dist/test/datasource-zabbix/datasource.js
vendored
@@ -64,6 +64,9 @@ var ZabbixAPIDatasource = function () {
|
|||||||
this.dashboardSrv = dashboardSrv;
|
this.dashboardSrv = dashboardSrv;
|
||||||
this.zabbixAlertingSrv = zabbixAlertingSrv;
|
this.zabbixAlertingSrv = zabbixAlertingSrv;
|
||||||
|
|
||||||
|
// Use custom format for template variables
|
||||||
|
this.replaceTemplateVars = _lodash2.default.partial(replaceTemplateVars, this.templateSrv);
|
||||||
|
|
||||||
// General data source settings
|
// General data source settings
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
this.url = instanceSettings.url;
|
this.url = instanceSettings.url;
|
||||||
@@ -88,10 +91,21 @@ var ZabbixAPIDatasource = function () {
|
|||||||
this.addThresholds = instanceSettings.jsonData.addThresholds;
|
this.addThresholds = instanceSettings.jsonData.addThresholds;
|
||||||
this.alertingMinSeverity = instanceSettings.jsonData.alertingMinSeverity || c.SEV_WARNING;
|
this.alertingMinSeverity = instanceSettings.jsonData.alertingMinSeverity || c.SEV_WARNING;
|
||||||
|
|
||||||
this.zabbix = new Zabbix(this.url, this.username, this.password, this.basicAuth, this.withCredentials, this.cacheTTL);
|
// Direct DB Connection options
|
||||||
|
this.enableDirectDBConnection = instanceSettings.jsonData.dbConnection.enable;
|
||||||
|
this.sqlDatasourceId = instanceSettings.jsonData.dbConnection.datasourceId;
|
||||||
|
|
||||||
// Use custom format for template variables
|
var zabbixOptions = {
|
||||||
this.replaceTemplateVars = _lodash2.default.partial(replaceTemplateVars, this.templateSrv);
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
basicAuth: this.basicAuth,
|
||||||
|
withCredentials: this.withCredentials,
|
||||||
|
cacheTTL: this.cacheTTL,
|
||||||
|
enableDirectDBConnection: this.enableDirectDBConnection,
|
||||||
|
sqlDatasourceId: this.sqlDatasourceId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.zabbix = new Zabbix(this.url, zabbixOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
@@ -126,6 +140,11 @@ var ZabbixAPIDatasource = function () {
|
|||||||
|
|
||||||
// Create request for each target
|
// Create request for each target
|
||||||
var promises = _lodash2.default.map(options.targets, function (t) {
|
var promises = _lodash2.default.map(options.targets, function (t) {
|
||||||
|
// Don't request undefined and hidden targets
|
||||||
|
if (t.hide) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
|
var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
|
||||||
var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);
|
var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);
|
||||||
|
|
||||||
@@ -149,7 +168,7 @@ var ZabbixAPIDatasource = function () {
|
|||||||
var useTrends = _this.isUseTrends(timeRange);
|
var useTrends = _this.isUseTrends(timeRange);
|
||||||
|
|
||||||
// Metrics or Text query mode
|
// Metrics or Text query mode
|
||||||
if (target.mode !== c.MODE_ITSERVICE) {
|
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT || target.mode === c.MODE_ITEMID) {
|
||||||
// Migrate old targets
|
// Migrate old targets
|
||||||
target = migrations.migrate(target);
|
target = migrations.migrate(target);
|
||||||
|
|
||||||
@@ -162,19 +181,12 @@ var ZabbixAPIDatasource = function () {
|
|||||||
return _this.queryNumericData(target, timeRange, useTrends, options);
|
return _this.queryNumericData(target, timeRange, useTrends, options);
|
||||||
} else if (target.mode === c.MODE_TEXT) {
|
} else if (target.mode === c.MODE_TEXT) {
|
||||||
return _this.queryTextData(target, timeRange);
|
return _this.queryTextData(target, timeRange);
|
||||||
|
} else if (target.mode === c.MODE_ITEMID) {
|
||||||
|
return _this.queryItemIdData(target, timeRange, useTrends, options);
|
||||||
}
|
}
|
||||||
}
|
} else if (target.mode === c.MODE_ITSERVICE) {
|
||||||
|
|
||||||
// IT services mode
|
// IT services mode
|
||||||
else if (target.mode === c.MODE_ITSERVICE) {
|
return _this.queryITServiceData(target, timeRange, options);
|
||||||
// Don't show undefined and hidden targets
|
|
||||||
if (target.hide || !target.itservice || !target.slaProperty) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return _this.zabbix.getSLA(target.itservice.serviceid, timeRange).then(function (slaObject) {
|
|
||||||
return _responseHandler2.default.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -183,24 +195,48 @@ var ZabbixAPIDatasource = function () {
|
|||||||
return { data: data };
|
return { data: data };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for Metrics mode
|
||||||
|
*/
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
key: 'queryNumericData',
|
key: 'queryNumericData',
|
||||||
value: function queryNumericData(target, timeRange, useTrends, options) {
|
value: function queryNumericData(target, timeRange, useTrends, options) {
|
||||||
var _this2 = this;
|
var _this2 = this;
|
||||||
|
|
||||||
var _timeRange = _slicedToArray(timeRange, 2),
|
|
||||||
timeFrom = _timeRange[0],
|
|
||||||
timeTo = _timeRange[1];
|
|
||||||
|
|
||||||
var getItemOptions = {
|
var getItemOptions = {
|
||||||
itemtype: 'num'
|
itemtype: 'num'
|
||||||
};
|
};
|
||||||
return this.zabbix.getItemsFromTarget(target, getItemOptions).then(function (items) {
|
return this.zabbix.getItemsFromTarget(target, getItemOptions).then(function (items) {
|
||||||
|
return _this2.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query history for numeric items
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'queryNumericDataForItems',
|
||||||
|
value: function queryNumericDataForItems(items, target, timeRange, useTrends, options) {
|
||||||
|
var _this3 = this;
|
||||||
|
|
||||||
|
var _timeRange = _slicedToArray(timeRange, 2),
|
||||||
|
timeFrom = _timeRange[0],
|
||||||
|
timeTo = _timeRange[1];
|
||||||
|
|
||||||
var getHistoryPromise = void 0;
|
var getHistoryPromise = void 0;
|
||||||
|
options.consolidateBy = getConsolidateBy(target);
|
||||||
|
|
||||||
if (useTrends) {
|
if (useTrends) {
|
||||||
var valueType = _this2.getTrendValueType(target);
|
if (this.enableDirectDBConnection) {
|
||||||
getHistoryPromise = _this2.zabbix.getTrend(items, timeFrom, timeTo).then(function (history) {
|
getHistoryPromise = this.zabbix.getTrendsDB(items, timeFrom, timeTo, options).then(function (history) {
|
||||||
|
return _this3.zabbix.dbConnector.handleGrafanaTSResponse(history, items);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var valueType = this.getTrendValueType(target);
|
||||||
|
getHistoryPromise = this.zabbix.getTrend(items, timeFrom, timeTo).then(function (history) {
|
||||||
return _responseHandler2.default.handleTrends(history, items, valueType);
|
return _responseHandler2.default.handleTrends(history, items, valueType);
|
||||||
}).then(function (timeseries) {
|
}).then(function (timeseries) {
|
||||||
// Sort trend data, issue #202
|
// Sort trend data, issue #202
|
||||||
@@ -209,19 +245,24 @@ var ZabbixAPIDatasource = function () {
|
|||||||
return point[c.DATAPOINT_TS];
|
return point[c.DATAPOINT_TS];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return timeseries;
|
return timeseries;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use history
|
// Use history
|
||||||
getHistoryPromise = _this2.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
if (this.enableDirectDBConnection) {
|
||||||
|
getHistoryPromise = this.zabbix.getHistoryDB(items, timeFrom, timeTo, options).then(function (history) {
|
||||||
|
return _this3.zabbix.dbConnector.handleGrafanaTSResponse(history, items);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getHistoryPromise = this.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
||||||
return _responseHandler2.default.handleHistory(history, items);
|
return _responseHandler2.default.handleHistory(history, items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return getHistoryPromise;
|
return getHistoryPromise.then(function (timeseries) {
|
||||||
}).then(function (timeseries) {
|
return _this3.applyDataProcessingFunctions(timeseries, target);
|
||||||
return _this2.applyDataProcessingFunctions(timeseries, target);
|
|
||||||
}).then(function (timeseries) {
|
}).then(function (timeseries) {
|
||||||
return downsampleSeries(timeseries, options);
|
return downsampleSeries(timeseries, options);
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
@@ -297,10 +338,15 @@ var ZabbixAPIDatasource = function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for Text mode
|
||||||
|
*/
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
key: 'queryTextData',
|
key: 'queryTextData',
|
||||||
value: function queryTextData(target, timeRange) {
|
value: function queryTextData(target, timeRange) {
|
||||||
var _this3 = this;
|
var _this4 = this;
|
||||||
|
|
||||||
var _timeRange2 = _slicedToArray(timeRange, 2),
|
var _timeRange2 = _slicedToArray(timeRange, 2),
|
||||||
timeFrom = _timeRange2[0],
|
timeFrom = _timeRange2[0],
|
||||||
@@ -311,7 +357,7 @@ var ZabbixAPIDatasource = function () {
|
|||||||
};
|
};
|
||||||
return this.zabbix.getItemsFromTarget(target, options).then(function (items) {
|
return this.zabbix.getItemsFromTarget(target, options).then(function (items) {
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
return _this3.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
return _this4.zabbix.getHistory(items, timeFrom, timeTo).then(function (history) {
|
||||||
return _responseHandler2.default.handleText(history, items, target);
|
return _responseHandler2.default.handleText(history, items, target);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -320,6 +366,74 @@ var ZabbixAPIDatasource = function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for Item ID mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'queryItemIdData',
|
||||||
|
value: function queryItemIdData(target, timeRange, useTrends, options) {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
var itemids = target.itemids;
|
||||||
|
itemids = this.templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||||
|
itemids = _lodash2.default.map(itemids.split(','), function (itemid) {
|
||||||
|
return itemid.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!itemids) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.zabbix.getItemsByIDs(itemids).then(function (items) {
|
||||||
|
return _this5.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for IT Services mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'queryITServiceData',
|
||||||
|
value: function queryITServiceData(target, timeRange, options) {
|
||||||
|
var _this6 = this;
|
||||||
|
|
||||||
|
// Don't show undefined and hidden targets
|
||||||
|
if (target.hide || !target.itservice && !target.itServiceFilter || !target.slaProperty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var itServiceIds = [];
|
||||||
|
var itServices = [];
|
||||||
|
var itServiceFilter = void 0;
|
||||||
|
var isOldVersion = target.itservice && !target.itServiceFilter;
|
||||||
|
|
||||||
|
if (isOldVersion) {
|
||||||
|
// Backward compatibility
|
||||||
|
itServiceFilter = '/.*/';
|
||||||
|
} else {
|
||||||
|
itServiceFilter = this.replaceTemplateVars(target.itServiceFilter, options.scopedVars);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.zabbix.getITServices(itServiceFilter).then(function (itservices) {
|
||||||
|
itServices = itservices;
|
||||||
|
if (isOldVersion) {
|
||||||
|
itServices = _lodash2.default.filter(itServices, { 'serviceid': target.itservice.serviceid });
|
||||||
|
}
|
||||||
|
|
||||||
|
itServiceIds = _lodash2.default.map(itServices, 'serviceid');
|
||||||
|
return itServiceIds;
|
||||||
|
}).then(function (serviceids) {
|
||||||
|
return _this6.zabbix.getSLA(serviceids, timeRange);
|
||||||
|
}).then(function (slaResponse) {
|
||||||
|
return _lodash2.default.map(itServiceIds, function (serviceid) {
|
||||||
|
var itservice = _lodash2.default.find(itServices, { 'serviceid': serviceid });
|
||||||
|
return _responseHandler2.default.handleSLAResponse(itservice, target.slaProperty, slaResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test connection to Zabbix API
|
* Test connection to Zabbix API
|
||||||
* @return {object} Connection status and Zabbix API version
|
* @return {object} Connection status and Zabbix API version
|
||||||
@@ -328,12 +442,18 @@ var ZabbixAPIDatasource = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'testDatasource',
|
key: 'testDatasource',
|
||||||
value: function testDatasource() {
|
value: function testDatasource() {
|
||||||
var _this4 = this;
|
var _this7 = this;
|
||||||
|
|
||||||
var zabbixVersion = void 0;
|
var zabbixVersion = void 0;
|
||||||
return this.zabbix.getVersion().then(function (version) {
|
return this.zabbix.getVersion().then(function (version) {
|
||||||
zabbixVersion = version;
|
zabbixVersion = version;
|
||||||
return _this4.zabbix.login();
|
return _this7.zabbix.login();
|
||||||
|
}).then(function () {
|
||||||
|
if (_this7.enableDirectDBConnection) {
|
||||||
|
return _this7.zabbix.dbConnector.testSQLDataSource();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
@@ -347,6 +467,12 @@ var ZabbixAPIDatasource = function () {
|
|||||||
title: error.message,
|
title: error.message,
|
||||||
message: error.data
|
message: error.data
|
||||||
};
|
};
|
||||||
|
} else if (error.data && error.data.message) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
title: "Connection failed",
|
||||||
|
message: error.data.message
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
@@ -372,14 +498,14 @@ var ZabbixAPIDatasource = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'metricFindQuery',
|
key: 'metricFindQuery',
|
||||||
value: function metricFindQuery(query) {
|
value: function metricFindQuery(query) {
|
||||||
var _this5 = this;
|
var _this8 = this;
|
||||||
|
|
||||||
var result = void 0;
|
var result = void 0;
|
||||||
var parts = [];
|
var parts = [];
|
||||||
|
|
||||||
// Split query. Query structure: group.host.app.item
|
// Split query. Query structure: group.host.app.item
|
||||||
_lodash2.default.each(utils.splitTemplateQuery(query), function (part) {
|
_lodash2.default.each(utils.splitTemplateQuery(query), function (part) {
|
||||||
part = _this5.replaceTemplateVars(part, {});
|
part = _this8.replaceTemplateVars(part, {});
|
||||||
|
|
||||||
// Replace wildcard to regex
|
// Replace wildcard to regex
|
||||||
if (part === '*') {
|
if (part === '*') {
|
||||||
@@ -421,7 +547,7 @@ var ZabbixAPIDatasource = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'annotationQuery',
|
key: 'annotationQuery',
|
||||||
value: function annotationQuery(options) {
|
value: function annotationQuery(options) {
|
||||||
var _this6 = this;
|
var _this9 = this;
|
||||||
|
|
||||||
var timeFrom = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000);
|
var timeFrom = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000);
|
||||||
var timeTo = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000);
|
var timeTo = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000);
|
||||||
@@ -439,13 +565,14 @@ var ZabbixAPIDatasource = function () {
|
|||||||
return getTriggers.then(function (triggers) {
|
return getTriggers.then(function (triggers) {
|
||||||
|
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
if (utils.isRegex(annotation.trigger)) {
|
var triggerName = _this9.replaceTemplateVars(annotation.trigger, {});
|
||||||
|
if (utils.isRegex(triggerName)) {
|
||||||
triggers = _lodash2.default.filter(triggers, function (trigger) {
|
triggers = _lodash2.default.filter(triggers, function (trigger) {
|
||||||
return utils.buildRegex(annotation.trigger).test(trigger.description);
|
return utils.buildRegex(triggerName).test(trigger.description);
|
||||||
});
|
});
|
||||||
} else if (annotation.trigger) {
|
} else if (triggerName) {
|
||||||
triggers = _lodash2.default.filter(triggers, function (trigger) {
|
triggers = _lodash2.default.filter(triggers, function (trigger) {
|
||||||
return trigger.description === annotation.trigger;
|
return trigger.description === triggerName;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +582,7 @@ var ZabbixAPIDatasource = function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var objectids = _lodash2.default.map(triggers, 'triggerid');
|
var objectids = _lodash2.default.map(triggers, 'triggerid');
|
||||||
return _this6.zabbix.getEvents(objectids, timeFrom, timeTo, showOkEvents).then(function (events) {
|
return _this9.zabbix.getEvents(objectids, timeFrom, timeTo, showOkEvents).then(function (events) {
|
||||||
var indexedTriggers = _lodash2.default.keyBy(triggers, 'triggerid');
|
var indexedTriggers = _lodash2.default.keyBy(triggers, 'triggerid');
|
||||||
|
|
||||||
// Hide acknowledged events if option enabled
|
// Hide acknowledged events if option enabled
|
||||||
@@ -496,21 +623,23 @@ var ZabbixAPIDatasource = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'alertQuery',
|
key: 'alertQuery',
|
||||||
value: function alertQuery(options) {
|
value: function alertQuery(options) {
|
||||||
var _this7 = this;
|
var _this10 = this;
|
||||||
|
|
||||||
var enabled_targets = filterEnabledTargets(options.targets);
|
var enabled_targets = filterEnabledTargets(options.targets);
|
||||||
var getPanelItems = _lodash2.default.map(enabled_targets, function (target) {
|
var getPanelItems = _lodash2.default.map(enabled_targets, function (t) {
|
||||||
return _this7.zabbix.getItemsFromTarget(target, { itemtype: 'num' });
|
var target = _lodash2.default.cloneDeep(t);
|
||||||
|
_this10.replaceTargetVariables(target, options);
|
||||||
|
return _this10.zabbix.getItemsFromTarget(target, { itemtype: 'num' });
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(getPanelItems).then(function (results) {
|
return Promise.all(getPanelItems).then(function (results) {
|
||||||
var items = _lodash2.default.flatten(results);
|
var items = _lodash2.default.flatten(results);
|
||||||
var itemids = _lodash2.default.map(items, 'itemid');
|
var itemids = _lodash2.default.map(items, 'itemid');
|
||||||
|
|
||||||
return _this7.zabbix.getAlerts(itemids);
|
return _this10.zabbix.getAlerts(itemids);
|
||||||
}).then(function (triggers) {
|
}).then(function (triggers) {
|
||||||
triggers = _lodash2.default.filter(triggers, function (trigger) {
|
triggers = _lodash2.default.filter(triggers, function (trigger) {
|
||||||
return trigger.priority >= _this7.alertingMinSeverity;
|
return trigger.priority >= _this10.alertingMinSeverity;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!triggers || triggers.length === 0) {
|
if (!triggers || triggers.length === 0) {
|
||||||
@@ -541,12 +670,12 @@ var ZabbixAPIDatasource = function () {
|
|||||||
}, {
|
}, {
|
||||||
key: 'replaceTargetVariables',
|
key: 'replaceTargetVariables',
|
||||||
value: function replaceTargetVariables(target, options) {
|
value: function replaceTargetVariables(target, options) {
|
||||||
var _this8 = this;
|
var _this11 = this;
|
||||||
|
|
||||||
var parts = ['group', 'host', 'application', 'item'];
|
var parts = ['group', 'host', 'application', 'item'];
|
||||||
_lodash2.default.forEach(parts, function (p) {
|
_lodash2.default.forEach(parts, function (p) {
|
||||||
if (target[p] && target[p].filter) {
|
if (target[p] && target[p].filter) {
|
||||||
target[p].filter = _this8.replaceTemplateVars(target[p].filter, options.scopedVars);
|
target[p].filter = _this11.replaceTemplateVars(target[p].filter, options.scopedVars);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
||||||
@@ -554,9 +683,9 @@ var ZabbixAPIDatasource = function () {
|
|||||||
_lodash2.default.forEach(target.functions, function (func) {
|
_lodash2.default.forEach(target.functions, function (func) {
|
||||||
func.params = _lodash2.default.map(func.params, function (param) {
|
func.params = _lodash2.default.map(func.params, function (param) {
|
||||||
if (typeof param === 'number') {
|
if (typeof param === 'number') {
|
||||||
return +_this8.templateSrv.replace(param.toString(), options.scopedVars);
|
return +_this11.templateSrv.replace(param.toString(), options.scopedVars);
|
||||||
} else {
|
} else {
|
||||||
return _this8.templateSrv.replace(param, options.scopedVars);
|
return _this11.templateSrv.replace(param, options.scopedVars);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -590,10 +719,23 @@ function bindFunctionDefs(functionDefs, category) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConsolidateBy(target) {
|
||||||
|
var consolidateBy = 'avg';
|
||||||
|
var funcDef = _lodash2.default.find(target.functions, function (func) {
|
||||||
|
return func.def.name === 'consolidateBy';
|
||||||
|
});
|
||||||
|
if (funcDef && funcDef.params && funcDef.params.length) {
|
||||||
|
consolidateBy = funcDef.params[0];
|
||||||
|
}
|
||||||
|
return consolidateBy;
|
||||||
|
}
|
||||||
|
|
||||||
function downsampleSeries(timeseries_data, options) {
|
function downsampleSeries(timeseries_data, options) {
|
||||||
|
var defaultAgg = _dataProcessor2.default.aggregationFunctions['avg'];
|
||||||
|
var consolidateByFunc = _dataProcessor2.default.aggregationFunctions[options.consolidateBy] || defaultAgg;
|
||||||
return _lodash2.default.map(timeseries_data, function (timeseries) {
|
return _lodash2.default.map(timeseries_data, function (timeseries) {
|
||||||
if (timeseries.datapoints.length > options.maxDataPoints) {
|
if (timeseries.datapoints.length > options.maxDataPoints) {
|
||||||
timeseries.datapoints = _dataProcessor2.default.groupBy(options.interval, _dataProcessor2.default.AVERAGE, timeseries.datapoints);
|
timeseries.datapoints = _dataProcessor2.default.groupBy(options.interval, consolidateByFunc, timeseries.datapoints);
|
||||||
}
|
}
|
||||||
return timeseries;
|
return timeseries;
|
||||||
});
|
});
|
||||||
@@ -625,6 +767,13 @@ function zabbixTemplateFormat(value) {
|
|||||||
return '(' + escapedValues.join('|') + ')';
|
return '(' + escapedValues.join('|') + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zabbixItemIdsTemplateFormat(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If template variables are used in request, replace it using regex format
|
* If template variables are used in request, replace it using regex format
|
||||||
* and wrap with '/' for proper multi-value work. Example:
|
* and wrap with '/' for proper multi-value work. Example:
|
||||||
|
|||||||
10
dist/test/datasource-zabbix/module.js
vendored
10
dist/test/datasource-zabbix/module.js
vendored
@@ -9,14 +9,10 @@ var _datasource = require('./datasource');
|
|||||||
|
|
||||||
var _query = require('./query.controller');
|
var _query = require('./query.controller');
|
||||||
|
|
||||||
|
var _config = require('./config.controller');
|
||||||
|
|
||||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
var ZabbixConfigController = function ZabbixConfigController() {
|
|
||||||
_classCallCheck(this, ZabbixConfigController);
|
|
||||||
};
|
|
||||||
|
|
||||||
ZabbixConfigController.templateUrl = 'datasource-zabbix/partials/config.html';
|
|
||||||
|
|
||||||
var ZabbixQueryOptionsController = function ZabbixQueryOptionsController() {
|
var ZabbixQueryOptionsController = function ZabbixQueryOptionsController() {
|
||||||
_classCallCheck(this, ZabbixQueryOptionsController);
|
_classCallCheck(this, ZabbixQueryOptionsController);
|
||||||
};
|
};
|
||||||
@@ -30,7 +26,7 @@ var ZabbixAnnotationsQueryController = function ZabbixAnnotationsQueryController
|
|||||||
ZabbixAnnotationsQueryController.templateUrl = 'datasource-zabbix/partials/annotations.editor.html';
|
ZabbixAnnotationsQueryController.templateUrl = 'datasource-zabbix/partials/annotations.editor.html';
|
||||||
|
|
||||||
exports.Datasource = _datasource.ZabbixAPIDatasource;
|
exports.Datasource = _datasource.ZabbixAPIDatasource;
|
||||||
exports.ConfigCtrl = ZabbixConfigController;
|
exports.ConfigCtrl = _config.ZabbixDSConfigController;
|
||||||
exports.QueryCtrl = _query.ZabbixQueryController;
|
exports.QueryCtrl = _query.ZabbixQueryController;
|
||||||
exports.QueryOptionsCtrl = ZabbixQueryOptionsController;
|
exports.QueryOptionsCtrl = ZabbixQueryOptionsController;
|
||||||
exports.AnnotationsQueryCtrl = ZabbixAnnotationsQueryController;
|
exports.AnnotationsQueryCtrl = ZabbixAnnotationsQueryController;
|
||||||
|
|||||||
101
dist/test/datasource-zabbix/query.controller.js
vendored
101
dist/test/datasource-zabbix/query.controller.js
vendored
@@ -9,10 +9,6 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|||||||
|
|
||||||
var _sdk = require('app/plugins/sdk');
|
var _sdk = require('app/plugins/sdk');
|
||||||
|
|
||||||
var _angular = require('angular');
|
|
||||||
|
|
||||||
var _angular2 = _interopRequireDefault(_angular);
|
|
||||||
|
|
||||||
var _lodash = require('lodash');
|
var _lodash = require('lodash');
|
||||||
|
|
||||||
var _lodash2 = _interopRequireDefault(_lodash);
|
var _lodash2 = _interopRequireDefault(_lodash);
|
||||||
@@ -64,17 +60,24 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
_this.replaceTemplateVars = _this.datasource.replaceTemplateVars;
|
_this.replaceTemplateVars = _this.datasource.replaceTemplateVars;
|
||||||
_this.templateSrv = templateSrv;
|
_this.templateSrv = templateSrv;
|
||||||
|
|
||||||
_this.editorModes = {
|
_this.editorModes = [{ value: 'num', text: 'Metrics', mode: c.MODE_METRICS }, { value: 'text', text: 'Text', mode: c.MODE_TEXT }, { value: 'itservice', text: 'IT Services', mode: c.MODE_ITSERVICE }, { value: 'itemid', text: 'Item ID', mode: c.MODE_ITEMID }];
|
||||||
0: { value: 'num', text: 'Metrics', mode: c.MODE_METRICS },
|
|
||||||
1: { value: 'itservice', text: 'IT Services', mode: c.MODE_ITSERVICE },
|
_this.$scope.editorMode = {
|
||||||
2: { value: 'text', text: 'Text', mode: c.MODE_TEXT }
|
METRICS: c.MODE_METRICS,
|
||||||
|
TEXT: c.MODE_TEXT,
|
||||||
|
ITSERVICE: c.MODE_ITSERVICE,
|
||||||
|
ITEMID: c.MODE_ITEMID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_this.slaPropertyList = [{ name: "Status", property: "status" }, { name: "SLA", property: "sla" }, { name: "OK time", property: "okTime" }, { name: "Problem time", property: "problemTime" }, { name: "Down time", property: "downtimeTime" }];
|
||||||
|
|
||||||
// Map functions for bs-typeahead
|
// Map functions for bs-typeahead
|
||||||
_this.getGroupNames = _lodash2.default.bind(_this.getMetricNames, _this, 'groupList');
|
_this.getGroupNames = _lodash2.default.bind(_this.getMetricNames, _this, 'groupList');
|
||||||
_this.getHostNames = _lodash2.default.bind(_this.getMetricNames, _this, 'hostList', true);
|
_this.getHostNames = _lodash2.default.bind(_this.getMetricNames, _this, 'hostList', true);
|
||||||
_this.getApplicationNames = _lodash2.default.bind(_this.getMetricNames, _this, 'appList');
|
_this.getApplicationNames = _lodash2.default.bind(_this.getMetricNames, _this, 'appList');
|
||||||
_this.getItemNames = _lodash2.default.bind(_this.getMetricNames, _this, 'itemList');
|
_this.getItemNames = _lodash2.default.bind(_this.getMetricNames, _this, 'itemList');
|
||||||
|
_this.getITServices = _lodash2.default.bind(_this.getMetricNames, _this, 'itServiceList');
|
||||||
|
_this.getVariables = _lodash2.default.bind(_this.getTemplateVariables, _this);
|
||||||
|
|
||||||
// Update metric suggestion when template variable was changed
|
// Update metric suggestion when template variable was changed
|
||||||
$rootScope.$on('template-variable-value-updated', function () {
|
$rootScope.$on('template-variable-value-updated', function () {
|
||||||
@@ -101,14 +104,14 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
|
|
||||||
// Load default values
|
// Load default values
|
||||||
var targetDefaults = {
|
var targetDefaults = {
|
||||||
mode: c.MODE_METRICS,
|
'mode': c.MODE_METRICS,
|
||||||
group: { filter: "" },
|
'group': { 'filter': "" },
|
||||||
host: { filter: "" },
|
'host': { 'filter': "" },
|
||||||
application: { filter: "" },
|
'application': { 'filter': "" },
|
||||||
item: { filter: "" },
|
'item': { 'filter': "" },
|
||||||
functions: [],
|
'functions': [],
|
||||||
options: {
|
'options': {
|
||||||
showDisabledItems: false
|
'showDisabledItems': false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_lodash2.default.defaults(target, targetDefaults);
|
_lodash2.default.defaults(target, targetDefaults);
|
||||||
@@ -120,13 +123,10 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
|
|
||||||
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT) {
|
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT) {
|
||||||
|
|
||||||
this.downsampleFunctionList = [{ name: "avg", value: "avg" }, { name: "min", value: "min" }, { name: "max", value: "max" }, { name: "sum", value: "sum" }, { name: "count", value: "count" }];
|
|
||||||
|
|
||||||
this.initFilters();
|
this.initFilters();
|
||||||
} else if (target.mode === c.MODE_ITSERVICE) {
|
} else if (target.mode === c.MODE_ITSERVICE) {
|
||||||
this.slaPropertyList = [{ name: "Status", property: "status" }, { name: "SLA", property: "sla" }, { name: "OK time", property: "okTime" }, { name: "Problem time", property: "problemTime" }, { name: "Down time", property: "downtimeTime" }];
|
_lodash2.default.defaults(target, { slaProperty: { name: "SLA", property: "sla" } });
|
||||||
this.itserviceList = [{ name: "test" }];
|
this.suggestITServices();
|
||||||
this.updateITServiceList();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,7 +137,8 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
_createClass(ZabbixQueryController, [{
|
_createClass(ZabbixQueryController, [{
|
||||||
key: 'initFilters',
|
key: 'initFilters',
|
||||||
value: function initFilters() {
|
value: function initFilters() {
|
||||||
var itemtype = this.editorModes[this.target.mode].value;
|
var itemtype = _lodash2.default.find(this.editorModes, { 'mode': this.target.mode });
|
||||||
|
itemtype = itemtype ? itemtype.value : null;
|
||||||
return Promise.all([this.suggestGroups(), this.suggestHosts(), this.suggestApps(), this.suggestItems(itemtype)]);
|
return Promise.all([this.suggestGroups(), this.suggestHosts(), this.suggestApps(), this.suggestItems(itemtype)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +160,13 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
|
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getTemplateVariables',
|
||||||
|
value: function getTemplateVariables() {
|
||||||
|
return _lodash2.default.map(this.templateSrv.variables, function (variable) {
|
||||||
|
return '$' + variable.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'suggestGroups',
|
key: 'suggestGroups',
|
||||||
value: function suggestGroups() {
|
value: function suggestGroups() {
|
||||||
@@ -212,6 +220,16 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
return items;
|
return items;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'suggestITServices',
|
||||||
|
value: function suggestITServices() {
|
||||||
|
var _this6 = this;
|
||||||
|
|
||||||
|
return this.zabbix.getITService().then(function (itservices) {
|
||||||
|
_this6.metric.itServiceList = itservices;
|
||||||
|
return itservices;
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'isRegex',
|
key: 'isRegex',
|
||||||
value: function isRegex(str) {
|
value: function isRegex(str) {
|
||||||
@@ -246,11 +264,11 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
}, {
|
}, {
|
||||||
key: 'isContainsVariables',
|
key: 'isContainsVariables',
|
||||||
value: function isContainsVariables() {
|
value: function isContainsVariables() {
|
||||||
var _this6 = this;
|
var _this7 = this;
|
||||||
|
|
||||||
return _lodash2.default.some(['group', 'host', 'application'], function (field) {
|
return _lodash2.default.some(['group', 'host', 'application'], function (field) {
|
||||||
if (_this6.target[field] && _this6.target[field].filter) {
|
if (_this7.target[field] && _this7.target[field].filter) {
|
||||||
return utils.isTemplateVariable(_this6.target[field].filter, _this6.templateSrv.variables);
|
return utils.isTemplateVariable(_this7.target[field].filter, _this7.templateSrv.variables);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -352,38 +370,7 @@ var ZabbixQueryController = exports.ZabbixQueryController = function (_QueryCtrl
|
|||||||
value: function switchEditorMode(mode) {
|
value: function switchEditorMode(mode) {
|
||||||
this.target.mode = mode;
|
this.target.mode = mode;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
this.targetChanged();
|
||||||
|
|
||||||
/////////////////
|
|
||||||
// IT Services //
|
|
||||||
/////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update list of IT services
|
|
||||||
*/
|
|
||||||
|
|
||||||
}, {
|
|
||||||
key: 'updateITServiceList',
|
|
||||||
value: function updateITServiceList() {
|
|
||||||
var _this7 = this;
|
|
||||||
|
|
||||||
this.zabbix.getITService().then(function (iteservices) {
|
|
||||||
_this7.itserviceList = [];
|
|
||||||
_this7.itserviceList = _this7.itserviceList.concat(iteservices);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call when IT service is selected.
|
|
||||||
*/
|
|
||||||
|
|
||||||
}, {
|
|
||||||
key: 'selectITService',
|
|
||||||
value: function selectITService() {
|
|
||||||
if (!_lodash2.default.isEqual(this.oldTarget, this.target) && _lodash2.default.isEmpty(this.target.errors)) {
|
|
||||||
this.oldTarget = _angular2.default.copy(this.target);
|
|
||||||
this.panelCtrl.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ describe('ZabbixDatasource', function () {
|
|||||||
password: 'zabbix',
|
password: 'zabbix',
|
||||||
trends: true,
|
trends: true,
|
||||||
trendsFrom: '14d',
|
trendsFrom: '14d',
|
||||||
trendsRange: '7d'
|
trendsRange: '7d',
|
||||||
|
dbConnection: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ctx.templateSrv = {};
|
ctx.templateSrv = {};
|
||||||
|
|||||||
42
dist/test/datasource-zabbix/specs/utils_specs.js
vendored
42
dist/test/datasource-zabbix/specs/utils_specs.js
vendored
@@ -86,4 +86,46 @@ describe('Utils', function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('splitTemplateQuery()', function () {
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
it('should properly split query in old format', function (done) {
|
||||||
|
var test_cases = [{
|
||||||
|
query: '/alu/./tw-(nyc|que|brx|dwt|brk)-sta_(w|d)*-alu-[0-9{2}/',
|
||||||
|
expected: ['/alu/', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9{2}/']
|
||||||
|
}, {
|
||||||
|
query: 'a.b.c.d',
|
||||||
|
expected: ['a', 'b', 'c', 'd']
|
||||||
|
}];
|
||||||
|
|
||||||
|
_lodash2.default.each(test_cases, function (test_case) {
|
||||||
|
var splitQuery = utils.splitTemplateQuery(test_case.query);
|
||||||
|
expect(splitQuery).to.eql(test_case.expected);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly split query', function (done) {
|
||||||
|
var test_cases = [{
|
||||||
|
query: '{alu}{/tw-(nyc|que|brx|dwt|brk)-sta_(w|d)*-alu-[0-9]*/}',
|
||||||
|
expected: ['alu', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]*/']
|
||||||
|
}, {
|
||||||
|
query: '{alu}{/tw-(nyc|que|brx|dwt|brk)-sta_(w|d)*-alu-[0-9]{2}/}',
|
||||||
|
expected: ['alu', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]{2}/']
|
||||||
|
}, {
|
||||||
|
query: '{a}{b}{c}{d}',
|
||||||
|
expected: ['a', 'b', 'c', 'd']
|
||||||
|
}, {
|
||||||
|
query: '{a}{b.c.d}',
|
||||||
|
expected: ['a', 'b.c.d']
|
||||||
|
}];
|
||||||
|
|
||||||
|
_lodash2.default.each(test_cases, function (test_case) {
|
||||||
|
var splitQuery = utils.splitTemplateQuery(test_case.query);
|
||||||
|
expect(splitQuery).to.eql(test_case.expected);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
15
dist/test/datasource-zabbix/utils.js
vendored
15
dist/test/datasource-zabbix/utils.js
vendored
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|||||||
});
|
});
|
||||||
exports.regexPattern = undefined;
|
exports.regexPattern = undefined;
|
||||||
exports.expandItemName = expandItemName;
|
exports.expandItemName = expandItemName;
|
||||||
|
exports.expandItems = expandItems;
|
||||||
exports.containsMacro = containsMacro;
|
exports.containsMacro = containsMacro;
|
||||||
exports.replaceMacro = replaceMacro;
|
exports.replaceMacro = replaceMacro;
|
||||||
exports.splitTemplateQuery = splitTemplateQuery;
|
exports.splitTemplateQuery = splitTemplateQuery;
|
||||||
@@ -49,6 +50,15 @@ function expandItemName(name, key) {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expandItems(items) {
|
||||||
|
_lodash2.default.forEach(items, function (item) {
|
||||||
|
item.item = item.name;
|
||||||
|
item.name = expandItemName(item.item, item.key_);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
function splitKeyParams(paramStr) {
|
function splitKeyParams(paramStr) {
|
||||||
var params = [];
|
var params = [];
|
||||||
var quoted = false;
|
var quoted = false;
|
||||||
@@ -120,7 +130,7 @@ function escapeMacro(macro) {
|
|||||||
* {group}{host.com} -> [group, host.com]
|
* {group}{host.com} -> [group, host.com]
|
||||||
*/
|
*/
|
||||||
function splitTemplateQuery(query) {
|
function splitTemplateQuery(query) {
|
||||||
var splitPattern = /{[^{}]*}/g;
|
var splitPattern = /\{[^\{\}]*\}|\{\/.*\/\}/g;
|
||||||
var split = void 0;
|
var split = void 0;
|
||||||
|
|
||||||
if (isContainsBraces(query)) {
|
if (isContainsBraces(query)) {
|
||||||
@@ -136,7 +146,8 @@ function splitTemplateQuery(query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isContainsBraces(query) {
|
function isContainsBraces(query) {
|
||||||
return query.includes('{') && query.includes('}');
|
var bracesPattern = /^\{.+\}$/;
|
||||||
|
return bracesPattern.test(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern for testing regex
|
// Pattern for testing regex
|
||||||
|
|||||||
34
dist/test/datasource-zabbix/zabbix.js
vendored
34
dist/test/datasource-zabbix/zabbix.js
vendored
@@ -18,6 +18,8 @@ require('./zabbixAPI.service.js');
|
|||||||
|
|
||||||
require('./zabbixCachingProxy.service.js');
|
require('./zabbixCachingProxy.service.js');
|
||||||
|
|
||||||
|
require('./zabbixDBConnector');
|
||||||
|
|
||||||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
||||||
|
|
||||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
@@ -30,25 +32,44 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|||||||
// Each Zabbix data source instance should initialize its own API instance.
|
// Each Zabbix data source instance should initialize its own API instance.
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) {
|
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy, ZabbixDBConnector) {
|
||||||
var Zabbix = function () {
|
var Zabbix = function () {
|
||||||
function Zabbix(url, username, password, basicAuth, withCredentials, cacheTTL) {
|
function Zabbix(url, options) {
|
||||||
_classCallCheck(this, Zabbix);
|
_classCallCheck(this, Zabbix);
|
||||||
|
|
||||||
|
var username = options.username,
|
||||||
|
password = options.password,
|
||||||
|
basicAuth = options.basicAuth,
|
||||||
|
withCredentials = options.withCredentials,
|
||||||
|
cacheTTL = options.cacheTTL,
|
||||||
|
enableDirectDBConnection = options.enableDirectDBConnection,
|
||||||
|
sqlDatasourceId = options.sqlDatasourceId;
|
||||||
|
|
||||||
// Initialize Zabbix API
|
// Initialize Zabbix API
|
||||||
|
|
||||||
var ZabbixAPI = zabbixAPIService;
|
var ZabbixAPI = zabbixAPIService;
|
||||||
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
||||||
|
|
||||||
|
if (enableDirectDBConnection) {
|
||||||
|
this.dbConnector = new ZabbixDBConnector(sqlDatasourceId);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize caching proxy for requests
|
// Initialize caching proxy for requests
|
||||||
var cacheOptions = {
|
var cacheOptions = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ttl: cacheTTL
|
ttl: cacheTTL
|
||||||
};
|
};
|
||||||
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheOptions);
|
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, this.dbConnector, cacheOptions);
|
||||||
|
|
||||||
// Proxy methods
|
// Proxy methods
|
||||||
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
||||||
this.getMacros = this.cachingProxy.getMacros.bind(this.cachingProxy);
|
this.getMacros = this.cachingProxy.getMacros.bind(this.cachingProxy);
|
||||||
|
this.getItemsByIDs = this.cachingProxy.getItemsByIDs.bind(this.cachingProxy);
|
||||||
|
|
||||||
|
if (enableDirectDBConnection) {
|
||||||
|
this.getHistoryDB = this.cachingProxy.getHistoryDB.bind(this.cachingProxy);
|
||||||
|
this.getTrendsDB = this.cachingProxy.getTrendsDB.bind(this.cachingProxy);
|
||||||
|
}
|
||||||
|
|
||||||
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
||||||
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
||||||
@@ -181,6 +202,13 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) {
|
|||||||
return filterByQuery(items, itemFilter);
|
return filterByQuery(items, itemFilter);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getITServices',
|
||||||
|
value: function getITServices(itServiceFilter) {
|
||||||
|
return this.cachingProxy.getITServices().then(function (itServices) {
|
||||||
|
return findByFilter(itServices, itServiceFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build query - convert target filters to array of Zabbix items
|
* Build query - convert target filters to array of Zabbix items
|
||||||
|
|||||||
21
dist/test/datasource-zabbix/zabbixAPI.service.js
vendored
21
dist/test/datasource-zabbix/zabbixAPI.service.js
vendored
@@ -217,16 +217,19 @@ function ZabbixAPIServiceFactory(alertSrv, zabbixAPICoreService) {
|
|||||||
params.filter.value_type = [1, 2, 4];
|
params.filter.value_type = [1, 2, 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.request('item.get', params).then(expandItems);
|
return this.request('item.get', params).then(utils.expandItems);
|
||||||
|
|
||||||
function expandItems(items) {
|
|
||||||
_lodash2.default.forEach(items, function (item) {
|
|
||||||
item.item = item.name;
|
|
||||||
item.name = utils.expandItemName(item.item, item.key_);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getItemsByIDs',
|
||||||
|
value: function getItemsByIDs(itemids) {
|
||||||
|
var params = {
|
||||||
|
itemids: itemids,
|
||||||
|
output: ['name', 'key_', 'value_type', 'hostid', 'status', 'state'],
|
||||||
|
webitems: true,
|
||||||
|
selectHosts: ['hostid', 'name']
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.request('item.get', params).then(utils.expandItems);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getMacros',
|
key: 'getMacros',
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ZabbixCachingProxyFactory() {
|
function ZabbixCachingProxyFactory() {
|
||||||
var ZabbixCachingProxy = function () {
|
var ZabbixCachingProxy = function () {
|
||||||
function ZabbixCachingProxy(zabbixAPI, cacheOptions) {
|
function ZabbixCachingProxy(zabbixAPI, zabbixDBConnector, cacheOptions) {
|
||||||
_classCallCheck(this, ZabbixCachingProxy);
|
_classCallCheck(this, ZabbixCachingProxy);
|
||||||
|
|
||||||
this.zabbixAPI = zabbixAPI;
|
this.zabbixAPI = zabbixAPI;
|
||||||
|
this.dbConnector = zabbixDBConnector;
|
||||||
this.cacheEnabled = cacheOptions.enabled;
|
this.cacheEnabled = cacheOptions.enabled;
|
||||||
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
||||||
|
|
||||||
@@ -38,7 +39,8 @@ function ZabbixCachingProxyFactory() {
|
|||||||
history: {},
|
history: {},
|
||||||
trends: {},
|
trends: {},
|
||||||
macros: {},
|
macros: {},
|
||||||
globalMacros: {}
|
globalMacros: {},
|
||||||
|
itServices: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.historyPromises = {};
|
this.historyPromises = {};
|
||||||
@@ -46,6 +48,11 @@ function ZabbixCachingProxyFactory() {
|
|||||||
// Don't run duplicated history requests
|
// Don't run duplicated history requests
|
||||||
this.getHistory = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getHistory, this.zabbixAPI), this.historyPromises, getHistoryRequestHash);
|
this.getHistory = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getHistory, this.zabbixAPI), this.historyPromises, getHistoryRequestHash);
|
||||||
|
|
||||||
|
if (this.dbConnector) {
|
||||||
|
this.getHistoryDB = callAPIRequestOnce(_lodash2.default.bind(this.dbConnector.getHistory, this.dbConnector), this.historyPromises, getDBQueryHash);
|
||||||
|
this.getTrendsDB = callAPIRequestOnce(_lodash2.default.bind(this.dbConnector.getTrends, this.dbConnector), this.historyPromises, getDBQueryHash);
|
||||||
|
}
|
||||||
|
|
||||||
// Don't run duplicated requests
|
// Don't run duplicated requests
|
||||||
this.groupPromises = {};
|
this.groupPromises = {};
|
||||||
this.getGroupsOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getGroups, this.zabbixAPI), this.groupPromises, getRequestHash);
|
this.getGroupsOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getGroups, this.zabbixAPI), this.groupPromises, getRequestHash);
|
||||||
@@ -59,6 +66,12 @@ function ZabbixCachingProxyFactory() {
|
|||||||
this.itemPromises = {};
|
this.itemPromises = {};
|
||||||
this.getItemsOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getItems, this.zabbixAPI), this.itemPromises, getRequestHash);
|
this.getItemsOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getItems, this.zabbixAPI), this.itemPromises, getRequestHash);
|
||||||
|
|
||||||
|
this.itemByIdPromises = {};
|
||||||
|
this.getItemsByIdOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getItemsByIDs, this.zabbixAPI), this.itemPromises, getRequestHash);
|
||||||
|
|
||||||
|
this.itServicesPromises = {};
|
||||||
|
this.getITServicesOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getITService, this.zabbixAPI), this.itServicesPromises, getRequestHash);
|
||||||
|
|
||||||
this.macroPromises = {};
|
this.macroPromises = {};
|
||||||
this.getMacrosOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getMacros, this.zabbixAPI), this.macroPromises, getRequestHash);
|
this.getMacrosOnce = callAPIRequestOnce(_lodash2.default.bind(this.zabbixAPI.getMacros, this.zabbixAPI), this.macroPromises, getRequestHash);
|
||||||
|
|
||||||
@@ -119,6 +132,17 @@ function ZabbixCachingProxyFactory() {
|
|||||||
var params = [hostids, appids, itemtype];
|
var params = [hostids, appids, itemtype];
|
||||||
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getItemsByIDs',
|
||||||
|
value: function getItemsByIDs(itemids) {
|
||||||
|
var params = [itemids];
|
||||||
|
return this.proxyRequest(this.getItemsByIdOnce, params, this.cache.items);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getITServices',
|
||||||
|
value: function getITServices() {
|
||||||
|
return this.proxyRequest(this.getITServicesOnce, [], this.cache.itServices);
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getMacros',
|
key: 'getMacros',
|
||||||
value: function getMacros(hostids) {
|
value: function getMacros(hostids) {
|
||||||
@@ -210,6 +234,14 @@ function getHistoryRequestHash(args) {
|
|||||||
return stamp.getHash();
|
return stamp.getHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDBQueryHash(args) {
|
||||||
|
var itemids = _lodash2.default.map(args[0], 'itemid');
|
||||||
|
var consolidateBy = args[3].consolidateBy;
|
||||||
|
var intervalMs = args[3].intervalMs;
|
||||||
|
var stamp = itemids.join() + args[1] + args[2] + consolidateBy + intervalMs;
|
||||||
|
return stamp.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
String.prototype.getHash = function () {
|
String.prototype.getHash = function () {
|
||||||
var hash = 0,
|
var hash = 0,
|
||||||
i,
|
i,
|
||||||
|
|||||||
217
dist/test/datasource-zabbix/zabbixDBConnector.js
vendored
Normal file
217
dist/test/datasource-zabbix/zabbixDBConnector.js
vendored
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
var _angular = require('angular');
|
||||||
|
|
||||||
|
var _angular2 = _interopRequireDefault(_angular);
|
||||||
|
|
||||||
|
var _lodash = require('lodash');
|
||||||
|
|
||||||
|
var _lodash2 = _interopRequireDefault(_lodash);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
|
var DEFAULT_QUERY_LIMIT = 10000;
|
||||||
|
var HISTORY_TO_TABLE_MAP = {
|
||||||
|
'0': 'history',
|
||||||
|
'1': 'history_str',
|
||||||
|
'2': 'history_log',
|
||||||
|
'3': 'history_uint',
|
||||||
|
'4': 'history_text'
|
||||||
|
};
|
||||||
|
|
||||||
|
var TREND_TO_TABLE_MAP = {
|
||||||
|
'0': 'trends',
|
||||||
|
'3': 'trends_uint'
|
||||||
|
};
|
||||||
|
|
||||||
|
var consolidateByFunc = {
|
||||||
|
'avg': 'AVG',
|
||||||
|
'min': 'MIN',
|
||||||
|
'max': 'MAX',
|
||||||
|
'sum': 'SUM',
|
||||||
|
'count': 'COUNT'
|
||||||
|
};
|
||||||
|
|
||||||
|
var consolidateByTrendColumns = {
|
||||||
|
'avg': 'value_avg',
|
||||||
|
'min': 'value_min',
|
||||||
|
'max': 'value_max'
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
function ZabbixDBConnectorFactory(datasourceSrv, backendSrv) {
|
||||||
|
var ZabbixDBConnector = function () {
|
||||||
|
function ZabbixDBConnector(sqlDataSourceId) {
|
||||||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
|
||||||
|
_classCallCheck(this, ZabbixDBConnector);
|
||||||
|
|
||||||
|
var limit = options.limit;
|
||||||
|
|
||||||
|
|
||||||
|
this.sqlDataSourceId = sqlDataSourceId;
|
||||||
|
this.limit = limit || DEFAULT_QUERY_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to load DS with given id to check it's exist.
|
||||||
|
* @param {*} datasourceId ID of SQL data source
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(ZabbixDBConnector, [{
|
||||||
|
key: 'loadSQLDataSource',
|
||||||
|
value: function loadSQLDataSource(datasourceId) {
|
||||||
|
var ds = _lodash2.default.find(datasourceSrv.getAll(), { 'id': datasourceId });
|
||||||
|
if (ds) {
|
||||||
|
return datasourceSrv.loadDatasource(ds.name).then(function (ds) {
|
||||||
|
console.log('SQL data source loaded', ds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.reject('SQL Data Source with ID ' + datasourceId + ' not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to invoke test query for one of Zabbix database tables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'testSQLDataSource',
|
||||||
|
value: function testSQLDataSource() {
|
||||||
|
var testQuery = 'SELECT itemid AS metric, clock AS time_sec, value_avg AS value FROM trends_uint LIMIT 1';
|
||||||
|
return this.invokeSQLQuery(testQuery);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getHistory',
|
||||||
|
value: function getHistory(items, timeFrom, timeTill, options) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
var intervalMs = options.intervalMs,
|
||||||
|
consolidateBy = options.consolidateBy;
|
||||||
|
|
||||||
|
var intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
var aggFunction = consolidateByFunc[consolidateBy];
|
||||||
|
|
||||||
|
// Group items by value type and perform request for each value type
|
||||||
|
var grouped_items = _lodash2.default.groupBy(items, 'value_type');
|
||||||
|
var promises = _lodash2.default.map(grouped_items, function (items, value_type) {
|
||||||
|
var itemids = _lodash2.default.map(items, 'itemid').join(', ');
|
||||||
|
var table = HISTORY_TO_TABLE_MAP[value_type];
|
||||||
|
|
||||||
|
var query = '\n SELECT itemid AS metric, clock AS time_sec, ' + aggFunction + '(value) AS value\n FROM ' + table + '\n WHERE itemid IN (' + itemids + ')\n AND clock > ' + timeFrom + ' AND clock < ' + timeTill + '\n GROUP BY time_sec DIV ' + intervalSec + ', metric\n ';
|
||||||
|
|
||||||
|
query = compactSQLQuery(query);
|
||||||
|
return _this.invokeSQLQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function (results) {
|
||||||
|
return _lodash2.default.flatten(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'getTrends',
|
||||||
|
value: function getTrends(items, timeFrom, timeTill, options) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
var intervalMs = options.intervalMs,
|
||||||
|
consolidateBy = options.consolidateBy;
|
||||||
|
|
||||||
|
var intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
var aggFunction = consolidateByFunc[consolidateBy];
|
||||||
|
|
||||||
|
// Group items by value type and perform request for each value type
|
||||||
|
var grouped_items = _lodash2.default.groupBy(items, 'value_type');
|
||||||
|
var promises = _lodash2.default.map(grouped_items, function (items, value_type) {
|
||||||
|
var itemids = _lodash2.default.map(items, 'itemid').join(', ');
|
||||||
|
var table = TREND_TO_TABLE_MAP[value_type];
|
||||||
|
var valueColumn = _lodash2.default.includes(['avg', 'min', 'max'], consolidateBy) ? consolidateBy : 'avg';
|
||||||
|
valueColumn = consolidateByTrendColumns[valueColumn];
|
||||||
|
|
||||||
|
var query = '\n SELECT itemid AS metric, clock AS time_sec, ' + aggFunction + '(' + valueColumn + ') AS value\n FROM ' + table + '\n WHERE itemid IN (' + itemids + ')\n AND clock > ' + timeFrom + ' AND clock < ' + timeTill + '\n GROUP BY time_sec DIV ' + intervalSec + ', metric\n ';
|
||||||
|
|
||||||
|
query = compactSQLQuery(query);
|
||||||
|
return _this2.invokeSQLQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function (results) {
|
||||||
|
return _lodash2.default.flatten(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'handleGrafanaTSResponse',
|
||||||
|
value: function handleGrafanaTSResponse(history, items) {
|
||||||
|
var addHostName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
||||||
|
|
||||||
|
return convertGrafanaTSResponse(history, items, addHostName);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'invokeSQLQuery',
|
||||||
|
value: function invokeSQLQuery(query) {
|
||||||
|
var queryDef = {
|
||||||
|
refId: 'A',
|
||||||
|
format: 'time_series',
|
||||||
|
datasourceId: this.sqlDataSourceId,
|
||||||
|
rawSql: query,
|
||||||
|
maxDataPoints: this.limit
|
||||||
|
};
|
||||||
|
|
||||||
|
return backendSrv.datasourceRequest({
|
||||||
|
url: '/api/tsdb/query',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
queries: [queryDef]
|
||||||
|
}
|
||||||
|
}).then(function (response) {
|
||||||
|
var results = response.data.results;
|
||||||
|
if (results['A']) {
|
||||||
|
return results['A'].series;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return ZabbixDBConnector;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return ZabbixDBConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
_angular2.default.module('grafana.services').factory('ZabbixDBConnector', ZabbixDBConnectorFactory);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function convertGrafanaTSResponse(time_series, items, addHostName) {
|
||||||
|
var hosts = _lodash2.default.uniqBy(_lodash2.default.flatten(_lodash2.default.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate
|
||||||
|
var grafanaSeries = _lodash2.default.map(time_series, function (series) {
|
||||||
|
var itemid = series.name;
|
||||||
|
var datapoints = series.points;
|
||||||
|
var item = _lodash2.default.find(items, { 'itemid': itemid });
|
||||||
|
var alias = item.name;
|
||||||
|
if (_lodash2.default.keys(hosts).length > 1 && addHostName) {
|
||||||
|
//only when actual multi hosts selected
|
||||||
|
var host = _lodash2.default.find(hosts, { 'hostid': item.hostid });
|
||||||
|
alias = host.name + ": " + alias;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
target: alias,
|
||||||
|
datapoints: datapoints
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return _lodash2.default.sortBy(grafanaSeries, 'target');
|
||||||
|
}
|
||||||
|
|
||||||
|
function compactSQLQuery(query) {
|
||||||
|
return query.replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
8
dist/test/panel-triggers/module.js
vendored
8
dist/test/panel-triggers/module.js
vendored
@@ -165,6 +165,7 @@ var TriggerPanelCtrl = function (_PanelCtrl) {
|
|||||||
return this.datasourceSrv.get(this.panel.datasource).then(function (datasource) {
|
return this.datasourceSrv.get(this.panel.datasource).then(function (datasource) {
|
||||||
var zabbix = datasource.zabbix;
|
var zabbix = datasource.zabbix;
|
||||||
_this3.zabbix = zabbix;
|
_this3.zabbix = zabbix;
|
||||||
|
_this3.datasource = datasource;
|
||||||
var showEvents = _this3.panel.showEvents.value;
|
var showEvents = _this3.panel.showEvents.value;
|
||||||
var triggerFilter = _this3.panel.triggers;
|
var triggerFilter = _this3.panel.triggers;
|
||||||
var hideHostsInMaintenance = _this3.panel.hideHostsInMaintenance;
|
var hideHostsInMaintenance = _this3.panel.hideHostsInMaintenance;
|
||||||
@@ -178,11 +179,11 @@ var TriggerPanelCtrl = function (_PanelCtrl) {
|
|||||||
showTriggers: showEvents,
|
showTriggers: showEvents,
|
||||||
hideHostsInMaintenance: hideHostsInMaintenance
|
hideHostsInMaintenance: hideHostsInMaintenance
|
||||||
};
|
};
|
||||||
var getTriggers = zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions);
|
|
||||||
return getTriggers.then(function (triggers) {
|
return zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions);
|
||||||
|
}).then(function (triggers) {
|
||||||
return _lodash2.default.map(triggers, _this3.formatTrigger.bind(_this3));
|
return _lodash2.default.map(triggers, _this3.formatTrigger.bind(_this3));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
key: 'getAcknowledges',
|
key: 'getAcknowledges',
|
||||||
@@ -231,6 +232,7 @@ var TriggerPanelCtrl = function (_PanelCtrl) {
|
|||||||
|
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
var triggerFilter = this.panel.triggers.trigger.filter;
|
var triggerFilter = this.panel.triggers.trigger.filter;
|
||||||
|
triggerFilter = this.datasource.replaceTemplateVars(triggerFilter);
|
||||||
if (triggerFilter) {
|
if (triggerFilter) {
|
||||||
triggerList = _filterTriggers(triggerList, triggerFilter);
|
triggerList = _filterTriggers(triggerList, triggerFilter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pages:
|
|||||||
- Installation:
|
- Installation:
|
||||||
- 'Installation': 'installation/index.md'
|
- 'Installation': 'installation/index.md'
|
||||||
- 'Configuration': 'installation/configuration.md'
|
- 'Configuration': 'installation/configuration.md'
|
||||||
|
- 'SQL Data Source Configuration': 'installation/configuration-sql.md'
|
||||||
- 'Upgrade': 'installation/upgrade.md'
|
- 'Upgrade': 'installation/upgrade.md'
|
||||||
- 'Troubleshooting': 'installation/troubleshooting.md'
|
- 'Troubleshooting': 'installation/troubleshooting.md'
|
||||||
- User Guides:
|
- User Guides:
|
||||||
@@ -28,5 +29,6 @@ pages:
|
|||||||
- 'Triggers Panel': 'reference/panel-triggers.md'
|
- 'Triggers Panel': 'reference/panel-triggers.md'
|
||||||
- 'Functions': 'reference/functions.md'
|
- 'Functions': 'reference/functions.md'
|
||||||
- 'Alerting': 'reference/alerting.md'
|
- 'Alerting': 'reference/alerting.md'
|
||||||
|
- 'Direct DB Connection': 'reference/direct-db-connection.md'
|
||||||
# - Tutorials:
|
# - Tutorials:
|
||||||
# - 'Building Host Dashboard': 'tutorials/host_dashboard.md'
|
# - 'Building Host Dashboard': 'tutorials/host_dashboard.md'
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ page_description: Grafana-Zabbix Feature Highlights.
|
|||||||
Grafana in couple with Grafana-Zabbix plugin allows to create great dashboards. There is some
|
Grafana in couple with Grafana-Zabbix plugin allows to create great dashboards. There is some
|
||||||
features:
|
features:
|
||||||
|
|
||||||
- Rich graphing with Grafana
|
- Rich graphing features
|
||||||
- Template variables allow to create reusable dashboards
|
- Create interactive and reusable dashboards with [template variables](http://docs.grafana-zabbix.org/guides/templating/)
|
||||||
|
- Show events on graphs with [Annotations](http://docs.grafana.org/reference/annotations/)
|
||||||
|
- Select multiple metrics [by using Regex](http://docs.grafana-zabbix.org/guides/gettingstarted/#multiple-items-on-one-graph)
|
||||||
|
- Display active problems with Triggers panel
|
||||||
|
- Transform and shape your data with [metric processing functions](http://docs.grafana-zabbix.org/reference/functions/) (Avg, Median, Min, Max, Multiply, Summarize, Time shift, Alias)
|
||||||
|
- Find problems faster with [Alerting](http://docs.grafana-zabbix.org/reference/alerting/) feature
|
||||||
|
- Mix metrics from multiple data sources in the same dashboard or even graph
|
||||||
|
- Discover and share [dashboards](https://grafana.com/dashboards) in the official library
|
||||||
|
|||||||
1
docs/sources/img/.gitattributes
vendored
1
docs/sources/img/.gitattributes
vendored
@@ -1,3 +1,4 @@
|
|||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
*.psd filter=lfs diff=lfs merge=lfs -text
|
*.psd filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.svg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6b58742637f58519a5fab038db0c15b5525d1d330fe5e4a1de954b72f4f1cc3e
|
oid sha256:367b5fa51e9d05eb87afedd4ef6ed8ea1ce0e095f94bfdc58ebe6744fbaca71c
|
||||||
size 225446
|
size 131343
|
||||||
|
|||||||
3
docs/sources/img/installation-mysql_ds_config.png
Normal file
3
docs/sources/img/installation-mysql_ds_config.png
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0bac25f3d28c74f1f309476120bc67275e15f420e3343f0ff9668ead3a1e6170
|
||||||
|
size 227988
|
||||||
3
docs/sources/img/reference-direct-db-connection.svg
Normal file
3
docs/sources/img/reference-direct-db-connection.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f0b942ebdeec31f98ae800c78223fb96ec077e8396e2f33a71e29d4d6d096661
|
||||||
|
size 200108
|
||||||
22
docs/sources/installation/configuration-sql.md
Normal file
22
docs/sources/installation/configuration-sql.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# SQL Data Source Configuration
|
||||||
|
|
||||||
|
In order to use _Direct DB Connection_ feature you should configure SQL data source first.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Select _MySQL_ data source type and provide your database host address and port (3306 is default for MySQL). Fill
|
||||||
|
database name (usually, `zabbix`) and specify credentials.
|
||||||
|
|
||||||
|
## Security notes
|
||||||
|
|
||||||
|
As you can see in _User Permission_ note, Grafana doesn't restrict any queries to the database. So you should be careful
|
||||||
|
and create a special user with limited access to Zabbix database. Grafana-Zabbix plugin uses only `SELECT` queries to
|
||||||
|
`history`, `history_uint`, `trends` and `trends_uint` tables. So it's reasonable to grant only SELECT privileges to
|
||||||
|
these tables for grafana user. But if you want to use this MySQL data source for querying another data, you can
|
||||||
|
grant SELECT privileges to entire zabbix database.
|
||||||
|
|
||||||
|
Also, all queries are invoked by grafana-server, so you can restrict connection to only grafana host.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
GRANT SELECT ON zabbix.* TO 'grafana'@'grafana-host' identified by 'password';
|
||||||
|
```
|
||||||
@@ -52,7 +52,23 @@ Direct access is still supported because in some cases it may be useful to acces
|
|||||||
- **Cache TTL**: plugin caches some api requests for increasing performance. Set this
|
- **Cache TTL**: plugin caches some api requests for increasing performance. Set this
|
||||||
value to desired cache lifetime (this option affect data like items list).
|
value to desired cache lifetime (this option affect data like items list).
|
||||||
|
|
||||||
|
### Direct DB Connection
|
||||||
|
|
||||||
|
Direct DB Connection allows plugin to use existing SQL data source for querying history data directly from Zabbix
|
||||||
|
database. This way usually faster than pulling data from Zabbix API, especially on the wide time ranges, and reduces
|
||||||
|
amount of data transfered.
|
||||||
|
|
||||||
|
Read [how to configure](/installation/configuration-sql) SQL data source in Grafana.
|
||||||
|
|
||||||
|
- **Enable**: enable Direct DB Connection.
|
||||||
|
- **SQL Data Source**: Select SQL Data Source for Zabbix database.
|
||||||
|
|
||||||
|
#### Supported databases
|
||||||
|
|
||||||
|
Now only **MySQL** is supported by Grafana.
|
||||||
|
|
||||||
### Alerting
|
### Alerting
|
||||||
|
|
||||||
- **Enable alerting**: enable limited alerting support.
|
- **Enable alerting**: enable limited alerting support.
|
||||||
- **Add thresholds**: get thresholds info from zabbix triggers and add it to graphs.
|
- **Add thresholds**: get thresholds info from zabbix triggers and add it to graphs.
|
||||||
For example, if you have trigger `{Zabbix server:system.cpu.util[,iowait].avg(5m)}>20`, threshold will be set to 20.
|
For example, if you have trigger `{Zabbix server:system.cpu.util[,iowait].avg(5m)}>20`, threshold will be set to 20.
|
||||||
@@ -65,10 +81,12 @@ or password, wrong api url.
|
|||||||

|

|
||||||
|
|
||||||
## Import example dashboards
|
## Import example dashboards
|
||||||
|
|
||||||
You can import dashboard examples from _Dashboards_ tab in plugin config.
|
You can import dashboard examples from _Dashboards_ tab in plugin config.
|
||||||

|

|
||||||
|
|
||||||
## Note about Zabbix 2.2 or less
|
## Note about Zabbix 2.2 or less
|
||||||
|
|
||||||
Zabbix API (api_jsonrpc.php) before zabbix 2.4 don't allow cross-domain requests (CORS). And you
|
Zabbix API (api_jsonrpc.php) before zabbix 2.4 don't allow cross-domain requests (CORS). And you
|
||||||
can get HTTP error 412 (Precondition Failed).
|
can get HTTP error 412 (Precondition Failed).
|
||||||
To fix it add this code to api_jsonrpc.php immediately after the copyright:
|
To fix it add this code to api_jsonrpc.php immediately after the copyright:
|
||||||
@@ -83,17 +101,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
before
|
before
|
||||||
|
|
||||||
```php
|
```php
|
||||||
require_once dirname(__FILE__).'/include/func.inc.php';
|
require_once dirname(__FILE__).'/include/func.inc.php';
|
||||||
require_once dirname(__FILE__).'/include/classes/core/CHttpRequest.php';
|
require_once dirname(__FILE__).'/include/classes/core/CHttpRequest.php';
|
||||||
```
|
```
|
||||||
|
|
||||||
[Full fix listing](https://gist.github.com/alexanderzobnin/f2348f318d7a93466a0c).
|
[Full fix listing](https://gist.github.com/alexanderzobnin/f2348f318d7a93466a0c).
|
||||||
For more details see zabbix issues [ZBXNEXT-1377](https://support.zabbix.com/browse/ZBXNEXT-1377)
|
For more details see zabbix issues [ZBXNEXT-1377](https://support.zabbix.com/browse/ZBXNEXT-1377)
|
||||||
and [ZBX-8459](https://support.zabbix.com/browse/ZBX-8459).
|
and [ZBX-8459](https://support.zabbix.com/browse/ZBX-8459).
|
||||||
|
|
||||||
## Note about Browser Cache
|
## Note about Browser Cache
|
||||||
|
|
||||||
After updating plugin, clear browser cache and reload application page. See details
|
After updating plugin, clear browser cache and reload application page. See details
|
||||||
for [Chrome](https://support.google.com/chrome/answer/95582),
|
for [Chrome](https://support.google.com/chrome/answer/95582),
|
||||||
[Firefox](https://support.mozilla.org/en-US/kb/how-clear-firefox-cache). You need to clear cache
|
[Firefox](https://support.mozilla.org/en-US/kb/how-clear-firefox-cache). You need to clear cache
|
||||||
|
|||||||
51
docs/sources/reference/direct-db-connection.md
Normal file
51
docs/sources/reference/direct-db-connection.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Direct DB Connection
|
||||||
|
|
||||||
|
Since version 4.3 Grafana has MySQL data source, Grafana-Zabbix plugin can use it for querying data directly from
|
||||||
|
Zabbix database.
|
||||||
|
|
||||||
|
One of the most hard queries for Zabbix API is history queries. For long time intervals `history.get`
|
||||||
|
returns huge amount of data. In order to display it, plugin should adjust time series resolution
|
||||||
|
by using [consolidateBy](/reference/functions/#consolidateby) function. Ultimately, Grafana displays this reduced
|
||||||
|
time series, but that data should be loaded and processed on the client side first. Direct DB Connection solves this
|
||||||
|
two problems by moving consolidation to the server side. Thus, client get ready-to-use dataset which has much smaller
|
||||||
|
size. Data loads faster and client doesn't spend time for data processing.
|
||||||
|
|
||||||
|
Also, many users point better performance of direct database queries versus API calls. This caused by several reasons,
|
||||||
|
such as additional PHP layer and additional SQL queries (user permissions checks).
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
This chart illustrates how plugin uses both Zabbix API and MySQL data source for querying different types
|
||||||
|
of data from Zabbix. MySQL data source is used only for pulling history and trend data instead of `history.get`
|
||||||
|
and `trend.get` API calls.
|
||||||
|
|
||||||
|
[](../img/reference-direct-db-connection.svg)
|
||||||
|
|
||||||
|
## Query structure
|
||||||
|
|
||||||
|
Grafana-Zabbix uses queries like this for getting history:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT itemid AS metric, clock AS time_sec, {aggFunc}(value) as value
|
||||||
|
FROM {historyTable}
|
||||||
|
WHERE itemid IN ({itemids})
|
||||||
|
AND clock > {timeFrom} AND clock < {timeTill}
|
||||||
|
GROUP BY time_sec DIV {intervalSec}, metric
|
||||||
|
```
|
||||||
|
|
||||||
|
where `{aggFunc}` is one of `[AVG, MIN, MAX, SUM, COUNT]` aggregation function, `{historyTable}` is a history table,
|
||||||
|
`{intervalSec}` - consolidation interval in seconds.
|
||||||
|
|
||||||
|
When getting trends, plugin additionally queries particular value column (`value_avg`, `value_min` or `value_max`)
|
||||||
|
depends on `consolidateBy` function value:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT itemid AS metric, clock AS time_sec, {aggFunc}({valueColumn}) as value
|
||||||
|
FROM {trendsTable}
|
||||||
|
WHERE itemid IN ({itemids})
|
||||||
|
AND clock > {timeFrom} AND clock < {timeTill}
|
||||||
|
GROUP BY time_sec DIV {intervalSec}, metric
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, plugin uses aggregation by given time interval. This interval is provided by Grafana and depends on the
|
||||||
|
panel with in pixels. Thus, Grafana always gets data in necessary resolution.
|
||||||
@@ -197,3 +197,14 @@ replaceAlias(/.*CPU (.*) time/, $1) -> system
|
|||||||
backend01: CPU system time
|
backend01: CPU system time
|
||||||
replaceAlias(/(.*): CPU (.*) time/, $1 - $2) -> backend01 - system
|
replaceAlias(/(.*): CPU (.*) time/, $1 - $2) -> backend01 - system
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Special
|
||||||
|
|
||||||
|
### consolidateBy
|
||||||
|
```
|
||||||
|
consolidateBy(consolidationFunc)
|
||||||
|
```
|
||||||
|
|
||||||
|
When a graph is drawn where width of the graph size in pixels is smaller than the number of datapoints to be graphed, plugin consolidates the values to to prevent line overlap. The consolidateBy() function changes the consolidation function from the default of average to one of `sum`, `min`, `max` or `count`.
|
||||||
|
|
||||||
|
Valid function names are `sum`, `avg`, `min`, `max` and `count`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "grafana-zabbix",
|
"name": "grafana-zabbix",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "3.2.0",
|
"version": "3.5.1",
|
||||||
"description": "Zabbix plugin for Grafana",
|
"description": "Zabbix plugin for Grafana",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
## Zabbix plugin for Grafana
|
# Zabbix plugin for Grafana
|
||||||
|
|
||||||
|
[](https://github.com/alexanderzobnin/grafana-zabbix/releases)
|
||||||
|
[](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/CHANGELOG.md)
|
||||||
[](http://docs.grafana-zabbix.org)
|
[](http://docs.grafana-zabbix.org)
|
||||||
[](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/LICENSE)
|
|
||||||
|
|
||||||
Zabbix plugin allows to show different type of data from [Zabbix](http://www.zabbix.com/)
|
Visualize your Zabbix metrics with the leading open source software for time series analytics.
|
||||||
monitoring system.
|
|
||||||
|
|
||||||
### Live Demo
|
### Live Demo
|
||||||
|
|
||||||
Check out the [live demo](http://play.grafana-zabbix.org/) with dashboard examples.
|
See all features overview and dashboards examples at Grafana-Zabbix [Live demo](http://play.grafana-zabbix.org) site.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
#### Flexible metric editor
|
- Select multiple metrics [by using Regex](http://docs.grafana-zabbix.org/guides/gettingstarted/#multiple-items-on-one-graph)
|
||||||
* Regex-based metric filtering
|
- Create interactive and reusable dashboards with [template variables](http://docs.grafana-zabbix.org/guides/templating/)
|
||||||
* Client-side data processing functions
|
- Show events on graphs with [Annotations](http://docs.grafana.org/reference/annotations/)
|
||||||
* Template variables support
|
- Display active problems with Triggers panel
|
||||||
|
- Transform and shape your data with [metric processing functions](http://docs.grafana-zabbix.org/reference/functions/) (Avg, Median, Min, Max, Multiply, Summarize, Time shift, Alias)
|
||||||
#### Templated dashboards support
|
- Find problems faster with [Alerting](http://docs.grafana-zabbix.org/reference/alerting/) feature
|
||||||
Group, host, application or item names can be replaced with a template variable. This allows you to create generic dashboards that can quickly be changed to show stats for a specific cluster, server or application.
|
- Mix metrics from multiple data sources in the same dashboard or even graph
|
||||||
|
- Discover and share [dashboards](https://grafana.com/dashboards) in the official library
|
||||||
#### Annotations support
|
|
||||||
* Display zabbix events on graphs
|
|
||||||
* Show acknowledges for problems
|
|
||||||
|
|
||||||
#### Triggers panel
|
|
||||||
Panel for showing Zabbix triggers (like Last 20 issues) with some customizable features.
|
|
||||||
|
|||||||
28
src/datasource-zabbix/config.controller.js
Normal file
28
src/datasource-zabbix/config.controller.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const SUPPORTED_SQL_DS = ['mysql'];
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
dbConnection: {
|
||||||
|
enable: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ZabbixDSConfigController {
|
||||||
|
/** @ngInject */
|
||||||
|
constructor($scope, $injector, datasourceSrv) {
|
||||||
|
this.datasourceSrv = datasourceSrv;
|
||||||
|
|
||||||
|
_.defaults(this.current.jsonData, defaultConfig);
|
||||||
|
this.sqlDataSources = this.getSupportedSQLDataSources();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupportedSQLDataSources() {
|
||||||
|
let datasources = this.datasourceSrv.getAll();
|
||||||
|
return _.filter(datasources, ds => {
|
||||||
|
return _.includes(SUPPORTED_SQL_DS, ds.type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ZabbixDSConfigController.templateUrl = 'datasource-zabbix/partials/config.html';
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
// Editor modes
|
// Editor modes
|
||||||
export const MODE_METRICS = 0;
|
export const MODE_METRICS = 0;
|
||||||
export const MODE_TEXT = 2;
|
|
||||||
export const MODE_ITSERVICE = 1;
|
export const MODE_ITSERVICE = 1;
|
||||||
|
export const MODE_TEXT = 2;
|
||||||
|
export const MODE_ITEMID = 3;
|
||||||
|
|
||||||
// Triggers severity
|
// Triggers severity
|
||||||
export const SEV_NOT_CLASSIFIED = 0;
|
export const SEV_NOT_CLASSIFIED = 0;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class ZabbixAPIDatasource {
|
|||||||
this.dashboardSrv = dashboardSrv;
|
this.dashboardSrv = dashboardSrv;
|
||||||
this.zabbixAlertingSrv = zabbixAlertingSrv;
|
this.zabbixAlertingSrv = zabbixAlertingSrv;
|
||||||
|
|
||||||
|
// Use custom format for template variables
|
||||||
|
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
||||||
|
|
||||||
// General data source settings
|
// General data source settings
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
this.url = instanceSettings.url;
|
this.url = instanceSettings.url;
|
||||||
@@ -43,10 +46,21 @@ class ZabbixAPIDatasource {
|
|||||||
this.addThresholds = instanceSettings.jsonData.addThresholds;
|
this.addThresholds = instanceSettings.jsonData.addThresholds;
|
||||||
this.alertingMinSeverity = instanceSettings.jsonData.alertingMinSeverity || c.SEV_WARNING;
|
this.alertingMinSeverity = instanceSettings.jsonData.alertingMinSeverity || c.SEV_WARNING;
|
||||||
|
|
||||||
this.zabbix = new Zabbix(this.url, this.username, this.password, this.basicAuth, this.withCredentials, this.cacheTTL);
|
// Direct DB Connection options
|
||||||
|
this.enableDirectDBConnection = instanceSettings.jsonData.dbConnection.enable;
|
||||||
|
this.sqlDatasourceId = instanceSettings.jsonData.dbConnection.datasourceId;
|
||||||
|
|
||||||
// Use custom format for template variables
|
let zabbixOptions = {
|
||||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
basicAuth: this.basicAuth,
|
||||||
|
withCredentials: this.withCredentials,
|
||||||
|
cacheTTL: this.cacheTTL,
|
||||||
|
enableDirectDBConnection: this.enableDirectDBConnection,
|
||||||
|
sqlDatasourceId: this.sqlDatasourceId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.zabbix = new Zabbix(this.url, zabbixOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
@@ -75,6 +89,11 @@ class ZabbixAPIDatasource {
|
|||||||
|
|
||||||
// Create request for each target
|
// Create request for each target
|
||||||
let promises = _.map(options.targets, t => {
|
let promises = _.map(options.targets, t => {
|
||||||
|
// Don't request undefined and hidden targets
|
||||||
|
if (t.hide) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
let timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
|
let timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
|
||||||
let timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);
|
let timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);
|
||||||
|
|
||||||
@@ -94,7 +113,7 @@ class ZabbixAPIDatasource {
|
|||||||
let useTrends = this.isUseTrends(timeRange);
|
let useTrends = this.isUseTrends(timeRange);
|
||||||
|
|
||||||
// Metrics or Text query mode
|
// Metrics or Text query mode
|
||||||
if (target.mode !== c.MODE_ITSERVICE) {
|
if (target.mode === c.MODE_METRICS || target.mode === c.MODE_TEXT || target.mode === c.MODE_ITEMID) {
|
||||||
// Migrate old targets
|
// Migrate old targets
|
||||||
target = migrations.migrate(target);
|
target = migrations.migrate(target);
|
||||||
|
|
||||||
@@ -107,20 +126,12 @@ class ZabbixAPIDatasource {
|
|||||||
return this.queryNumericData(target, timeRange, useTrends, options);
|
return this.queryNumericData(target, timeRange, useTrends, options);
|
||||||
} else if (target.mode === c.MODE_TEXT) {
|
} else if (target.mode === c.MODE_TEXT) {
|
||||||
return this.queryTextData(target, timeRange);
|
return this.queryTextData(target, timeRange);
|
||||||
|
} else if (target.mode === c.MODE_ITEMID) {
|
||||||
|
return this.queryItemIdData(target, timeRange, useTrends, options);
|
||||||
}
|
}
|
||||||
}
|
} else if (target.mode === c.MODE_ITSERVICE) {
|
||||||
|
|
||||||
// IT services mode
|
// IT services mode
|
||||||
else if (target.mode === c.MODE_ITSERVICE) {
|
return this.queryITServiceData(target, timeRange, options);
|
||||||
// Don't show undefined and hidden targets
|
|
||||||
if (target.hide || !target.itservice || !target.slaProperty) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.zabbix.getSLA(target.itservice.serviceid, timeRange)
|
|
||||||
.then(slaObject => {
|
|
||||||
return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,39 +143,55 @@ class ZabbixAPIDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for Metrics mode
|
||||||
|
*/
|
||||||
queryNumericData(target, timeRange, useTrends, options) {
|
queryNumericData(target, timeRange, useTrends, options) {
|
||||||
let [timeFrom, timeTo] = timeRange;
|
|
||||||
let getItemOptions = {
|
let getItemOptions = {
|
||||||
itemtype: 'num'
|
itemtype: 'num'
|
||||||
};
|
};
|
||||||
return this.zabbix.getItemsFromTarget(target, getItemOptions)
|
return this.zabbix.getItemsFromTarget(target, getItemOptions)
|
||||||
.then(items => {
|
.then(items => {
|
||||||
|
return this.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query history for numeric items
|
||||||
|
*/
|
||||||
|
queryNumericDataForItems(items, target, timeRange, useTrends, options) {
|
||||||
|
let [timeFrom, timeTo] = timeRange;
|
||||||
let getHistoryPromise;
|
let getHistoryPromise;
|
||||||
|
options.consolidateBy = getConsolidateBy(target);
|
||||||
|
|
||||||
if (useTrends) {
|
if (useTrends) {
|
||||||
|
if (this.enableDirectDBConnection) {
|
||||||
|
getHistoryPromise = this.zabbix.getTrendsDB(items, timeFrom, timeTo, options)
|
||||||
|
.then(history => this.zabbix.dbConnector.handleGrafanaTSResponse(history, items));
|
||||||
|
} else {
|
||||||
let valueType = this.getTrendValueType(target);
|
let valueType = this.getTrendValueType(target);
|
||||||
getHistoryPromise = this.zabbix.getTrend(items, timeFrom, timeTo)
|
getHistoryPromise = this.zabbix.getTrend(items, timeFrom, timeTo)
|
||||||
.then(history => {
|
.then(history => responseHandler.handleTrends(history, items, valueType))
|
||||||
return responseHandler.handleTrends(history, items, valueType);
|
|
||||||
})
|
|
||||||
.then(timeseries => {
|
.then(timeseries => {
|
||||||
// Sort trend data, issue #202
|
// Sort trend data, issue #202
|
||||||
_.forEach(timeseries, series => {
|
_.forEach(timeseries, series => {
|
||||||
series.datapoints = _.sortBy(series.datapoints, point => point[c.DATAPOINT_TS]);
|
series.datapoints = _.sortBy(series.datapoints, point => point[c.DATAPOINT_TS]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return timeseries;
|
return timeseries;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use history
|
// Use history
|
||||||
|
if (this.enableDirectDBConnection) {
|
||||||
|
getHistoryPromise = this.zabbix.getHistoryDB(items, timeFrom, timeTo, options)
|
||||||
|
.then(history => this.zabbix.dbConnector.handleGrafanaTSResponse(history, items));
|
||||||
|
} else {
|
||||||
getHistoryPromise = this.zabbix.getHistory(items, timeFrom, timeTo)
|
getHistoryPromise = this.zabbix.getHistory(items, timeFrom, timeTo)
|
||||||
.then(history => {
|
.then(history => responseHandler.handleHistory(history, items));
|
||||||
return responseHandler.handleHistory(history, items);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return getHistoryPromise;
|
return getHistoryPromise
|
||||||
})
|
|
||||||
.then(timeseries => this.applyDataProcessingFunctions(timeseries, target))
|
.then(timeseries => this.applyDataProcessingFunctions(timeseries, target))
|
||||||
.then(timeseries => downsampleSeries(timeseries, options))
|
.then(timeseries => downsampleSeries(timeseries, options))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -238,6 +265,9 @@ class ZabbixAPIDatasource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for Text mode
|
||||||
|
*/
|
||||||
queryTextData(target, timeRange) {
|
queryTextData(target, timeRange) {
|
||||||
let [timeFrom, timeTo] = timeRange;
|
let [timeFrom, timeTo] = timeRange;
|
||||||
let options = {
|
let options = {
|
||||||
@@ -256,6 +286,66 @@ class ZabbixAPIDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for Item ID mode
|
||||||
|
*/
|
||||||
|
queryItemIdData(target, timeRange, useTrends, options) {
|
||||||
|
let itemids = target.itemids;
|
||||||
|
itemids = this.templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||||
|
itemids = _.map(itemids.split(','), itemid => itemid.trim());
|
||||||
|
|
||||||
|
if (!itemids) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.zabbix.getItemsByIDs(itemids)
|
||||||
|
.then(items => {
|
||||||
|
return this.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query target data for IT Services mode
|
||||||
|
*/
|
||||||
|
queryITServiceData(target, timeRange, options) {
|
||||||
|
// Don't show undefined and hidden targets
|
||||||
|
if (target.hide || (!target.itservice && !target.itServiceFilter) || !target.slaProperty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let itServiceIds = [];
|
||||||
|
let itServices = [];
|
||||||
|
let itServiceFilter;
|
||||||
|
let isOldVersion = target.itservice && !target.itServiceFilter;
|
||||||
|
|
||||||
|
if (isOldVersion) {
|
||||||
|
// Backward compatibility
|
||||||
|
itServiceFilter = '/.*/';
|
||||||
|
} else {
|
||||||
|
itServiceFilter = this.replaceTemplateVars(target.itServiceFilter, options.scopedVars);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.zabbix.getITServices(itServiceFilter)
|
||||||
|
.then(itservices => {
|
||||||
|
itServices = itservices;
|
||||||
|
if (isOldVersion) {
|
||||||
|
itServices = _.filter(itServices, {'serviceid': target.itservice.serviceid});
|
||||||
|
}
|
||||||
|
|
||||||
|
itServiceIds = _.map(itServices, 'serviceid');
|
||||||
|
return itServiceIds;
|
||||||
|
})
|
||||||
|
.then(serviceids => {
|
||||||
|
return this.zabbix.getSLA(serviceids, timeRange);
|
||||||
|
})
|
||||||
|
.then(slaResponse => {
|
||||||
|
return _.map(itServiceIds, serviceid => {
|
||||||
|
let itservice = _.find(itServices, {'serviceid': serviceid});
|
||||||
|
return responseHandler.handleSLAResponse(itservice, target.slaProperty, slaResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test connection to Zabbix API
|
* Test connection to Zabbix API
|
||||||
* @return {object} Connection status and Zabbix API version
|
* @return {object} Connection status and Zabbix API version
|
||||||
@@ -267,6 +357,13 @@ class ZabbixAPIDatasource {
|
|||||||
zabbixVersion = version;
|
zabbixVersion = version;
|
||||||
return this.zabbix.login();
|
return this.zabbix.login();
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (this.enableDirectDBConnection) {
|
||||||
|
return this.zabbix.dbConnector.testSQLDataSource();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
@@ -281,6 +378,12 @@ class ZabbixAPIDatasource {
|
|||||||
title: error.message,
|
title: error.message,
|
||||||
message: error.data
|
message: error.data
|
||||||
};
|
};
|
||||||
|
} else if (error.data && error.data.message) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
title: "Connection failed",
|
||||||
|
message: error.data.message
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
@@ -367,13 +470,14 @@ class ZabbixAPIDatasource {
|
|||||||
return getTriggers.then(triggers => {
|
return getTriggers.then(triggers => {
|
||||||
|
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
if (utils.isRegex(annotation.trigger)) {
|
let triggerName = this.replaceTemplateVars(annotation.trigger, {});
|
||||||
|
if (utils.isRegex(triggerName)) {
|
||||||
triggers = _.filter(triggers, trigger => {
|
triggers = _.filter(triggers, trigger => {
|
||||||
return utils.buildRegex(annotation.trigger).test(trigger.description);
|
return utils.buildRegex(triggerName).test(trigger.description);
|
||||||
});
|
});
|
||||||
} else if (annotation.trigger) {
|
} else if (triggerName) {
|
||||||
triggers = _.filter(triggers, trigger => {
|
triggers = _.filter(triggers, trigger => {
|
||||||
return trigger.description === annotation.trigger;
|
return trigger.description === triggerName;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +528,9 @@ class ZabbixAPIDatasource {
|
|||||||
*/
|
*/
|
||||||
alertQuery(options) {
|
alertQuery(options) {
|
||||||
let enabled_targets = filterEnabledTargets(options.targets);
|
let enabled_targets = filterEnabledTargets(options.targets);
|
||||||
let getPanelItems = _.map(enabled_targets, target => {
|
let getPanelItems = _.map(enabled_targets, t => {
|
||||||
|
let target = _.cloneDeep(t);
|
||||||
|
this.replaceTargetVariables(target, options);
|
||||||
return this.zabbix.getItemsFromTarget(target, {itemtype: 'num'});
|
return this.zabbix.getItemsFromTarget(target, {itemtype: 'num'});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -508,11 +614,24 @@ function bindFunctionDefs(functionDefs, category) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConsolidateBy(target) {
|
||||||
|
let consolidateBy = 'avg';
|
||||||
|
let funcDef = _.find(target.functions, func => {
|
||||||
|
return func.def.name === 'consolidateBy';
|
||||||
|
});
|
||||||
|
if (funcDef && funcDef.params && funcDef.params.length) {
|
||||||
|
consolidateBy = funcDef.params[0];
|
||||||
|
}
|
||||||
|
return consolidateBy;
|
||||||
|
}
|
||||||
|
|
||||||
function downsampleSeries(timeseries_data, options) {
|
function downsampleSeries(timeseries_data, options) {
|
||||||
|
let defaultAgg = dataProcessor.aggregationFunctions['avg'];
|
||||||
|
let consolidateByFunc = dataProcessor.aggregationFunctions[options.consolidateBy] || defaultAgg;
|
||||||
return _.map(timeseries_data, timeseries => {
|
return _.map(timeseries_data, timeseries => {
|
||||||
if (timeseries.datapoints.length > options.maxDataPoints) {
|
if (timeseries.datapoints.length > options.maxDataPoints) {
|
||||||
timeseries.datapoints = dataProcessor
|
timeseries.datapoints = dataProcessor
|
||||||
.groupBy(options.interval, dataProcessor.AVERAGE, timeseries.datapoints);
|
.groupBy(options.interval, consolidateByFunc, timeseries.datapoints);
|
||||||
}
|
}
|
||||||
return timeseries;
|
return timeseries;
|
||||||
});
|
});
|
||||||
@@ -544,6 +663,13 @@ function zabbixTemplateFormat(value) {
|
|||||||
return '(' + escapedValues.join('|') + ')';
|
return '(' + escapedValues.join('|') + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zabbixItemIdsTemplateFormat(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If template variables are used in request, replace it using regex format
|
* If template variables are used in request, replace it using regex format
|
||||||
* and wrap with '/' for proper multi-value work. Example:
|
* and wrap with '/' for proper multi-value work. Example:
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ var categories = {
|
|||||||
Filter: [],
|
Filter: [],
|
||||||
Trends: [],
|
Trends: [],
|
||||||
Time: [],
|
Time: [],
|
||||||
Alias: []
|
Alias: [],
|
||||||
|
Special: []
|
||||||
};
|
};
|
||||||
|
|
||||||
function addFuncDef(funcDef) {
|
function addFuncDef(funcDef) {
|
||||||
@@ -222,6 +223,16 @@ addFuncDef({
|
|||||||
defaultParams: ['/(.*)/', '$1']
|
defaultParams: ['/(.*)/', '$1']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Special
|
||||||
|
addFuncDef({
|
||||||
|
name: 'consolidateBy',
|
||||||
|
category: 'Special',
|
||||||
|
params: [
|
||||||
|
{ name: 'type', type: 'string', options: ['avg', 'min', 'max', 'sum', 'count'] }
|
||||||
|
],
|
||||||
|
defaultParams: ['avg'],
|
||||||
|
});
|
||||||
|
|
||||||
_.each(categories, function(funcList, catName) {
|
_.each(categories, function(funcList, catName) {
|
||||||
categories[catName] = _.sortBy(funcList, 'name');
|
categories[catName] = _.sortBy(funcList, 'name');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import {ZabbixAPIDatasource} from './datasource';
|
import {ZabbixAPIDatasource} from './datasource';
|
||||||
import {ZabbixQueryController} from './query.controller';
|
import {ZabbixQueryController} from './query.controller';
|
||||||
|
import {ZabbixDSConfigController} from './config.controller';
|
||||||
class ZabbixConfigController {}
|
|
||||||
ZabbixConfigController.templateUrl = 'datasource-zabbix/partials/config.html';
|
|
||||||
|
|
||||||
class ZabbixQueryOptionsController {}
|
class ZabbixQueryOptionsController {}
|
||||||
ZabbixQueryOptionsController.templateUrl = 'datasource-zabbix/partials/query.options.html';
|
ZabbixQueryOptionsController.templateUrl = 'datasource-zabbix/partials/query.options.html';
|
||||||
@@ -12,7 +10,7 @@ ZabbixAnnotationsQueryController.templateUrl = 'datasource-zabbix/partials/annot
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
ZabbixAPIDatasource as Datasource,
|
ZabbixAPIDatasource as Datasource,
|
||||||
ZabbixConfigController as ConfigCtrl,
|
ZabbixDSConfigController as ConfigCtrl,
|
||||||
ZabbixQueryController as QueryCtrl,
|
ZabbixQueryController as QueryCtrl,
|
||||||
ZabbixQueryOptionsController as QueryOptionsCtrl,
|
ZabbixQueryOptionsController as QueryOptionsCtrl,
|
||||||
ZabbixAnnotationsQueryController as AnnotationsQueryCtrl
|
ZabbixAnnotationsQueryController as AnnotationsQueryCtrl
|
||||||
|
|||||||
@@ -75,18 +75,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="gf-form-group">
|
||||||
|
<h3 class="page-heading">Direct DB Connection</h3>
|
||||||
|
<gf-form-switch class="gf-form" label-class="width-12"
|
||||||
|
label="Enable"
|
||||||
|
checked="ctrl.current.jsonData.dbConnection.enable">
|
||||||
|
</gf-form-switch>
|
||||||
|
<div ng-if="ctrl.current.jsonData.dbConnection.enable">
|
||||||
|
<div class="gf-form max-width-20">
|
||||||
|
<span class="gf-form-label width-12">
|
||||||
|
SQL Data Source
|
||||||
|
<info-popover mode="right-normal">
|
||||||
|
Select SQL Data Source for Zabbix database.
|
||||||
|
In order to use this feature you should <a href="/datasources/new" target="_blank">create</a> and
|
||||||
|
configure it first. Zabbix plugin uses this data source for querying history data directly from database.
|
||||||
|
This way usually faster than pulling data from Zabbix API, especially on the wide time ranges, and reduces
|
||||||
|
amount of data transfered.
|
||||||
|
</info-popover>
|
||||||
|
</span>
|
||||||
|
<div class="gf-form-select-wrapper max-width-16">
|
||||||
|
<select class="gf-form-input" ng-model="ctrl.current.jsonData.dbConnection.datasourceId"
|
||||||
|
ng-options="ds.id as ds.name for ds in ctrl.sqlDataSources">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<h3 class="page-heading">Alerting</h3>
|
<h3 class="page-heading">Alerting</h3>
|
||||||
<gf-form-switch class="gf-form" label-class="width-9"
|
<gf-form-switch class="gf-form" label-class="width-12"
|
||||||
label="Enable alerting"
|
label="Enable alerting"
|
||||||
checked="ctrl.current.jsonData.alerting">
|
checked="ctrl.current.jsonData.alerting">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
<gf-form-switch class="gf-form" label-class="width-9"
|
<div ng-if="ctrl.current.jsonData.alerting">
|
||||||
|
<gf-form-switch class="gf-form" label-class="width-12"
|
||||||
label="Add thresholds"
|
label="Add thresholds"
|
||||||
checked="ctrl.current.jsonData.addThresholds">
|
checked="ctrl.current.jsonData.addThresholds">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<span class="gf-form-label width-9">Min severity</span>
|
<span class="gf-form-label width-12">Min severity</span>
|
||||||
<div class="gf-form-select-wrapper max-width-16">
|
<div class="gf-form-select-wrapper max-width-16">
|
||||||
<select class="gf-form-input" ng-model="ctrl.current.jsonData.alertingMinSeverity"
|
<select class="gf-form-input" ng-model="ctrl.current.jsonData.alertingMinSeverity"
|
||||||
ng-options="s.val as s.text for s in [
|
ng-options="s.val as s.text for s in [
|
||||||
@@ -97,3 +125,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<select class="gf-form-input"
|
<select class="gf-form-input"
|
||||||
ng-change="ctrl.switchEditorMode(ctrl.target.mode)"
|
ng-change="ctrl.switchEditorMode(ctrl.target.mode)"
|
||||||
ng-model="ctrl.target.mode"
|
ng-model="ctrl.target.mode"
|
||||||
ng-options="v.mode as v.text for (k, v) in ctrl.editorModes">
|
ng-options="m.mode as m.text for m in ctrl.editorModes">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -17,27 +17,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- IT Service editor -->
|
<!-- IT Service editor -->
|
||||||
<div class="gf-form-inline" ng-show="ctrl.target.mode == 1">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.ITSERVICE">
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">IT Service</label>
|
<label class="gf-form-label query-keyword width-7">IT Service</label>
|
||||||
<div class="gf-form-select-wrapper max-width-20">
|
<input type="text"
|
||||||
<select class="gf-form-input"
|
ng-model="ctrl.target.itServiceFilter"
|
||||||
ng-change="ctrl.selectITService()"
|
bs-typeahead="ctrl.getITServices"
|
||||||
ng-model="ctrl.target.itservice"
|
ng-blur="ctrl.onTargetBlur()"
|
||||||
bs-tooltip="ctrl.target.itservice.name.length > 25 ? ctrl.target.itservice.name : ''"
|
data-min-length=0
|
||||||
ng-options="itservice.name for itservice in ctrl.itserviceList track by itservice.name">
|
data-items=100
|
||||||
<option value="">-- Select IT service --</option>
|
class="gf-form-input"
|
||||||
</select>
|
ng-class="{
|
||||||
</div>
|
'zbx-variable': ctrl.isVariable(ctrl.target.itServiceFilter),
|
||||||
|
'zbx-regex': ctrl.isRegex(ctrl.target.itServiceFilter)
|
||||||
|
}">
|
||||||
|
</input>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword">IT service property</label>
|
<label class="gf-form-label query-keyword">Property</label>
|
||||||
<div class="gf-form-select-wrapper">
|
<div class="gf-form-select-wrapper">
|
||||||
<select class="gf-form-input"
|
<select class="gf-form-input"
|
||||||
ng-change="ctrl.selectITService()"
|
ng-change="ctrl.onTargetBlur()"
|
||||||
ng-model="ctrl.target.slaProperty"
|
ng-model="ctrl.target.slaProperty"
|
||||||
ng-options="slaProperty.name for slaProperty in ctrl.slaPropertyList track by slaProperty.name">
|
ng-options="slaProperty.name for slaProperty in ctrl.slaPropertyList track by slaProperty.name">
|
||||||
<option value="">-- Property --</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-hide="ctrl.target.mode == 1">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.METRICS || ctrl.target.mode == editorMode.TEXT">
|
||||||
<!-- Select Group -->
|
<!-- Select Group -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Group</label>
|
<label class="gf-form-label query-keyword width-7">Group</label>
|
||||||
@@ -83,7 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-hide="ctrl.target.mode == 1">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.METRICS || ctrl.target.mode == editorMode.TEXT">
|
||||||
<!-- Select Application -->
|
<!-- Select Application -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Application</label>
|
<label class="gf-form-label query-keyword width-7">Application</label>
|
||||||
@@ -129,7 +131,7 @@
|
|||||||
<!-- Query options -->
|
<!-- Query options -->
|
||||||
<div class="gf-form-group" ng-if="ctrl.showQueryOptions">
|
<div class="gf-form-group" ng-if="ctrl.showQueryOptions">
|
||||||
<div class="gf-form offset-width-7">
|
<div class="gf-form offset-width-7">
|
||||||
<gf-form-switch class="gf-form" ng-hide="ctrl.target.mode == 2"
|
<gf-form-switch class="gf-form"
|
||||||
label="Show disabled items"
|
label="Show disabled items"
|
||||||
checked="ctrl.target.options.showDisabledItems"
|
checked="ctrl.target.options.showDisabledItems"
|
||||||
on-change="ctrl.onQueryOptionChange()">
|
on-change="ctrl.onQueryOptionChange()">
|
||||||
@@ -137,8 +139,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Item IDs editor mode -->
|
||||||
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.ITEMID">
|
||||||
|
<div class="gf-form max-width-20">
|
||||||
|
<label class="gf-form-label query-keyword width-7">Item IDs</label>
|
||||||
|
<input type="text"
|
||||||
|
ng-model="ctrl.target.itemids"
|
||||||
|
bs-typeahead="ctrl.getVariables"
|
||||||
|
ng-blur="ctrl.onTargetBlur()"
|
||||||
|
data-min-length=0
|
||||||
|
data-items=100
|
||||||
|
class="gf-form-input"
|
||||||
|
ng-class="{
|
||||||
|
'zbx-variable': ctrl.isVariable(ctrl.target.itServiceFilter),
|
||||||
|
'zbx-regex': ctrl.isRegex(ctrl.target.itServiceFilter)
|
||||||
|
}">
|
||||||
|
</input>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Metric processing functions -->
|
<!-- Metric processing functions -->
|
||||||
<div class="gf-form-inline" ng-hide="ctrl.target.mode">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.METRICS || ctrl.target.mode == editorMode.ITEMID">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword width-7">Functions</label>
|
<label class="gf-form-label query-keyword width-7">Functions</label>
|
||||||
<div ng-repeat="func in ctrl.target.functions" class="gf-form-label query-part" metric-function-editor></div>
|
<div ng-repeat="func in ctrl.target.functions" class="gf-form-label query-part" metric-function-editor></div>
|
||||||
@@ -151,7 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Text mode options -->
|
<!-- Text mode options -->
|
||||||
<div class="gf-form-inline" ng-show="ctrl.target.mode == 2">
|
<div class="gf-form-inline" ng-show="ctrl.target.mode == editorMode.TEXT">
|
||||||
<!-- Text metric regex -->
|
<!-- Text metric regex -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Text filter</label>
|
<label class="gf-form-label query-keyword width-7">Text filter</label>
|
||||||
@@ -165,6 +189,8 @@
|
|||||||
|
|
||||||
<gf-form-switch class="gf-form" label="Use capture groups" checked="ctrl.target.useCaptureGroups" on-change="ctrl.onTargetBlur()">
|
<gf-form-switch class="gf-form" label="Use capture groups" checked="ctrl.target.useCaptureGroups" on-change="ctrl.onTargetBlur()">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
|
<div class="gf-form gf-form--grow">
|
||||||
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</query-editor-row>
|
</query-editor-row>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {QueryCtrl} from 'app/plugins/sdk';
|
import {QueryCtrl} from 'app/plugins/sdk';
|
||||||
import angular from 'angular';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as c from './constants';
|
import * as c from './constants';
|
||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
@@ -22,17 +21,35 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
this.replaceTemplateVars = this.datasource.replaceTemplateVars;
|
this.replaceTemplateVars = this.datasource.replaceTemplateVars;
|
||||||
this.templateSrv = templateSrv;
|
this.templateSrv = templateSrv;
|
||||||
|
|
||||||
this.editorModes = {
|
this.editorModes = [
|
||||||
0: {value: 'num', text: 'Metrics', mode: c.MODE_METRICS},
|
{value: 'num', text: 'Metrics', mode: c.MODE_METRICS},
|
||||||
1: {value: 'itservice', text: 'IT Services', mode: c.MODE_ITSERVICE},
|
{value: 'text', text: 'Text', mode: c.MODE_TEXT},
|
||||||
2: {value: 'text', text: 'Text', mode: c.MODE_TEXT}
|
{value: 'itservice', text: 'IT Services', mode: c.MODE_ITSERVICE},
|
||||||
|
{value: 'itemid', text: 'Item ID', mode: c.MODE_ITEMID}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.$scope.editorMode = {
|
||||||
|
METRICS: c.MODE_METRICS,
|
||||||
|
TEXT: c.MODE_TEXT,
|
||||||
|
ITSERVICE: c.MODE_ITSERVICE,
|
||||||
|
ITEMID: c.MODE_ITEMID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.slaPropertyList = [
|
||||||
|
{name: "Status", property: "status"},
|
||||||
|
{name: "SLA", property: "sla"},
|
||||||
|
{name: "OK time", property: "okTime"},
|
||||||
|
{name: "Problem time", property: "problemTime"},
|
||||||
|
{name: "Down time", property: "downtimeTime"}
|
||||||
|
];
|
||||||
|
|
||||||
// Map functions for bs-typeahead
|
// Map functions for bs-typeahead
|
||||||
this.getGroupNames = _.bind(this.getMetricNames, this, 'groupList');
|
this.getGroupNames = _.bind(this.getMetricNames, this, 'groupList');
|
||||||
this.getHostNames = _.bind(this.getMetricNames, this, 'hostList', true);
|
this.getHostNames = _.bind(this.getMetricNames, this, 'hostList', true);
|
||||||
this.getApplicationNames = _.bind(this.getMetricNames, this, 'appList');
|
this.getApplicationNames = _.bind(this.getMetricNames, this, 'appList');
|
||||||
this.getItemNames = _.bind(this.getMetricNames, this, 'itemList');
|
this.getItemNames = _.bind(this.getMetricNames, this, 'itemList');
|
||||||
|
this.getITServices = _.bind(this.getMetricNames, this, 'itServiceList');
|
||||||
|
this.getVariables = _.bind(this.getTemplateVariables, this);
|
||||||
|
|
||||||
// Update metric suggestion when template variable was changed
|
// Update metric suggestion when template variable was changed
|
||||||
$rootScope.$on('template-variable-value-updated', () => this.onVariableChange());
|
$rootScope.$on('template-variable-value-updated', () => this.onVariableChange());
|
||||||
@@ -57,14 +74,14 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
|
|
||||||
// Load default values
|
// Load default values
|
||||||
var targetDefaults = {
|
var targetDefaults = {
|
||||||
mode: c.MODE_METRICS,
|
'mode': c.MODE_METRICS,
|
||||||
group: { filter: "" },
|
'group': { 'filter': "" },
|
||||||
host: { filter: "" },
|
'host': { 'filter': "" },
|
||||||
application: { filter: "" },
|
'application': { 'filter': "" },
|
||||||
item: { filter: "" },
|
'item': { 'filter': "" },
|
||||||
functions: [],
|
'functions': [],
|
||||||
options: {
|
'options': {
|
||||||
showDisabledItems: false
|
'showDisabledItems': false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_.defaults(target, targetDefaults);
|
_.defaults(target, targetDefaults);
|
||||||
@@ -77,26 +94,11 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
if (target.mode === c.MODE_METRICS ||
|
if (target.mode === c.MODE_METRICS ||
|
||||||
target.mode === c.MODE_TEXT) {
|
target.mode === c.MODE_TEXT) {
|
||||||
|
|
||||||
this.downsampleFunctionList = [
|
|
||||||
{name: "avg", value: "avg"},
|
|
||||||
{name: "min", value: "min"},
|
|
||||||
{name: "max", value: "max"},
|
|
||||||
{name: "sum", value: "sum"},
|
|
||||||
{name: "count", value: "count"}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.initFilters();
|
this.initFilters();
|
||||||
}
|
}
|
||||||
else if (target.mode === c.MODE_ITSERVICE) {
|
else if (target.mode === c.MODE_ITSERVICE) {
|
||||||
this.slaPropertyList = [
|
_.defaults(target, {slaProperty: {name: "SLA", property: "sla"}});
|
||||||
{name: "Status", property: "status"},
|
this.suggestITServices();
|
||||||
{name: "SLA", property: "sla"},
|
|
||||||
{name: "OK time", property: "okTime"},
|
|
||||||
{name: "Problem time", property: "problemTime"},
|
|
||||||
{name: "Down time", property: "downtimeTime"}
|
|
||||||
];
|
|
||||||
this.itserviceList = [{name: "test"}];
|
|
||||||
this.updateITServiceList();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +106,8 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initFilters() {
|
initFilters() {
|
||||||
let itemtype = this.editorModes[this.target.mode].value;
|
let itemtype = _.find(this.editorModes, {'mode': this.target.mode});
|
||||||
|
itemtype = itemtype ? itemtype.value : null;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.suggestGroups(),
|
this.suggestGroups(),
|
||||||
this.suggestHosts(),
|
this.suggestHosts(),
|
||||||
@@ -129,6 +132,12 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTemplateVariables() {
|
||||||
|
return _.map(this.templateSrv.variables, variable => {
|
||||||
|
return '$' + variable.name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
suggestGroups() {
|
suggestGroups() {
|
||||||
return this.zabbix.getAllGroups()
|
return this.zabbix.getAllGroups()
|
||||||
.then(groups => {
|
.then(groups => {
|
||||||
@@ -173,6 +182,14 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suggestITServices() {
|
||||||
|
return this.zabbix.getITService()
|
||||||
|
.then(itservices => {
|
||||||
|
this.metric.itServiceList = itservices;
|
||||||
|
return itservices;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
isRegex(str) {
|
isRegex(str) {
|
||||||
return utils.isRegex(str);
|
return utils.isRegex(str);
|
||||||
}
|
}
|
||||||
@@ -292,30 +309,7 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
switchEditorMode(mode) {
|
switchEditorMode(mode) {
|
||||||
this.target.mode = mode;
|
this.target.mode = mode;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
this.targetChanged();
|
||||||
|
|
||||||
/////////////////
|
|
||||||
// IT Services //
|
|
||||||
/////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update list of IT services
|
|
||||||
*/
|
|
||||||
updateITServiceList() {
|
|
||||||
this.zabbix.getITService().then((iteservices) => {
|
|
||||||
this.itserviceList = [];
|
|
||||||
this.itserviceList = this.itserviceList.concat(iteservices);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call when IT service is selected.
|
|
||||||
*/
|
|
||||||
selectITService() {
|
|
||||||
if (!_.isEqual(this.oldTarget, this.target) && _.isEmpty(this.target.errors)) {
|
|
||||||
this.oldTarget = angular.copy(this.target);
|
|
||||||
this.panelCtrl.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ describe('ZabbixDatasource', () => {
|
|||||||
password: 'zabbix',
|
password: 'zabbix',
|
||||||
trends: true,
|
trends: true,
|
||||||
trendsFrom: '14d',
|
trendsFrom: '14d',
|
||||||
trendsRange: '7d'
|
trendsRange: '7d',
|
||||||
|
dbConnection: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ctx.templateSrv = {};
|
ctx.templateSrv = {};
|
||||||
|
|||||||
@@ -88,4 +88,54 @@ describe('Utils', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('splitTemplateQuery()', () => {
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
it('should properly split query in old format', (done) => {
|
||||||
|
let test_cases = [
|
||||||
|
{
|
||||||
|
query: `/alu/./tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9{2}/`,
|
||||||
|
expected: ['/alu/', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9{2}/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `a.b.c.d`,
|
||||||
|
expected: ['a', 'b', 'c', 'd']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
_.each(test_cases, test_case => {
|
||||||
|
let splitQuery = utils.splitTemplateQuery(test_case.query);
|
||||||
|
expect(splitQuery).to.eql(test_case.expected);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly split query', (done) => {
|
||||||
|
let test_cases = [
|
||||||
|
{
|
||||||
|
query: `{alu}{/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]*/}`,
|
||||||
|
expected: ['alu', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]*/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `{alu}{/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]{2}/}`,
|
||||||
|
expected: ['alu', '/tw-(nyc|que|brx|dwt|brk)-sta_(\w|\d)*-alu-[0-9]{2}/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `{a}{b}{c}{d}`,
|
||||||
|
expected: ['a', 'b', 'c', 'd']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: `{a}{b.c.d}`,
|
||||||
|
expected: ['a', 'b.c.d']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
_.each(test_cases, test_case => {
|
||||||
|
let splitQuery = utils.splitTemplateQuery(test_case.query);
|
||||||
|
expect(splitQuery).to.eql(test_case.expected);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ export function expandItemName(name, key) {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function expandItems(items) {
|
||||||
|
_.forEach(items, item => {
|
||||||
|
item.item = item.name;
|
||||||
|
item.name = expandItemName(item.item, item.key_);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
function splitKeyParams(paramStr) {
|
function splitKeyParams(paramStr) {
|
||||||
let params = [];
|
let params = [];
|
||||||
let quoted = false;
|
let quoted = false;
|
||||||
@@ -93,7 +102,7 @@ function escapeMacro(macro) {
|
|||||||
* {group}{host.com} -> [group, host.com]
|
* {group}{host.com} -> [group, host.com]
|
||||||
*/
|
*/
|
||||||
export function splitTemplateQuery(query) {
|
export function splitTemplateQuery(query) {
|
||||||
let splitPattern = /{[^{}]*}/g;
|
let splitPattern = /\{[^\{\}]*\}|\{\/.*\/\}/g;
|
||||||
let split;
|
let split;
|
||||||
|
|
||||||
if (isContainsBraces(query)) {
|
if (isContainsBraces(query)) {
|
||||||
@@ -109,7 +118,8 @@ export function splitTemplateQuery(query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isContainsBraces(query) {
|
function isContainsBraces(query) {
|
||||||
return query.includes('{') && query.includes('}');
|
let bracesPattern = /^\{.+\}$/;
|
||||||
|
return bracesPattern.test(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern for testing regex
|
// Pattern for testing regex
|
||||||
|
|||||||
@@ -3,30 +3,45 @@ import _ from 'lodash';
|
|||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
import './zabbixAPI.service.js';
|
import './zabbixAPI.service.js';
|
||||||
import './zabbixCachingProxy.service.js';
|
import './zabbixCachingProxy.service.js';
|
||||||
|
import './zabbixDBConnector';
|
||||||
|
|
||||||
// Use factory() instead service() for multiple data sources support.
|
// Use factory() instead service() for multiple data sources support.
|
||||||
// Each Zabbix data source instance should initialize its own API instance.
|
// Each Zabbix data source instance should initialize its own API instance.
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) {
|
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy, ZabbixDBConnector) {
|
||||||
|
|
||||||
class Zabbix {
|
class Zabbix {
|
||||||
constructor(url, username, password, basicAuth, withCredentials, cacheTTL) {
|
constructor(url, options) {
|
||||||
|
let {
|
||||||
|
username, password, basicAuth, withCredentials, cacheTTL,
|
||||||
|
enableDirectDBConnection, sqlDatasourceId
|
||||||
|
} = options;
|
||||||
|
|
||||||
// Initialize Zabbix API
|
// Initialize Zabbix API
|
||||||
var ZabbixAPI = zabbixAPIService;
|
var ZabbixAPI = zabbixAPIService;
|
||||||
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
||||||
|
|
||||||
|
if (enableDirectDBConnection) {
|
||||||
|
this.dbConnector = new ZabbixDBConnector(sqlDatasourceId);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize caching proxy for requests
|
// Initialize caching proxy for requests
|
||||||
let cacheOptions = {
|
let cacheOptions = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ttl: cacheTTL
|
ttl: cacheTTL
|
||||||
};
|
};
|
||||||
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheOptions);
|
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, this.dbConnector, cacheOptions);
|
||||||
|
|
||||||
// Proxy methods
|
// Proxy methods
|
||||||
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
||||||
this.getMacros = this.cachingProxy.getMacros.bind(this.cachingProxy);
|
this.getMacros = this.cachingProxy.getMacros.bind(this.cachingProxy);
|
||||||
|
this.getItemsByIDs = this.cachingProxy.getItemsByIDs.bind(this.cachingProxy);
|
||||||
|
|
||||||
|
if (enableDirectDBConnection) {
|
||||||
|
this.getHistoryDB = this.cachingProxy.getHistoryDB.bind(this.cachingProxy);
|
||||||
|
this.getTrendsDB = this.cachingProxy.getTrendsDB.bind(this.cachingProxy);
|
||||||
|
}
|
||||||
|
|
||||||
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
||||||
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
||||||
@@ -134,6 +149,11 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) {
|
|||||||
.then(items => filterByQuery(items, itemFilter));
|
.then(items => filterByQuery(items, itemFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getITServices(itServiceFilter) {
|
||||||
|
return this.cachingProxy.getITServices()
|
||||||
|
.then(itServices => findByFilter(itServices, itServiceFilter));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build query - convert target filters to array of Zabbix items
|
* Build query - convert target filters to array of Zabbix items
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -165,10 +165,7 @@ function ZabbixAPIServiceFactory(alertSrv, zabbixAPICoreService) {
|
|||||||
sortfield: 'name',
|
sortfield: 'name',
|
||||||
webitems: true,
|
webitems: true,
|
||||||
filter: {},
|
filter: {},
|
||||||
selectHosts: [
|
selectHosts: ['hostid', 'name']
|
||||||
'hostid',
|
|
||||||
'name'
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
if (hostids) {
|
if (hostids) {
|
||||||
params.hostids = hostids;
|
params.hostids = hostids;
|
||||||
@@ -186,16 +183,25 @@ function ZabbixAPIServiceFactory(alertSrv, zabbixAPICoreService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.request('item.get', params)
|
return this.request('item.get', params)
|
||||||
.then(expandItems);
|
.then(utils.expandItems);
|
||||||
|
|
||||||
function expandItems(items) {
|
|
||||||
_.forEach(items, item => {
|
|
||||||
item.item = item.name;
|
|
||||||
item.name = utils.expandItemName(item.item, item.key_);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItemsByIDs(itemids) {
|
||||||
|
var params = {
|
||||||
|
itemids: itemids,
|
||||||
|
output: [
|
||||||
|
'name', 'key_',
|
||||||
|
'value_type',
|
||||||
|
'hostid',
|
||||||
|
'status',
|
||||||
|
'state'
|
||||||
|
],
|
||||||
|
webitems: true,
|
||||||
|
selectHosts: ['hostid', 'name']
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.request('item.get', params)
|
||||||
|
.then(utils.expandItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMacros(hostids) {
|
getMacros(hostids) {
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import _ from 'lodash';
|
|||||||
function ZabbixCachingProxyFactory() {
|
function ZabbixCachingProxyFactory() {
|
||||||
|
|
||||||
class ZabbixCachingProxy {
|
class ZabbixCachingProxy {
|
||||||
constructor(zabbixAPI, cacheOptions) {
|
constructor(zabbixAPI, zabbixDBConnector, cacheOptions) {
|
||||||
this.zabbixAPI = zabbixAPI;
|
this.zabbixAPI = zabbixAPI;
|
||||||
|
this.dbConnector = zabbixDBConnector;
|
||||||
this.cacheEnabled = cacheOptions.enabled;
|
this.cacheEnabled = cacheOptions.enabled;
|
||||||
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
||||||
|
|
||||||
@@ -22,7 +23,8 @@ function ZabbixCachingProxyFactory() {
|
|||||||
history: {},
|
history: {},
|
||||||
trends: {},
|
trends: {},
|
||||||
macros: {},
|
macros: {},
|
||||||
globalMacros: {}
|
globalMacros: {},
|
||||||
|
itServices: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.historyPromises = {};
|
this.historyPromises = {};
|
||||||
@@ -31,6 +33,13 @@ function ZabbixCachingProxyFactory() {
|
|||||||
this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI),
|
this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI),
|
||||||
this.historyPromises, getHistoryRequestHash);
|
this.historyPromises, getHistoryRequestHash);
|
||||||
|
|
||||||
|
if (this.dbConnector) {
|
||||||
|
this.getHistoryDB = callAPIRequestOnce(_.bind(this.dbConnector.getHistory, this.dbConnector),
|
||||||
|
this.historyPromises, getDBQueryHash);
|
||||||
|
this.getTrendsDB = callAPIRequestOnce(_.bind(this.dbConnector.getTrends, this.dbConnector),
|
||||||
|
this.historyPromises, getDBQueryHash);
|
||||||
|
}
|
||||||
|
|
||||||
// Don't run duplicated requests
|
// Don't run duplicated requests
|
||||||
this.groupPromises = {};
|
this.groupPromises = {};
|
||||||
this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI),
|
this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI),
|
||||||
@@ -48,6 +57,14 @@ function ZabbixCachingProxyFactory() {
|
|||||||
this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI),
|
this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI),
|
||||||
this.itemPromises, getRequestHash);
|
this.itemPromises, getRequestHash);
|
||||||
|
|
||||||
|
this.itemByIdPromises = {};
|
||||||
|
this.getItemsByIdOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItemsByIDs, this.zabbixAPI),
|
||||||
|
this.itemPromises, getRequestHash);
|
||||||
|
|
||||||
|
this.itServicesPromises = {};
|
||||||
|
this.getITServicesOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getITService, this.zabbixAPI),
|
||||||
|
this.itServicesPromises, getRequestHash);
|
||||||
|
|
||||||
this.macroPromises = {};
|
this.macroPromises = {};
|
||||||
this.getMacrosOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getMacros, this.zabbixAPI),
|
this.getMacrosOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getMacros, this.zabbixAPI),
|
||||||
this.macroPromises, getRequestHash);
|
this.macroPromises, getRequestHash);
|
||||||
@@ -103,6 +120,15 @@ function ZabbixCachingProxyFactory() {
|
|||||||
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItemsByIDs(itemids) {
|
||||||
|
let params = [itemids];
|
||||||
|
return this.proxyRequest(this.getItemsByIdOnce, params, this.cache.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
getITServices() {
|
||||||
|
return this.proxyRequest(this.getITServicesOnce, [], this.cache.itServices);
|
||||||
|
}
|
||||||
|
|
||||||
getMacros(hostids) {
|
getMacros(hostids) {
|
||||||
// Merge global macros and host macros
|
// Merge global macros and host macros
|
||||||
let promises = [
|
let promises = [
|
||||||
@@ -195,6 +221,14 @@ function getHistoryRequestHash(args) {
|
|||||||
return stamp.getHash();
|
return stamp.getHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDBQueryHash(args) {
|
||||||
|
let itemids = _.map(args[0], 'itemid');
|
||||||
|
let consolidateBy = args[3].consolidateBy;
|
||||||
|
let intervalMs = args[3].intervalMs;
|
||||||
|
let stamp = itemids.join() + args[1] + args[2] + consolidateBy + intervalMs;
|
||||||
|
return stamp.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
String.prototype.getHash = function() {
|
String.prototype.getHash = function() {
|
||||||
var hash = 0, i, chr, len;
|
var hash = 0, i, chr, len;
|
||||||
if (this.length !== 0) {
|
if (this.length !== 0) {
|
||||||
|
|||||||
192
src/datasource-zabbix/zabbixDBConnector.js
Normal file
192
src/datasource-zabbix/zabbixDBConnector.js
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const DEFAULT_QUERY_LIMIT = 10000;
|
||||||
|
const HISTORY_TO_TABLE_MAP = {
|
||||||
|
'0': 'history',
|
||||||
|
'1': 'history_str',
|
||||||
|
'2': 'history_log',
|
||||||
|
'3': 'history_uint',
|
||||||
|
'4': 'history_text'
|
||||||
|
};
|
||||||
|
|
||||||
|
const TREND_TO_TABLE_MAP = {
|
||||||
|
'0': 'trends',
|
||||||
|
'3': 'trends_uint'
|
||||||
|
};
|
||||||
|
|
||||||
|
const consolidateByFunc = {
|
||||||
|
'avg': 'AVG',
|
||||||
|
'min': 'MIN',
|
||||||
|
'max': 'MAX',
|
||||||
|
'sum': 'SUM',
|
||||||
|
'count': 'COUNT'
|
||||||
|
};
|
||||||
|
|
||||||
|
const consolidateByTrendColumns = {
|
||||||
|
'avg': 'value_avg',
|
||||||
|
'min': 'value_min',
|
||||||
|
'max': 'value_max'
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @ngInject */
|
||||||
|
function ZabbixDBConnectorFactory(datasourceSrv, backendSrv) {
|
||||||
|
|
||||||
|
class ZabbixDBConnector {
|
||||||
|
|
||||||
|
constructor(sqlDataSourceId, options = {}) {
|
||||||
|
let {limit} = options;
|
||||||
|
|
||||||
|
this.sqlDataSourceId = sqlDataSourceId;
|
||||||
|
this.limit = limit || DEFAULT_QUERY_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to load DS with given id to check it's exist.
|
||||||
|
* @param {*} datasourceId ID of SQL data source
|
||||||
|
*/
|
||||||
|
loadSQLDataSource(datasourceId) {
|
||||||
|
let ds = _.find(datasourceSrv.getAll(), {'id': datasourceId});
|
||||||
|
if (ds) {
|
||||||
|
return datasourceSrv.loadDatasource(ds.name)
|
||||||
|
.then(ds => {
|
||||||
|
console.log('SQL data source loaded', ds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.reject(`SQL Data Source with ID ${datasourceId} not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to invoke test query for one of Zabbix database tables.
|
||||||
|
*/
|
||||||
|
testSQLDataSource() {
|
||||||
|
let testQuery = `SELECT itemid AS metric, clock AS time_sec, value_avg AS value FROM trends_uint LIMIT 1`;
|
||||||
|
return this.invokeSQLQuery(testQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory(items, timeFrom, timeTill, options) {
|
||||||
|
let {intervalMs, consolidateBy} = options;
|
||||||
|
let intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
let aggFunction = consolidateByFunc[consolidateBy];
|
||||||
|
|
||||||
|
// Group items by value type and perform request for each value type
|
||||||
|
let grouped_items = _.groupBy(items, 'value_type');
|
||||||
|
let promises = _.map(grouped_items, (items, value_type) => {
|
||||||
|
let itemids = _.map(items, 'itemid').join(', ');
|
||||||
|
let table = HISTORY_TO_TABLE_MAP[value_type];
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
SELECT itemid AS metric, clock AS time_sec, ${aggFunction}(value) AS value
|
||||||
|
FROM ${table}
|
||||||
|
WHERE itemid IN (${itemids})
|
||||||
|
AND clock > ${timeFrom} AND clock < ${timeTill}
|
||||||
|
GROUP BY time_sec DIV ${intervalSec}, metric
|
||||||
|
`;
|
||||||
|
|
||||||
|
query = compactSQLQuery(query);
|
||||||
|
return this.invokeSQLQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(results => {
|
||||||
|
return _.flatten(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTrends(items, timeFrom, timeTill, options) {
|
||||||
|
let {intervalMs, consolidateBy} = options;
|
||||||
|
let intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
let aggFunction = consolidateByFunc[consolidateBy];
|
||||||
|
|
||||||
|
// Group items by value type and perform request for each value type
|
||||||
|
let grouped_items = _.groupBy(items, 'value_type');
|
||||||
|
let promises = _.map(grouped_items, (items, value_type) => {
|
||||||
|
let itemids = _.map(items, 'itemid').join(', ');
|
||||||
|
let table = TREND_TO_TABLE_MAP[value_type];
|
||||||
|
let valueColumn = _.includes(['avg', 'min', 'max'], consolidateBy) ? consolidateBy : 'avg';
|
||||||
|
valueColumn = consolidateByTrendColumns[valueColumn];
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
SELECT itemid AS metric, clock AS time_sec, ${aggFunction}(${valueColumn}) AS value
|
||||||
|
FROM ${table}
|
||||||
|
WHERE itemid IN (${itemids})
|
||||||
|
AND clock > ${timeFrom} AND clock < ${timeTill}
|
||||||
|
GROUP BY time_sec DIV ${intervalSec}, metric
|
||||||
|
`;
|
||||||
|
|
||||||
|
query = compactSQLQuery(query);
|
||||||
|
return this.invokeSQLQuery(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(results => {
|
||||||
|
return _.flatten(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGrafanaTSResponse(history, items, addHostName = true) {
|
||||||
|
return convertGrafanaTSResponse(history, items, addHostName);
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeSQLQuery(query) {
|
||||||
|
let queryDef = {
|
||||||
|
refId: 'A',
|
||||||
|
format: 'time_series',
|
||||||
|
datasourceId: this.sqlDataSourceId,
|
||||||
|
rawSql: query,
|
||||||
|
maxDataPoints: this.limit
|
||||||
|
};
|
||||||
|
|
||||||
|
return backendSrv.datasourceRequest({
|
||||||
|
url: '/api/tsdb/query',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
queries: [queryDef],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
let results = response.data.results;
|
||||||
|
if (results['A']) {
|
||||||
|
return results['A'].series;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZabbixDBConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('grafana.services')
|
||||||
|
.factory('ZabbixDBConnector', ZabbixDBConnectorFactory);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function convertGrafanaTSResponse(time_series, items, addHostName) {
|
||||||
|
var hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate
|
||||||
|
let grafanaSeries = _.map(time_series, series => {
|
||||||
|
let itemid = series.name;
|
||||||
|
let datapoints = series.points;
|
||||||
|
var item = _.find(items, {'itemid': itemid});
|
||||||
|
var alias = item.name;
|
||||||
|
if (_.keys(hosts).length > 1 && addHostName) { //only when actual multi hosts selected
|
||||||
|
var host = _.find(hosts, {'hostid': item.hostid});
|
||||||
|
alias = host.name + ": " + alias;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
target: alias,
|
||||||
|
datapoints: datapoints
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return _.sortBy(grafanaSeries, 'target');
|
||||||
|
}
|
||||||
|
|
||||||
|
function compactSQLQuery(query) {
|
||||||
|
return query.replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
@@ -55,6 +55,10 @@
|
|||||||
placeholder="trigger name"
|
placeholder="trigger name"
|
||||||
class="gf-form-input"
|
class="gf-form-input"
|
||||||
ng-style="editor.panel.triggers.trigger.style"
|
ng-style="editor.panel.triggers.trigger.style"
|
||||||
|
ng-class="{
|
||||||
|
'zbx-variable': editor.isVariable(editor.panel.triggers.trigger.filter),
|
||||||
|
'zbx-regex': editor.isRegex(editor.panel.triggers.trigger.filter)
|
||||||
|
}"
|
||||||
empty-to-null>
|
empty-to-null>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ class TriggerPanelCtrl extends PanelCtrl {
|
|||||||
.then(datasource => {
|
.then(datasource => {
|
||||||
var zabbix = datasource.zabbix;
|
var zabbix = datasource.zabbix;
|
||||||
this.zabbix = zabbix;
|
this.zabbix = zabbix;
|
||||||
|
this.datasource = datasource;
|
||||||
var showEvents = this.panel.showEvents.value;
|
var showEvents = this.panel.showEvents.value;
|
||||||
var triggerFilter = this.panel.triggers;
|
var triggerFilter = this.panel.triggers;
|
||||||
var hideHostsInMaintenance = this.panel.hideHostsInMaintenance;
|
var hideHostsInMaintenance = this.panel.hideHostsInMaintenance;
|
||||||
@@ -143,11 +144,12 @@ class TriggerPanelCtrl extends PanelCtrl {
|
|||||||
showTriggers: showEvents,
|
showTriggers: showEvents,
|
||||||
hideHostsInMaintenance: hideHostsInMaintenance
|
hideHostsInMaintenance: hideHostsInMaintenance
|
||||||
};
|
};
|
||||||
let getTriggers = zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions);
|
|
||||||
return getTriggers.then(triggers => {
|
return zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions);
|
||||||
|
})
|
||||||
|
.then(triggers => {
|
||||||
return _.map(triggers, this.formatTrigger.bind(this));
|
return _.map(triggers, this.formatTrigger.bind(this));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAcknowledges(triggerList) {
|
getAcknowledges(triggerList) {
|
||||||
@@ -191,6 +193,7 @@ class TriggerPanelCtrl extends PanelCtrl {
|
|||||||
filterTriggers(triggerList) {
|
filterTriggers(triggerList) {
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
var triggerFilter = this.panel.triggers.trigger.filter;
|
var triggerFilter = this.panel.triggers.trigger.filter;
|
||||||
|
triggerFilter = this.datasource.replaceTemplateVars(triggerFilter);
|
||||||
if (triggerFilter) {
|
if (triggerFilter) {
|
||||||
triggerList = filterTriggers(triggerList, triggerFilter);
|
triggerList = filterTriggers(triggerList, triggerFilter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
{"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"},
|
{"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"},
|
||||||
{"name": "Triggers", "path": "img/screenshot-triggers.png"}
|
{"name": "Triggers", "path": "img/screenshot-triggers.png"}
|
||||||
],
|
],
|
||||||
"version": "3.4.0",
|
"version": "3.5.1",
|
||||||
"updated": "2017-05-17"
|
"updated": "2017-07-10"
|
||||||
},
|
},
|
||||||
|
|
||||||
"includes": [
|
"includes": [
|
||||||
|
|||||||
Reference in New Issue
Block a user