From 8b374781318052e8da39d59cf2f174d457f19549 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 14 Mar 2016 23:42:24 +0300 Subject: [PATCH 01/24] Initial ES6 migration. module.js and datasource.js are migrated to ES6. --- .gitignore | 15 + .jshintrc | 6 +- Gruntfile.js | 62 +++ package.json | 37 ++ plugins/datasource-zabbix/datasource.js | 429 ----------------- plugins/datasource-zabbix/module.js | 24 - .../datasource-zabbix/addMetricFunction.js | 0 .../dataProcessingService.js | 0 src/datasource-zabbix/datasource.js | 430 ++++++++++++++++++ .../datasource-zabbix/directives.js | 0 .../datasource-zabbix/helperFunctions.js | 0 .../datasource-zabbix/metricFunctionEditor.js | 0 .../datasource-zabbix/metricFunctions.js | 0 src/datasource-zabbix/module.js | 19 + .../partials/annotations.editor.html | 0 .../datasource-zabbix/partials/config.html | 0 .../partials/query.editor.html | 0 .../partials/query.options.html | 0 .../datasource-zabbix/plugin.json | 0 .../datasource-zabbix/queryCtrl.js | 0 .../datasource-zabbix/queryProcessor.js | 0 {plugins => src}/datasource-zabbix/utils.js | 0 .../datasource-zabbix/zabbixAPI.js | 0 .../datasource-zabbix/zabbixAPIService.js | 0 .../datasource-zabbix/zabbixCache.js | 0 {plugins => src}/panel-triggers/editor.html | 0 {plugins => src}/panel-triggers/editor.js | 0 {plugins => src}/panel-triggers/module.html | 0 {plugins => src}/panel-triggers/module.js | 0 {plugins => src}/panel-triggers/plugin.json | 0 .../panel-triggers/trigger.colorpicker.html | 0 31 files changed, 567 insertions(+), 455 deletions(-) create mode 100644 Gruntfile.js create mode 100644 package.json delete mode 100644 plugins/datasource-zabbix/datasource.js delete mode 100644 plugins/datasource-zabbix/module.js rename {plugins => src}/datasource-zabbix/addMetricFunction.js (100%) rename {plugins => src}/datasource-zabbix/dataProcessingService.js (100%) create mode 100644 src/datasource-zabbix/datasource.js rename {plugins => src}/datasource-zabbix/directives.js (100%) rename {plugins => src}/datasource-zabbix/helperFunctions.js (100%) rename {plugins => src}/datasource-zabbix/metricFunctionEditor.js (100%) rename {plugins => src}/datasource-zabbix/metricFunctions.js (100%) create mode 100644 src/datasource-zabbix/module.js rename {plugins => src}/datasource-zabbix/partials/annotations.editor.html (100%) rename {plugins => src}/datasource-zabbix/partials/config.html (100%) rename {plugins => src}/datasource-zabbix/partials/query.editor.html (100%) rename {plugins => src}/datasource-zabbix/partials/query.options.html (100%) rename {plugins => src}/datasource-zabbix/plugin.json (100%) rename {plugins => src}/datasource-zabbix/queryCtrl.js (100%) rename {plugins => src}/datasource-zabbix/queryProcessor.js (100%) rename {plugins => src}/datasource-zabbix/utils.js (100%) rename {plugins => src}/datasource-zabbix/zabbixAPI.js (100%) rename {plugins => src}/datasource-zabbix/zabbixAPIService.js (100%) rename {plugins => src}/datasource-zabbix/zabbixCache.js (100%) rename {plugins => src}/panel-triggers/editor.html (100%) rename {plugins => src}/panel-triggers/editor.js (100%) rename {plugins => src}/panel-triggers/module.html (100%) rename {plugins => src}/panel-triggers/module.js (100%) rename {plugins => src}/panel-triggers/plugin.json (100%) rename {plugins => src}/panel-triggers/trigger.colorpicker.html (100%) diff --git a/.gitignore b/.gitignore index e368599..5593838 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,18 @@ # Builded docs docs/site/ + +node_modules +npm-debug.log +coverage/ +.aws-config.json +awsconfig +/emails/dist +/public_gen +/tmp +vendor/phantomjs/phantomjs + +dist/ + +# locally required config files +public/css/*.min.css diff --git a/.jshintrc b/.jshintrc index 9924799..36c9c9f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -4,7 +4,7 @@ "bitwise":false, "curly": true, "eqnull": true, - "globalstrict": true, + "strict": true, "devel": true, "eqeqeq": true, "forin": false, @@ -12,7 +12,7 @@ "supernew": true, "expr": true, "indent": 2, - "latedef": true, + "latedef": false, "newcap": true, "noarg": true, "noempty": true, @@ -25,9 +25,11 @@ "unused": true, "maxdepth": 6, "maxlen": 140, + "esnext": true, "globals": { "System": true, + "Promise": true, "define": true, "require": true, "Chromath": false, diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..2c8f35a --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,62 @@ +module.exports = function(grunt) { + + require('load-grunt-tasks')(grunt); + + grunt.loadNpmTasks('grunt-execute'); + grunt.loadNpmTasks('grunt-contrib-clean'); + + grunt.initConfig({ + + clean: ["dist"], + + copy: { + src_to_dist: { + cwd: 'src', + expand: true, + src: [ + '**/*', + '!**/datasource.js', + '!**/module.js', + '!**/*.scss' + ], + dest: 'dist/src' + }, + pluginDef: { + expand: true, + src: ['plugin.json', 'README.md'], + dest: 'dist', + } + }, + + watch: { + rebuild_all: { + files: ['src/**/*', 'plugin.json'], + tasks: ['default'], + options: {spawn: false} + }, + }, + + babel: { + options: { + sourceMap: true, + presets: ["es2015"], + plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"], + }, + dist: { + files: [{ + cwd: 'src', + expand: true, + src: [ + '**/**/datasource.js', + '**/**/module.js', + ], + dest: 'dist/src', + ext:'.js' + }] + }, + }, + + }); + + grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel']); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..64cde5b --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "grafana-zabbix", + "private": false, + "version": "3.0.0", + "description": "", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/alexanderzobnin/grafana-zabbix.git" + }, + "author": "Alexander Zobnin", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/alexanderzobnin/grafana-zabbix/issues" + }, + "devDependencies": { + "grunt": "~0.4.5", + "babel": "~6.5.1", + "grunt-babel": "~6.0.0", + "grunt-contrib-copy": "~0.8.2", + "grunt-contrib-watch": "^0.6.1", + "grunt-contrib-uglify": "~0.11.0", + "grunt-systemjs-builder": "^0.2.5", + "load-grunt-tasks": "~3.2.0", + "grunt-execute": "~0.2.2", + "grunt-contrib-clean": "~0.6.0" + }, + "dependencies": { + "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", + "babel-plugin-transform-es2015-for-of": "^6.5.0", + "babel-preset-es2015": "^6.5.0", + "lodash": "~4.0.0" + }, + "homepage": "http://grafana-zabbix.org" +} diff --git a/plugins/datasource-zabbix/datasource.js b/plugins/datasource-zabbix/datasource.js deleted file mode 100644 index d089444..0000000 --- a/plugins/datasource-zabbix/datasource.js +++ /dev/null @@ -1,429 +0,0 @@ -define([ - 'angular', - 'lodash', - 'app/core/utils/datemath', - './utils', - './metricFunctions', - './queryProcessor', - './directives', - './zabbixAPI', - './helperFunctions', - './dataProcessingService', - './zabbixCache', - './queryCtrl', - './addMetricFunction', - './metricFunctionEditor' -], -function (angular, _, dateMath, utils, metricFunctions) { - 'use strict'; - - /** @ngInject */ - function ZabbixAPIDatasource(instanceSettings, $q, templateSrv, alertSrv, zabbixHelperSrv, - ZabbixAPI, ZabbixCachingProxy, QueryProcessor, DataProcessingService) { - - // General data source settings - this.name = instanceSettings.name; - this.url = instanceSettings.url; - this.basicAuth = instanceSettings.basicAuth; - this.withCredentials = instanceSettings.withCredentials; - - // Zabbix API credentials - this.username = instanceSettings.jsonData.username; - this.password = instanceSettings.jsonData.password; - - // Use trends instead history since specified time - this.trends = instanceSettings.jsonData.trends; - this.trendsFrom = instanceSettings.jsonData.trendsFrom || '7d'; - - // Set cache update interval - var ttl = instanceSettings.jsonData.cacheTTL || '1h'; - this.cacheTTL = utils.parseInterval(ttl); - - // Initialize Zabbix API - this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials); - - // Initialize cache service - this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL); - - // Initialize query builder - this.queryProcessor = new QueryProcessor(this.zabbixCache); - - console.log(this.zabbixCache); - - //////////////////////// - // Datasource methods // - //////////////////////// - - /** - * Test connection to Zabbix API - * @return {object} Connection status and Zabbix API version - */ - this.testDatasource = function() { - var self = this; - return this.zabbixAPI.getVersion().then(function (version) { - return self.zabbixAPI.login().then(function (auth) { - if (auth) { - return { - status: "success", - title: "Success", - message: "Zabbix API version: " + version - }; - } else { - return { - status: "error", - title: "Invalid user name or password", - message: "Zabbix API version: " + version - }; - } - }, function(error) { - console.log(error); - return { - status: "error", - title: "Connection failed", - message: error - }; - }); - }, - function(error) { - console.log(error); - return { - status: "error", - title: "Connection failed", - message: "Could not connect to given url" - }; - }); - }; - - /** - * Query panel data. Calls for each panel in dashboard. - * @param {Object} options Contains time range, targets and other info. - * @return {Object} Grafana metrics object with timeseries data for each target. - */ - this.query = function(options) { - var self = this; - - // get from & to in seconds - var from = Math.ceil(dateMath.parse(options.range.from) / 1000); - var to = Math.ceil(dateMath.parse(options.range.to) / 1000); - var useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000); - - // Create request for each target - var promises = _.map(options.targets, function(target) { - - if (target.mode !== 1) { - - // Don't request undefined and hidden targets - if (target.hide || !target.group || - !target.host || !target.item) { - return []; - } - - // Replace templated variables - var groupFilter = templateSrv.replace(target.group.filter, options.scopedVars); - var hostFilter = templateSrv.replace(target.host.filter, options.scopedVars); - var appFilter = templateSrv.replace(target.application.filter, options.scopedVars); - var itemFilter = templateSrv.replace(target.item.filter, options.scopedVars); - - // Query numeric data - if (!target.mode || target.mode === 0) { - - // Build query in asynchronous manner - return self.queryProcessor.build(groupFilter, hostFilter, appFilter, itemFilter) - .then(function(items) { - // Add hostname for items from multiple hosts - var addHostName = target.host.isRegex; - var getHistory; - - // Use trends - if ((from < useTrendsFrom) && self.trends) { - - // Find trendValue() function and get specified trend value - var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); - var trendValueFunc = _.find(target.functions, function(func) { - return _.contains(trendFunctions, func.def.name); - }); - var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg"; - - getHistory = self.zabbixAPI.getTrend(items, from, to).then(function(history) { - return self.queryProcessor.handleTrends(history, addHostName, valueType); - }); - } else { - - // Use history - getHistory = self.zabbixCache.getHistory(items, from, to).then(function(history) { - return self.queryProcessor.handleHistory(history, addHostName); - }); - } - - return getHistory.then(function (timeseries_data) { - timeseries_data = _.map(timeseries_data, function (timeseries) { - - // Filter only transform functions - var transformFunctions = bindFunctionDefs(target.functions, 'Transform'); - - // Metric data processing - var dp = timeseries.datapoints; - for (var i = 0; i < transformFunctions.length; i++) { - dp = transformFunctions[i](dp); - } - timeseries.datapoints = dp; - - return timeseries; - }); - - // Aggregations - var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate'); - var dp = _.map(timeseries_data, 'datapoints'); - if (aggregationFunctions.length) { - for (var i = 0; i < aggregationFunctions.length; i++) { - dp = aggregationFunctions[i](dp); - } - var lastAgg = _.findLast(target.functions, function(func) { - return _.contains( - _.map(metricFunctions.getCategories()['Aggregate'], 'name'), func.def.name); - }); - timeseries_data = [{ - target: lastAgg.text, - datapoints: dp - }]; - } - - // Apply alias functions - var aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); - for (var j = 0; j < aliasFunctions.length; j++) { - _.each(timeseries_data, aliasFunctions[j]); - } - - return timeseries_data; - }); - }); - } - - // Query text data - else if (target.mode === 2) { - return self.queryProcessor.build(groupFilter, hostFilter, appFilter, itemFilter) - .then(function(items) { - var deferred = $q.defer(); - if (items.length) { - self.zabbixAPI.getLastValue(items[0].itemid).then(function(lastvalue) { - if (target.textFilter) { - var text_extract_pattern = new RegExp(templateSrv.replace(target.textFilter, options.scopedVars)); - var result = text_extract_pattern.exec(lastvalue); - if (result) { - if (target.useCaptureGroups) { - result = result[1]; - } else { - result = result[0]; - } - } - deferred.resolve(result); - } else { - deferred.resolve(lastvalue); - } - }); - } else { - deferred.resolve(null); - } - return deferred.promise.then(function(text) { - return { - target: target.item.name, - datapoints: [[text, to * 1000]] - }; - }); - }); - } - } - - // IT services mode - else if (target.mode === 1) { - // Don't show undefined and hidden targets - if (target.hide || !target.itservice || !target.slaProperty) { - return []; - } else { - return this.zabbixAPI.getSLA(target.itservice.serviceid, from, to) - .then(_.bind(zabbixHelperSrv.handleSLAResponse, zabbixHelperSrv, target.itservice, target.slaProperty)); - } - } - }, this); - - // Data for panel (all targets) - return $q.all(_.flatten(promises)) - .then(_.flatten) - .then(function (timeseries_data) { - - // Series downsampling - var data = _.map(timeseries_data, function(timeseries) { - var DPS = DataProcessingService; - if (timeseries.datapoints.length > options.maxDataPoints) { - timeseries.datapoints = DPS.groupBy(options.interval, DPS.AVERAGE, timeseries.datapoints); - } - return timeseries; - }); - return { data: data }; - }); - }; - - function bindFunctionDefs(functionDefs, category) { - var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); - var aggFuncDefs = _.filter(functionDefs, function(func) { - return _.contains(aggregationFunctions, func.def.name); - }); - return _.map(aggFuncDefs, function(func) { - var funcInstance = metricFunctions.createFuncInstance(func.def, func.params); - return funcInstance.bindFunction(DataProcessingService.metricFunctions); - }); - } - - //////////////// - // Templating // - //////////////// - - /** - * Find metrics from templated request. - * - * @param {string} query Query from Templating - * @return {string} Metric name - group, host, app or item or list - * of metrics in "{metric1,metcic2,...,metricN}" format. - */ - this.metricFindQuery = function (query) { - // Split query. Query structure: - // group.host.app.item - var parts = []; - _.each(query.split('.'), function (part) { - part = templateSrv.replace(part); - - // Replace wildcard to regex - if (part === '*') { - part = '/.*/'; - } - parts.push(part); - }); - var template = _.object(['group', 'host', 'app', 'item'], parts); - - // Get items - if (parts.length === 4) { - return this.queryProcessor.filterItems(template.group, template.host, - template.app, 'all', true) - .then(function(items) { - return _.map(items, formatMetric); - }); - } - // Get applications - else if (parts.length === 3) { - return this.queryProcessor.filterApplications(template.group, template.host) - .then(function(apps) { - return _.map(apps, formatMetric); - }); - } - // Get hosts - else if (parts.length === 2) { - return this.queryProcessor.filterHosts(template.group) - .then(function(hosts) { - return _.map(hosts, formatMetric); - }); - } - // Get groups - else if (parts.length === 1) { - return this.zabbixCache.getGroups(template.group).then(function(groups) { - return _.map(groups, formatMetric); - }); - } - // Return empty object for invalid request - else { - return $q.when([]); - } - }; - - function formatMetric(metricObj) { - return { - text: metricObj.name, - expandable: false - }; - } - - ///////////////// - // Annotations // - ///////////////// - - this.annotationQuery = function(options) { - var from = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000); - var to = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000); - var annotation = options.annotation; - var self = this; - var showEvents = annotation.showOkEvents ? [0, 1] : 1; - - var buildQuery = self.queryProcessor.buildTriggerQuery(templateSrv.replace(annotation.group), - templateSrv.replace(annotation.host), - templateSrv.replace(annotation.application)); - return buildQuery.then(function(query) { - return self.zabbixAPI.getTriggers(query.groupids, - query.hostids, - query.applicationids, - showEvents) - .then(function(triggers) { - - // Filter triggers by description - if (utils.isRegex(annotation.trigger)) { - triggers = _.filter(triggers, function(trigger) { - return utils.buildRegex(annotation.trigger).test(trigger.description); - }); - } else if (annotation.trigger) { - triggers = _.filter(triggers, function(trigger) { - return trigger.description === annotation.trigger; - }); - } - - // Remove events below the chose severity - triggers = _.filter(triggers, function(trigger) { - return Number(trigger.priority) >= Number(annotation.minseverity); - }); - - var objectids = _.map(triggers, 'triggerid'); - var params = { - output: 'extend', - time_from: from, - time_till: to, - objectids: objectids, - select_acknowledges: 'extend', - selectHosts: 'extend', - value: showEvents - }; - - return self.zabbixAPI.request('event.get', params) - .then(function (events) { - var indexedTriggers = _.indexBy(triggers, 'triggerid'); - - // Hide acknowledged events if option enabled - if (annotation.hideAcknowledged) { - events = _.filter(events, function(event) { - return !event.acknowledges.length; - }); - } - - return _.map(events, function(e) { - var title =''; - if (annotation.showHostname) { - title += e.hosts[0].name + ': '; - } - - // Show event type (OK or Problem) - title += Number(e.value) ? 'Problem' : 'OK'; - - var formatted_acknowledges = utils.formatAcknowledges(e.acknowledges); - return { - annotation: annotation, - time: e.clock * 1000, - title: title, - text: indexedTriggers[e.objectid].description + formatted_acknowledges - }; - }); - }); - }); - }); - }; - - } - - return ZabbixAPIDatasource; - -}); diff --git a/plugins/datasource-zabbix/module.js b/plugins/datasource-zabbix/module.js deleted file mode 100644 index 38722d9..0000000 --- a/plugins/datasource-zabbix/module.js +++ /dev/null @@ -1,24 +0,0 @@ -define([ - './datasource', - './queryCtrl' -], -function (ZabbixAPIDatasource, ZabbixQueryCtrl) { - 'use strict'; - - function ZabbixQueryOptionsCtrl() {} - ZabbixQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; - - function ZabbixAnnotationsQueryCtrl() {} - ZabbixAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html'; - - function ZabbixConfigCtrl() {} - ZabbixConfigCtrl.templateUrl = 'partials/config.html'; - - return { - Datasource: ZabbixAPIDatasource, - QueryCtrl: ZabbixQueryCtrl, - ConfigCtrl: ZabbixConfigCtrl, - QueryOptionsCtrl: ZabbixQueryOptionsCtrl, - AnnotationsQueryCtrl: ZabbixAnnotationsQueryCtrl - }; -}); diff --git a/plugins/datasource-zabbix/addMetricFunction.js b/src/datasource-zabbix/addMetricFunction.js similarity index 100% rename from plugins/datasource-zabbix/addMetricFunction.js rename to src/datasource-zabbix/addMetricFunction.js diff --git a/plugins/datasource-zabbix/dataProcessingService.js b/src/datasource-zabbix/dataProcessingService.js similarity index 100% rename from plugins/datasource-zabbix/dataProcessingService.js rename to src/datasource-zabbix/dataProcessingService.js diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js new file mode 100644 index 0000000..f9699f3 --- /dev/null +++ b/src/datasource-zabbix/datasource.js @@ -0,0 +1,430 @@ +//import angular from 'angular'; +import _ from 'lodash'; +import {parse as dateMathParse} from 'app/core/utils/datemath'; +import Utils from './utils'; +import metricFunctions from './metricFunctions'; +import {zabbixHelperSrv} from './helperFunctions'; +import {ZabbixAPI} from './zabbixAPI'; +import {ZabbixCachingProxy} from './zabbixCache'; +import {QueryProcessor} from './queryProcessor'; +import {DataProcessingService} from './dataProcessingService'; + +export class ZabbixAPIDatasource { + + constructor(instanceSettings, $q, templateSrv, alertSrv, ZabbixAPI, ZabbixCachingProxy, QueryProcessor, zabbixHelperSrv, DataProcessingService) { + + // General data source settings + this.name = instanceSettings.name; + this.url = instanceSettings.url; + this.basicAuth = instanceSettings.basicAuth; + this.withCredentials = instanceSettings.withCredentials; + + // Zabbix API credentials + this.username = instanceSettings.jsonData.username; + this.password = instanceSettings.jsonData.password; + + // Use trends instead history since specified time + this.trends = instanceSettings.jsonData.trends; + this.trendsFrom = instanceSettings.jsonData.trendsFrom || '7d'; + + // Set cache update interval + var ttl = instanceSettings.jsonData.cacheTTL || '1h'; + this.cacheTTL = Utils.parseInterval(ttl); + + // Initialize Zabbix API + this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials); + + // Initialize cache service + this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL); + + // Initialize query builder + this.queryProcessor = new QueryProcessor(this.zabbixCache); + + // Dependencies + this.q = $q; + this.templateSrv = templateSrv; + this.alertSrv = alertSrv; + this.zabbixHelperSrv = zabbixHelperSrv; + this.DataProcessingService = DataProcessingService; + + console.log(this.zabbixCache); + } + + //////////////////////// + // Datasource methods // + //////////////////////// + + /** + * Test connection to Zabbix API + * @return {object} Connection status and Zabbix API version + */ + testDatasource() { + var self = this; + return this.zabbixAPI.getVersion().then(function (version) { + return self.zabbixAPI.login().then(function (auth) { + if (auth) { + return { + status: "success", + title: "Success", + message: "Zabbix API version: " + version + }; + } else { + return { + status: "error", + title: "Invalid user name or password", + message: "Zabbix API version: " + version + }; + } + }, function(error) { + console.log(error); + return { + status: "error", + title: "Connection failed", + message: error + }; + }); + }, + function(error) { + console.log(error); + return { + status: "error", + title: "Connection failed", + message: "Could not connect to given url" + }; + }); + } + + /** + * Query panel data. Calls for each panel in dashboard. + * @param {Object} options Contains time range, targets and other info. + * @return {Object} Grafana metrics object with timeseries data for each target. + */ + query(options) { + var self = this; + + // get from & to in seconds + var from = Math.ceil(dateMathParse(options.range.from) / 1000); + var to = Math.ceil(dateMathParse(options.range.to) / 1000); + var useTrendsFrom = Math.ceil(dateMathParse('now-' + this.trendsFrom) / 1000); + + // Create request for each target + var promises = _.map(options.targets, function(target) { + + if (target.mode !== 1) { + + // Don't request undefined and hidden targets + if (target.hide || !target.group || + !target.host || !target.item) { + return []; + } + + // Replace templated variables + var groupFilter = this.templateSrv.replace(target.group.filter, options.scopedVars); + var hostFilter = this.templateSrv.replace(target.host.filter, options.scopedVars); + var appFilter = this.templateSrv.replace(target.application.filter, options.scopedVars); + var itemFilter = this.templateSrv.replace(target.item.filter, options.scopedVars); + + // Query numeric data + if (!target.mode || target.mode === 0) { + + // Build query in asynchronous manner + return self.queryProcessor.build(groupFilter, hostFilter, appFilter, itemFilter) + .then(function(items) { + // Add hostname for items from multiple hosts + var addHostName = target.host.isRegex; + var getHistory; + + // Use trends + if ((from < useTrendsFrom) && self.trends) { + + // Find trendValue() function and get specified trend value + var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); + var trendValueFunc = _.find(target.functions, function(func) { + return _.contains(trendFunctions, func.def.name); + }); + var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg"; + + getHistory = self.zabbixAPI.getTrend(items, from, to).then(function(history) { + return self.queryProcessor.handleTrends(history, addHostName, valueType); + }); + } else { + + // Use history + getHistory = self.zabbixCache.getHistory(items, from, to).then(function(history) { + return self.queryProcessor.handleHistory(history, addHostName); + }); + } + + return getHistory.then(function (timeseries_data) { + timeseries_data = _.map(timeseries_data, function (timeseries) { + + // Filter only transform functions + var transformFunctions = bindFunctionDefs(target.functions, 'Transform', self.DataProcessingService); + + // Metric data processing + var dp = timeseries.datapoints; + for (var i = 0; i < transformFunctions.length; i++) { + dp = transformFunctions[i](dp); + } + timeseries.datapoints = dp; + + return timeseries; + }); + + // Aggregations + var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate', self.DataProcessingService); + var dp = _.map(timeseries_data, 'datapoints'); + if (aggregationFunctions.length) { + for (var i = 0; i < aggregationFunctions.length; i++) { + dp = aggregationFunctions[i](dp); + } + var lastAgg = _.findLast(target.functions, function(func) { + return _.contains( + _.map(metricFunctions.getCategories()['Aggregate'], 'name'), func.def.name); + }); + timeseries_data = [{ + target: lastAgg.text, + datapoints: dp + }]; + } + + // Apply alias functions + var aliasFunctions = bindFunctionDefs(target.functions, 'Alias', self.DataProcessingService); + for (var j = 0; j < aliasFunctions.length; j++) { + _.each(timeseries_data, aliasFunctions[j]); + } + + return timeseries_data; + }); + }); + } + + // Query text data + else if (target.mode === 2) { + return self.queryProcessor.build(groupFilter, hostFilter, appFilter, itemFilter) + .then(function(items) { + var deferred = self.q.defer(); + if (items.length) { + self.zabbixAPI.getLastValue(items[0].itemid).then(function(lastvalue) { + if (target.textFilter) { + var text_extract_pattern = new RegExp(self.templateSrv.replace(target.textFilter, options.scopedVars)); + var result = text_extract_pattern.exec(lastvalue); + if (result) { + if (target.useCaptureGroups) { + result = result[1]; + } else { + result = result[0]; + } + } + deferred.resolve(result); + } else { + deferred.resolve(lastvalue); + } + }); + } else { + deferred.resolve(null); + } + return deferred.promise.then(function(text) { + return { + target: target.item.name, + datapoints: [[text, to * 1000]] + }; + }); + }); + } + } + + // IT services mode + else if (target.mode === 1) { + // Don't show undefined and hidden targets + if (target.hide || !target.itservice || !target.slaProperty) { + return []; + } else { + return this.zabbixAPI.getSLA(target.itservice.serviceid, from, to) + .then(_.bind(zabbixHelperSrv.handleSLAResponse, zabbixHelperSrv, target.itservice, target.slaProperty)); + } + } + }, this); + + var self = this; + + // Data for panel (all targets) + return this.q.all(_.flatten(promises)) + .then(_.flatten) + .then(function (timeseries_data) { + + // Series downsampling + var data = _.map(timeseries_data, function(timeseries) { + var DPS = self.DataProcessingService; + if (timeseries.datapoints.length > options.maxDataPoints) { + timeseries.datapoints = DPS.groupBy(options.interval, DPS.AVERAGE, timeseries.datapoints); + } + return timeseries; + }); + return { data: data }; + }); + } + + //////////////// + // Templating // + //////////////// + + /** + * Find metrics from templated request. + * + * @param {string} query Query from Templating + * @return {string} Metric name - group, host, app or item or list + * of metrics in "{metric1,metcic2,...,metricN}" format. + */ + metricFindQuery(query) { + // Split query. Query structure: + // group.host.app.item + var parts = []; + _.each(query.split('.'), function (part) { + part = this.templateSrv.replace(part); + + // Replace wildcard to regex + if (part === '*') { + part = '/.*/'; + } + parts.push(part); + }); + var template = _.object(['group', 'host', 'app', 'item'], parts); + + // Get items + if (parts.length === 4) { + return this.queryProcessor.filterItems(template.group, template.host, + template.app, 'all', true) + .then(function(items) { + return _.map(items, formatMetric); + }); + } + // Get applications + else if (parts.length === 3) { + return this.queryProcessor.filterApplications(template.group, template.host) + .then(function(apps) { + return _.map(apps, formatMetric); + }); + } + // Get hosts + else if (parts.length === 2) { + return this.queryProcessor.filterHosts(template.group) + .then(function(hosts) { + return _.map(hosts, formatMetric); + }); + } + // Get groups + else if (parts.length === 1) { + return this.zabbixCache.getGroups(template.group).then(function(groups) { + return _.map(groups, formatMetric); + }); + } + // Return empty object for invalid request + else { + return this.q.when([]); + } + } + + ///////////////// + // Annotations // + ///////////////// + + annotationQuery(options) { + var from = Math.ceil(dateMathParse(options.rangeRaw.from) / 1000); + var to = Math.ceil(dateMathParse(options.rangeRaw.to) / 1000); + var annotation = options.annotation; + var self = this; + var showEvents = annotation.showOkEvents ? [0, 1] : 1; + + var buildQuery = self.queryProcessor.buildTriggerQuery(this.templateSrv.replace(annotation.group), + this.templateSrv.replace(annotation.host), + this.templateSrv.replace(annotation.application)); + return buildQuery.then(function(query) { + return self.zabbixAPI.getTriggers(query.groupids, + query.hostids, + query.applicationids, + showEvents) + .then(function(triggers) { + + // Filter triggers by description + if (Utils.isRegex(annotation.trigger)) { + triggers = _.filter(triggers, function(trigger) { + return Utils.buildRegex(annotation.trigger).test(trigger.description); + }); + } else if (annotation.trigger) { + triggers = _.filter(triggers, function(trigger) { + return trigger.description === annotation.trigger; + }); + } + + // Remove events below the chose severity + triggers = _.filter(triggers, function(trigger) { + return Number(trigger.priority) >= Number(annotation.minseverity); + }); + + var objectids = _.map(triggers, 'triggerid'); + var params = { + output: 'extend', + time_from: from, + time_till: to, + objectids: objectids, + select_acknowledges: 'extend', + selectHosts: 'extend', + value: showEvents + }; + + return self.zabbixAPI.request('event.get', params) + .then(function (events) { + var indexedTriggers = _.indexBy(triggers, 'triggerid'); + + // Hide acknowledged events if option enabled + if (annotation.hideAcknowledged) { + events = _.filter(events, function(event) { + return !event.acknowledges.length; + }); + } + + return _.map(events, function(e) { + var title =''; + if (annotation.showHostname) { + title += e.hosts[0].name + ': '; + } + + // Show event type (OK or Problem) + title += Number(e.value) ? 'Problem' : 'OK'; + + var formatted_acknowledges = Utils.formatAcknowledges(e.acknowledges); + return { + annotation: annotation, + time: e.clock * 1000, + title: title, + text: indexedTriggers[e.objectid].description + formatted_acknowledges + }; + }); + }); + }); + }); + } + +} + +function bindFunctionDefs(functionDefs, category, DataProcessingService) { + 'use strict'; + var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); + var aggFuncDefs = _.filter(functionDefs, function(func) { + return _.contains(aggregationFunctions, func.def.name); + }); + + return _.map(aggFuncDefs, function(func) { + var funcInstance = metricFunctions.createFuncInstance(func.def, func.params); + return funcInstance.bindFunction(DataProcessingService.metricFunctions); + }); +} + +function formatMetric(metricObj) { + 'use strict'; + return { + text: metricObj.name, + expandable: false + }; +} \ No newline at end of file diff --git a/plugins/datasource-zabbix/directives.js b/src/datasource-zabbix/directives.js similarity index 100% rename from plugins/datasource-zabbix/directives.js rename to src/datasource-zabbix/directives.js diff --git a/plugins/datasource-zabbix/helperFunctions.js b/src/datasource-zabbix/helperFunctions.js similarity index 100% rename from plugins/datasource-zabbix/helperFunctions.js rename to src/datasource-zabbix/helperFunctions.js diff --git a/plugins/datasource-zabbix/metricFunctionEditor.js b/src/datasource-zabbix/metricFunctionEditor.js similarity index 100% rename from plugins/datasource-zabbix/metricFunctionEditor.js rename to src/datasource-zabbix/metricFunctionEditor.js diff --git a/plugins/datasource-zabbix/metricFunctions.js b/src/datasource-zabbix/metricFunctions.js similarity index 100% rename from plugins/datasource-zabbix/metricFunctions.js rename to src/datasource-zabbix/metricFunctions.js diff --git a/src/datasource-zabbix/module.js b/src/datasource-zabbix/module.js new file mode 100644 index 0000000..d947214 --- /dev/null +++ b/src/datasource-zabbix/module.js @@ -0,0 +1,19 @@ +import {ZabbixAPIDatasource} from './datasource'; +import {ZabbixQueryCtrl} from './queryCtrl'; + +class ZabbixConfigCtrl {} +ZabbixConfigCtrl.templateUrl = 'partials/config.html'; + +class ZabbixQueryOptionsCtrl {} +ZabbixQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; + +class ZabbixAnnotationsQueryCtrl {} +ZabbixAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html'; + +export { + ZabbixAPIDatasource as Datasource, + ZabbixQueryCtrl as QueryCtrl, + ZabbixConfigCtrl as ConfigCtrl, + ZabbixQueryOptionsCtrl as QueryOptionsCtrl, + ZabbixAnnotationsQueryCtrl as AnnotationsQueryCtrl +}; diff --git a/plugins/datasource-zabbix/partials/annotations.editor.html b/src/datasource-zabbix/partials/annotations.editor.html similarity index 100% rename from plugins/datasource-zabbix/partials/annotations.editor.html rename to src/datasource-zabbix/partials/annotations.editor.html diff --git a/plugins/datasource-zabbix/partials/config.html b/src/datasource-zabbix/partials/config.html similarity index 100% rename from plugins/datasource-zabbix/partials/config.html rename to src/datasource-zabbix/partials/config.html diff --git a/plugins/datasource-zabbix/partials/query.editor.html b/src/datasource-zabbix/partials/query.editor.html similarity index 100% rename from plugins/datasource-zabbix/partials/query.editor.html rename to src/datasource-zabbix/partials/query.editor.html diff --git a/plugins/datasource-zabbix/partials/query.options.html b/src/datasource-zabbix/partials/query.options.html similarity index 100% rename from plugins/datasource-zabbix/partials/query.options.html rename to src/datasource-zabbix/partials/query.options.html diff --git a/plugins/datasource-zabbix/plugin.json b/src/datasource-zabbix/plugin.json similarity index 100% rename from plugins/datasource-zabbix/plugin.json rename to src/datasource-zabbix/plugin.json diff --git a/plugins/datasource-zabbix/queryCtrl.js b/src/datasource-zabbix/queryCtrl.js similarity index 100% rename from plugins/datasource-zabbix/queryCtrl.js rename to src/datasource-zabbix/queryCtrl.js diff --git a/plugins/datasource-zabbix/queryProcessor.js b/src/datasource-zabbix/queryProcessor.js similarity index 100% rename from plugins/datasource-zabbix/queryProcessor.js rename to src/datasource-zabbix/queryProcessor.js diff --git a/plugins/datasource-zabbix/utils.js b/src/datasource-zabbix/utils.js similarity index 100% rename from plugins/datasource-zabbix/utils.js rename to src/datasource-zabbix/utils.js diff --git a/plugins/datasource-zabbix/zabbixAPI.js b/src/datasource-zabbix/zabbixAPI.js similarity index 100% rename from plugins/datasource-zabbix/zabbixAPI.js rename to src/datasource-zabbix/zabbixAPI.js diff --git a/plugins/datasource-zabbix/zabbixAPIService.js b/src/datasource-zabbix/zabbixAPIService.js similarity index 100% rename from plugins/datasource-zabbix/zabbixAPIService.js rename to src/datasource-zabbix/zabbixAPIService.js diff --git a/plugins/datasource-zabbix/zabbixCache.js b/src/datasource-zabbix/zabbixCache.js similarity index 100% rename from plugins/datasource-zabbix/zabbixCache.js rename to src/datasource-zabbix/zabbixCache.js diff --git a/plugins/panel-triggers/editor.html b/src/panel-triggers/editor.html similarity index 100% rename from plugins/panel-triggers/editor.html rename to src/panel-triggers/editor.html diff --git a/plugins/panel-triggers/editor.js b/src/panel-triggers/editor.js similarity index 100% rename from plugins/panel-triggers/editor.js rename to src/panel-triggers/editor.js diff --git a/plugins/panel-triggers/module.html b/src/panel-triggers/module.html similarity index 100% rename from plugins/panel-triggers/module.html rename to src/panel-triggers/module.html diff --git a/plugins/panel-triggers/module.js b/src/panel-triggers/module.js similarity index 100% rename from plugins/panel-triggers/module.js rename to src/panel-triggers/module.js diff --git a/plugins/panel-triggers/plugin.json b/src/panel-triggers/plugin.json similarity index 100% rename from plugins/panel-triggers/plugin.json rename to src/panel-triggers/plugin.json diff --git a/plugins/panel-triggers/trigger.colorpicker.html b/src/panel-triggers/trigger.colorpicker.html similarity index 100% rename from plugins/panel-triggers/trigger.colorpicker.html rename to src/panel-triggers/trigger.colorpicker.html From 775c2ddac1e6a1fdd0632c3853df6be8e8e1f8fd Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 15 Mar 2016 20:14:23 +0300 Subject: [PATCH 02/24] queryCtrl and utils module refactor. Suppress "use strict" warnings in modules. --- .jshintrc | 1 + Gruntfile.js | 6 +- src/datasource-zabbix/datasource.js | 22 +- src/datasource-zabbix/queryCtrl.js | 433 ++++++++++++++-------------- src/datasource-zabbix/utils.js | 132 ++++----- 5 files changed, 292 insertions(+), 302 deletions(-) diff --git a/.jshintrc b/.jshintrc index 36c9c9f..fe86382 100644 --- a/.jshintrc +++ b/.jshintrc @@ -5,6 +5,7 @@ "curly": true, "eqnull": true, "strict": true, + "module": true, "devel": true, "eqeqeq": true, "forin": false, diff --git a/Gruntfile.js b/Gruntfile.js index 2c8f35a..9823176 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -17,6 +17,8 @@ module.exports = function(grunt) { '**/*', '!**/datasource.js', '!**/module.js', + '!**/queryCtrl.js', + '!**/utils.js', '!**/*.scss' ], dest: 'dist/src' @@ -47,8 +49,10 @@ module.exports = function(grunt) { cwd: 'src', expand: true, src: [ - '**/**/datasource.js', '**/**/module.js', + '**/**/datasource.js', + '**/**/queryCtrl.js', + '**/**/utils.js', ], dest: 'dist/src', ext:'.js' diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index f9699f3..24e755d 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -1,7 +1,7 @@ //import angular from 'angular'; import _ from 'lodash'; -import {parse as dateMathParse} from 'app/core/utils/datemath'; -import Utils from './utils'; +import * as dateMath from 'app/core/utils/datemath'; +import * as utils from './utils'; import metricFunctions from './metricFunctions'; import {zabbixHelperSrv} from './helperFunctions'; import {ZabbixAPI} from './zabbixAPI'; @@ -29,7 +29,7 @@ export class ZabbixAPIDatasource { // Set cache update interval var ttl = instanceSettings.jsonData.cacheTTL || '1h'; - this.cacheTTL = Utils.parseInterval(ttl); + this.cacheTTL = utils.parseInterval(ttl); // Initialize Zabbix API this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials); @@ -103,9 +103,9 @@ export class ZabbixAPIDatasource { var self = this; // get from & to in seconds - var from = Math.ceil(dateMathParse(options.range.from) / 1000); - var to = Math.ceil(dateMathParse(options.range.to) / 1000); - var useTrendsFrom = Math.ceil(dateMathParse('now-' + this.trendsFrom) / 1000); + var from = Math.ceil(dateMath.parse(options.range.from) / 1000); + var to = Math.ceil(dateMath.parse(options.range.to) / 1000); + var useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000); // Create request for each target var promises = _.map(options.targets, function(target) { @@ -330,8 +330,8 @@ export class ZabbixAPIDatasource { ///////////////// annotationQuery(options) { - var from = Math.ceil(dateMathParse(options.rangeRaw.from) / 1000); - var to = Math.ceil(dateMathParse(options.rangeRaw.to) / 1000); + var from = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000); + var to = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000); var annotation = options.annotation; var self = this; var showEvents = annotation.showOkEvents ? [0, 1] : 1; @@ -347,9 +347,9 @@ export class ZabbixAPIDatasource { .then(function(triggers) { // Filter triggers by description - if (Utils.isRegex(annotation.trigger)) { + if (utils.isRegex(annotation.trigger)) { triggers = _.filter(triggers, function(trigger) { - return Utils.buildRegex(annotation.trigger).test(trigger.description); + return utils.buildRegex(annotation.trigger).test(trigger.description); }); } else if (annotation.trigger) { triggers = _.filter(triggers, function(trigger) { @@ -393,7 +393,7 @@ export class ZabbixAPIDatasource { // Show event type (OK or Problem) title += Number(e.value) ? 'Problem' : 'OK'; - var formatted_acknowledges = Utils.formatAcknowledges(e.acknowledges); + var formatted_acknowledges = utils.formatAcknowledges(e.acknowledges); return { annotation: annotation, time: e.clock * 1000, diff --git a/src/datasource-zabbix/queryCtrl.js b/src/datasource-zabbix/queryCtrl.js index 6f927c3..935fc7a 100644 --- a/src/datasource-zabbix/queryCtrl.js +++ b/src/datasource-zabbix/queryCtrl.js @@ -1,242 +1,237 @@ -define([ +/*define([ 'app/plugins/sdk', 'angular', 'lodash', './metricFunctions', './utils' -], -function (sdk, angular, _, metricFunctions, utils) { - 'use strict'; +],*/ - var ZabbixQueryCtrl = (function(_super) { +import {QueryCtrl} from 'app/plugins/sdk'; +import _ from 'lodash'; +import * as utils from './utils'; +import metricFunctions from './metricFunctions'; - // ZabbixQueryCtrl constructor - function ZabbixQueryCtrl($scope, $injector, $sce, $q, templateSrv) { +export class ZabbixQueryCtrl extends QueryCtrl { - // Call superclass constructor - _super.call(this, $scope, $injector); + // ZabbixQueryCtrl constructor + constructor($scope, $injector, $sce, $q, templateSrv) { - this.editorModes = { - 0: 'num', - 1: 'itservice', - 2: 'text' + // Call superclass constructor + super($scope, $injector); + + this.editorModes = { + 0: 'num', + 1: 'itservice', + 2: 'text' + }; + + // Map functions for bs-typeahead + this.getGroupNames = _.partial(getMetricNames, this, 'groupList'); + this.getHostNames = _.partial(getMetricNames, this, 'filteredHosts'); + this.getApplicationNames = _.partial(getMetricNames, this, 'filteredApplications'); + this.getItemNames = _.partial(getMetricNames, this, 'filteredItems'); + + this.init = function() { + + this.templateSrv = templateSrv; + var target = this.target; + + var scopeDefaults = { + metric: {} }; + _.defaults(this, scopeDefaults); - // Map functions for bs-typeahead - this.getGroupNames = _.partial(getMetricNames, this, 'groupList'); - this.getHostNames = _.partial(getMetricNames, this, 'filteredHosts'); - this.getApplicationNames = _.partial(getMetricNames, this, 'filteredApplications'); - this.getItemNames = _.partial(getMetricNames, this, 'filteredItems'); - - this.init = function() { - - this.templateSrv = templateSrv; - var target = this.target; - - var scopeDefaults = { - metric: {} - }; - _.defaults(this, scopeDefaults); - - // Load default values - var targetDefaults = { - mode: 0, - group: { filter: "" }, - host: { filter: "" }, - application: { filter: "" }, - item: { filter: "" }, - functions: [], - }; - _.defaults(target, targetDefaults); - - // Create function instances from saved JSON - target.functions = _.map(target.functions, function(func) { - return metricFunctions.createFuncInstance(func.def, func.params); - }); - - if (target.mode === 0 || - target.mode === 2) { - - this.downsampleFunctionList = [ - {name: "avg", value: "avg"}, - {name: "min", value: "min"}, - {name: "max", value: "max"} - ]; - - // Set avg by default - if (!target.downsampleFunction) { - target.downsampleFunction = this.downsampleFunctionList[0]; - } - - this.initFilters(); - } - else if (target.mode === 1) { - 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"} - ]; - this.itserviceList = [{name: "test"}]; - this.updateITServiceList(); - } + // Load default values + var targetDefaults = { + mode: 0, + group: { filter: "" }, + host: { filter: "" }, + application: { filter: "" }, + item: { filter: "" }, + functions: [], }; + _.defaults(target, targetDefaults); - this.init(); - } - - ZabbixQueryCtrl.templateUrl = 'partials/query.editor.html'; - - ZabbixQueryCtrl.prototype = Object.create(_super.prototype); - ZabbixQueryCtrl.prototype.constructor = ZabbixQueryCtrl; - - var p = ZabbixQueryCtrl.prototype; - - p.initFilters = function () { - this.filterGroups(); - this.filterHosts(); - this.filterApplications(); - this.filterItems(); - }; - - p.filterHosts = function () { - var self = this; - var groupFilter = this.templateSrv.replace(this.target.group.filter); - this.datasource.queryProcessor.filterHosts(groupFilter).then(function(hosts) { - self.metric.filteredHosts = hosts; + // Create function instances from saved JSON + target.functions = _.map(target.functions, function(func) { + return metricFunctions.createFuncInstance(func.def, func.params); }); - }; - p.filterGroups = function() { - var self = this; - this.datasource.queryProcessor.filterGroups().then(function(groups) { - self.metric.groupList = groups; - }); - }; + if (target.mode === 0 || + target.mode === 2) { - p.filterApplications = function () { - var self = this; - var groupFilter = this.templateSrv.replace(this.target.group.filter); - var hostFilter = this.templateSrv.replace(this.target.host.filter); - this.datasource.queryProcessor.filterApplications(groupFilter, hostFilter) - .then(function(apps) { - self.metric.filteredApplications = apps; - }); - }; + this.downsampleFunctionList = [ + {name: "avg", value: "avg"}, + {name: "min", value: "min"}, + {name: "max", value: "max"} + ]; - p.filterItems = function () { - var self = this; - var item_type = this.editorModes[this.target.mode]; - var groupFilter = this.templateSrv.replace(this.target.group.filter); - var hostFilter = this.templateSrv.replace(this.target.host.filter); - var appFilter = this.templateSrv.replace(this.target.application.filter); - this.datasource.queryProcessor.filterItems(groupFilter, hostFilter, appFilter, - item_type, this.target.showDisabledItems).then(function(items) { - self.metric.filteredItems = items; - }); - }; + // Set avg by default + if (!target.downsampleFunction) { + target.downsampleFunction = this.downsampleFunctionList[0]; + } - p.onTargetPartChange = function (targetPart) { - var regexStyle = {'color': '#CCA300'}; - targetPart.isRegex = utils.isRegex(targetPart.filter); - targetPart.style = targetPart.isRegex ? regexStyle : {}; - }; - - p.onTargetBlur = function() { - this.initFilters(); - this.parseTarget(); - this.panelCtrl.refresh(); - }; - - p.parseTarget = function() { - // Parse target - }; - - // Validate target and set validation info - p.validateTarget = function () {}; - - p.targetChanged = function() { - this.panelCtrl.refresh(); - }; - - p.addFunction = function(funcDef) { - var newFunc = metricFunctions.createFuncInstance(funcDef); - newFunc.added = true; - this.target.functions.push(newFunc); - - this.moveAliasFuncLast(); - - if (newFunc.params.length && newFunc.added || - newFunc.def.params.length === 0) { - this.targetChanged(); + this.initFilters(); + } + else if (target.mode === 1) { + 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"} + ]; + this.itserviceList = [{name: "test"}]; + this.updateITServiceList(); } }; - p.removeFunction = function(func) { - this.target.functions = _.without(this.target.functions, func); - this.targetChanged(); - }; - - p.moveAliasFuncLast = function() { - var aliasFunc = _.find(this.target.functions, function(func) { - return func.def.name === 'alias' || - func.def.name === 'aliasByNode' || - func.def.name === 'aliasByMetric'; - }); - - if (aliasFunc) { - this.target.functions = _.without(this.target.functions, aliasFunc); - this.target.functions.push(aliasFunc); - } - }; - - /** - * Switch query editor to specified mode. - * Modes: - * 0 - items - * 1 - IT services - * 2 - Text metrics - */ - p.switchEditorMode = function (mode) { - this.target.mode = mode; - this.init(); - }; - - ///////////////// - // IT Services // - ///////////////// - - /** - * Update list of IT services - */ - p.updateITServiceList = function () { - var self = this; - this.datasource.zabbixAPI.getITService().then(function (iteservices) { - self.itserviceList = []; - self.itserviceList = self.itserviceList.concat(iteservices); - }); - }; - - /** - * Call when IT service is selected. - */ - p.selectITService = function () { - if (!_.isEqual(this.oldTarget, this.target) && _.isEmpty(this.target.errors)) { - this.oldTarget = angular.copy(this.target); - this.panelCtrl.refresh(); - } - }; - - return ZabbixQueryCtrl; - - })(sdk.QueryCtrl); - - return ZabbixQueryCtrl; - - // Get list of metric names for bs-typeahead directive - function getMetricNames(scope, metricList) { - return _.uniq(_.map(scope.metric[metricList], 'name')); + this.init(); } -}); \ No newline at end of file + initFilters() { + this.filterGroups(); + this.filterHosts(); + this.filterApplications(); + this.filterItems(); + } + + filterHosts() { + var self = this; + var groupFilter = this.templateSrv.replace(this.target.group.filter); + this.datasource.queryProcessor.filterHosts(groupFilter).then(function(hosts) { + self.metric.filteredHosts = hosts; + }); + } + + filterGroups() { + var self = this; + this.datasource.queryProcessor.filterGroups().then(function(groups) { + self.metric.groupList = groups; + }); + } + + filterApplications() { + var self = this; + var groupFilter = this.templateSrv.replace(this.target.group.filter); + var hostFilter = this.templateSrv.replace(this.target.host.filter); + this.datasource.queryProcessor.filterApplications(groupFilter, hostFilter) + .then(function(apps) { + self.metric.filteredApplications = apps; + }); + } + + filterItems() { + var self = this; + var item_type = this.editorModes[this.target.mode]; + var groupFilter = this.templateSrv.replace(this.target.group.filter); + var hostFilter = this.templateSrv.replace(this.target.host.filter); + var appFilter = this.templateSrv.replace(this.target.application.filter); + this.datasource.queryProcessor.filterItems(groupFilter, hostFilter, appFilter, + item_type, this.target.showDisabledItems).then(function(items) { + self.metric.filteredItems = items; + }); + } + + onTargetPartChange(targetPart) { + var regexStyle = {'color': '#CCA300'}; + targetPart.isRegex = utils.isRegex(targetPart.filter); + targetPart.style = targetPart.isRegex ? regexStyle : {}; + } + + onTargetBlur() { + this.initFilters(); + this.parseTarget(); + this.panelCtrl.refresh(); + } + + parseTarget() { + // Parse target + } + + // Validate target and set validation info + validateTarget() { + // validate + } + + targetChanged() { + this.panelCtrl.refresh(); + } + + addFunction(funcDef) { + var newFunc = metricFunctions.createFuncInstance(funcDef); + newFunc.added = true; + this.target.functions.push(newFunc); + + this.moveAliasFuncLast(); + + if (newFunc.params.length && newFunc.added || + newFunc.def.params.length === 0) { + this.targetChanged(); + } + } + + removeFunction(func) { + this.target.functions = _.without(this.target.functions, func); + this.targetChanged(); + } + + moveAliasFuncLast() { + var aliasFunc = _.find(this.target.functions, function(func) { + return func.def.name === 'alias' || + func.def.name === 'aliasByNode' || + func.def.name === 'aliasByMetric'; + }); + + if (aliasFunc) { + this.target.functions = _.without(this.target.functions, aliasFunc); + this.target.functions.push(aliasFunc); + } + } + + /** + * Switch query editor to specified mode. + * Modes: + * 0 - items + * 1 - IT services + * 2 - Text metrics + */ + switchEditorMode(mode) { + this.target.mode = mode; + this.init(); + } + + ///////////////// + // IT Services // + ///////////////// + + /** + * Update list of IT services + */ + updateITServiceList() { + var self = this; + this.datasource.zabbixAPI.getITService().then(function (iteservices) { + self.itserviceList = []; + self.itserviceList = self.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(); + } + } + +} + +// Set templateUrl as static property +ZabbixQueryCtrl.templateUrl = 'partials/query.editor.html'; + +// Get list of metric names for bs-typeahead directive +function getMetricNames(scope, metricList) { + return _.uniq(_.map(scope.metric[metricList], 'name')); +} \ No newline at end of file diff --git a/src/datasource-zabbix/utils.js b/src/datasource-zabbix/utils.js index c92c616..ff36135 100644 --- a/src/datasource-zabbix/utils.js +++ b/src/datasource-zabbix/utils.js @@ -1,77 +1,67 @@ -define([ - 'lodash', - 'moment' -], -function (_, moment) { - 'use strict'; +import _ from 'lodash'; +import moment from 'moment'; - function Utils() { - /** - * Expand Zabbix item name - * - * @param {string} name item name, ie "CPU $2 time" - * @param {string} key item key, ie system.cpu.util[,system,avg1] - * @return {string} expanded name, ie "CPU system time" - */ - this.expandItemName = function(name, key) { +/** + * Expand Zabbix item name + * + * @param {string} name item name, ie "CPU $2 time" + * @param {string} key item key, ie system.cpu.util[,system,avg1] + * @return {string} expanded name, ie "CPU system time" + */ +export function expandItemName(name, key) { - // extract params from key: - // "system.cpu.util[,system,avg1]" --> ["", "system", "avg1"] - var key_params = key.substring(key.indexOf('[') + 1, key.lastIndexOf(']')).split(','); - - // replace item parameters - for (var i = key_params.length; i >= 1; i--) { - name = name.replace('$' + i, key_params[i - 1]); - } - return name; - }; - - // Pattern for testing regex - var regexPattern = /^\/(.*)\/([gmi]*)$/m; - - this.isRegex = function (str) { - return regexPattern.test(str); - }; - - this.buildRegex = function (str) { - var matches = str.match(regexPattern); - var pattern = matches[1]; - var flags = matches[2] !== "" ? matches[2] : undefined; - return new RegExp(pattern, flags); - }; - - this.parseInterval = function(interval) { - var intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)/g; - var momentInterval = intervalPattern.exec(interval); - return moment.duration(Number(momentInterval[1]), momentInterval[2]).valueOf(); - }; - - /** - * Format acknowledges. - * - * @param {array} acknowledges array of Zabbix acknowledge objects - * @return {string} HTML-formatted table - */ - this.formatAcknowledges = function(acknowledges) { - if (acknowledges.length) { - var formatted_acknowledges = '

Acknowledges:
' - + ''; - _.each(_.map(acknowledges, function (ack) { - var timestamp = moment.unix(ack.clock); - return ''; - }), function (ack) { - formatted_acknowledges = formatted_acknowledges.concat(ack); - }); - formatted_acknowledges = formatted_acknowledges.concat('
TimeUserComments
' + timestamp.format("DD MMM YYYY HH:mm:ss") + '' + ack.alias - + ' (' + ack.name + ' ' + ack.surname + ')' + '' + ack.message + '
'); - return formatted_acknowledges; - } else { - return ''; - } - }; + // extract params from key: + // "system.cpu.util[,system,avg1]" --> ["", "system", "avg1"] + var key_params = key.substring(key.indexOf('[') + 1, key.lastIndexOf(']')).split(','); + // replace item parameters + for (var i = key_params.length; i >= 1; i--) { + name = name.replace('$' + i, key_params[i - 1]); } + return name; +} - return new Utils(); -}); \ No newline at end of file +// Pattern for testing regex +var regexPattern = /^\/(.*)\/([gmi]*)$/m; + +export function isRegex(str) { + return regexPattern.test(str); +} + +export function buildRegex(str) { + var matches = str.match(regexPattern); + var pattern = matches[1]; + var flags = matches[2] !== "" ? matches[2] : undefined; + return new RegExp(pattern, flags); +} + +export function parseInterval(interval) { + var intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)/g; + var momentInterval = intervalPattern.exec(interval); + return moment.duration(Number(momentInterval[1]), momentInterval[2]).valueOf(); +} + +/** + * Format acknowledges. + * + * @param {array} acknowledges array of Zabbix acknowledge objects + * @return {string} HTML-formatted table + */ +export function formatAcknowledges(acknowledges) { + if (acknowledges.length) { + var formatted_acknowledges = '

Acknowledges:
' + + ''; + _.each(_.map(acknowledges, function (ack) { + var timestamp = moment.unix(ack.clock); + return ''; + }), function (ack) { + formatted_acknowledges = formatted_acknowledges.concat(ack); + }); + formatted_acknowledges = formatted_acknowledges.concat('
TimeUserComments
' + timestamp.format("DD MMM YYYY HH:mm:ss") + '' + ack.alias + + ' (' + ack.name + ' ' + ack.surname + ')' + '' + ack.message + '
'); + return formatted_acknowledges; + } else { + return ''; + } +} From de8a1ff621594d96a801fdf19ca3ccbe0f034acd Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 15 Mar 2016 22:54:02 +0300 Subject: [PATCH 03/24] zabbixAPI (-> zabbixAPIService) and zabbixAPIService (-> zabbixAPICoreService) refactor. --- Gruntfile.js | 4 + src/datasource-zabbix/datasource.js | 8 +- src/datasource-zabbix/zabbixAPI.js | 370 --------------- src/datasource-zabbix/zabbixAPICoreService.js | 104 +++++ src/datasource-zabbix/zabbixAPIService.js | 432 ++++++++++++++---- 5 files changed, 463 insertions(+), 455 deletions(-) delete mode 100644 src/datasource-zabbix/zabbixAPI.js create mode 100644 src/datasource-zabbix/zabbixAPICoreService.js diff --git a/Gruntfile.js b/Gruntfile.js index 9823176..4b85222 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,6 +19,8 @@ module.exports = function(grunt) { '!**/module.js', '!**/queryCtrl.js', '!**/utils.js', + '!**/zabbixAPICoreService.js', + '!**/zabbixAPIService.js', '!**/*.scss' ], dest: 'dist/src' @@ -53,6 +55,8 @@ module.exports = function(grunt) { '**/**/datasource.js', '**/**/queryCtrl.js', '**/**/utils.js', + '**/**/zabbixAPICoreService.js', + '**/**/zabbixAPIService.js', ], dest: 'dist/src', ext:'.js' diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 24e755d..77eddba 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -4,14 +4,15 @@ import * as dateMath from 'app/core/utils/datemath'; import * as utils from './utils'; import metricFunctions from './metricFunctions'; import {zabbixHelperSrv} from './helperFunctions'; -import {ZabbixAPI} from './zabbixAPI'; +import './zabbixAPIService'; import {ZabbixCachingProxy} from './zabbixCache'; import {QueryProcessor} from './queryProcessor'; import {DataProcessingService} from './dataProcessingService'; export class ZabbixAPIDatasource { - constructor(instanceSettings, $q, templateSrv, alertSrv, ZabbixAPI, ZabbixCachingProxy, QueryProcessor, zabbixHelperSrv, DataProcessingService) { + /** @ngInject */ + constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor, zabbixHelperSrv, DataProcessingService) { // General data source settings this.name = instanceSettings.name; @@ -32,6 +33,7 @@ export class ZabbixAPIDatasource { this.cacheTTL = utils.parseInterval(ttl); // Initialize Zabbix API + var ZabbixAPI = zabbixAPIService; this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials); // Initialize cache service @@ -427,4 +429,4 @@ function formatMetric(metricObj) { text: metricObj.name, expandable: false }; -} \ No newline at end of file +} diff --git a/src/datasource-zabbix/zabbixAPI.js b/src/datasource-zabbix/zabbixAPI.js deleted file mode 100644 index eac7589..0000000 --- a/src/datasource-zabbix/zabbixAPI.js +++ /dev/null @@ -1,370 +0,0 @@ -define([ - 'angular', - 'lodash', - './zabbixAPIService' - ], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.services'); - - /** - * Zabbix API Wrapper. - * Creates Zabbix API instance with given parameters (url, credentials and other). - * Wraps API calls and provides high-level methods. - */ - module.factory('ZabbixAPI', function($q, backendSrv, alertSrv, ZabbixAPIService) { - - // Initialize Zabbix API. - function ZabbixAPI(api_url, username, password, basicAuth, withCredentials) { - this.url = api_url; - this.username = username; - this.password = password; - this.auth = ""; - - this.requestOptions = { - basicAuth: basicAuth, - withCredentials: withCredentials - }; - - this.loginPromise = null; - } - - var p = ZabbixAPI.prototype; - - ////////////////// - // Core methods // - ////////////////// - - p.request = function(method, params) { - var self = this; - - return ZabbixAPIService.request(this.url, method, params, this.requestOptions, this.auth) - .then(function(result) { - return result; - }, - // Handle API errors - function(error) { - if (isNotAuthorized(error.data)) { - return self.loginOnce().then( - function() { - return self.request(method, params); - }, - // Handle user.login method errors - function(error) { - self.alertAPIError(error.data); - }); - } - }); - }; - - p.alertAPIError = function(message) { - alertSrv.set( - "Zabbix API Error", - message, - 'error' - ); - }; - - function isNotAuthorized(message) { - return ( - message === "Session terminated, re-login, please." || - message === "Not authorised." || - message === "Not authorized." - ); - } - - /** - * When API unauthenticated or auth token expired each request produce login() - * call. But auth token is common to all requests. This function wraps login() method - * and call it once. If login() already called just wait for it (return its promise). - * @return login promise - */ - p.loginOnce = function() { - var self = this; - var deferred = $q.defer(); - if (!self.loginPromise) { - self.loginPromise = deferred.promise; - self.login().then( - function(auth) { - self.loginPromise = null; - self.auth = auth; - deferred.resolve(auth); - }, - function(error) { - self.loginPromise = null; - deferred.reject(error); - } - ); - } else { - return self.loginPromise; - } - return deferred.promise; - }; - - /** - * Get authentication token. - */ - p.login = function() { - return ZabbixAPIService.login(this.url, this.username, this.password, this.requestOptions); - }; - - /** - * Get Zabbix API version - */ - p.getVersion = function() { - return ZabbixAPIService.getVersion(this.url, this.requestOptions); - }; - - ///////////////// - // API methods // - ///////////////// - - p.getGroups = function() { - var params = { - output: ['name'], - sortfield: 'name', - selectHosts: [] - }; - - return this.request('hostgroup.get', params); - }; - - p.getHosts = function() { - var params = { - output: ['name', 'host'], - sortfield: 'name', - selectGroups: [] - }; - - return this.request('host.get', params); - }; - - p.getApplications = function() { - var params = { - output: ['name'], - sortfield: 'name', - - // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) - selectHost: [], - selectHosts: [] - }; - - return this.request('application.get', params); - }; - - p.getItems = function() { - var params = { - output: [ - 'name', 'key_', - 'value_type', - 'hostid', - 'status', - 'state' - ], - sortfield: 'name', - selectApplications: [] - }; - - return this.request('item.get', params); - }; - - /** - * Get Hosts list with host's items. - * @return {[type]} [description] - */ - p.getHostsExtend = function() { - var params = { - output: ['name', 'host'], - sortfield: 'name', - selectGroups: [], - selectItems: [ - 'name', 'key_', - 'value_type', - 'hostid', - 'status', - 'state' - ] - }; - - return this.request('host.get', params); - }; - - p.getLastValue = function(itemid) { - var params = { - output: ['lastvalue'], - itemids: itemid - }; - return this.request('item.get', params).then(function(items) { - if (items.length) { - return items[0].lastvalue; - } else { - return null; - } - }); - }; - - /** - * Perform history query from Zabbix API - * - * @param {Array} items Array of Zabbix item objects - * @param {Number} time_from Time in seconds - * @param {Number} time_till Time in seconds - * @return {Array} Array of Zabbix history objects - */ - p.getHistory = function(items, time_from, time_till) { - var self = this; - - // Group items by value type - var grouped_items = _.groupBy(items, 'value_type'); - - // Perform request for each value type - return $q.all(_.map(grouped_items, function (items, value_type) { - var itemids = _.map(items, 'itemid'); - var params = { - output: 'extend', - history: value_type, - itemids: itemids, - sortfield: 'clock', - sortorder: 'ASC', - time_from: time_from - }; - - // Relative queries (e.g. last hour) don't include an end time - if (time_till) { - params.time_till = time_till; - } - - return self.request('history.get', params); - })).then(_.flatten); - }; - - /** - * Perform trends query from Zabbix API - * Use trends api extension from ZBXNEXT-1193 patch. - * - * @param {Array} items Array of Zabbix item objects - * @param {Number} time_from Time in seconds - * @param {Number} time_till Time in seconds - * @return {Array} Array of Zabbix trend objects - */ - p.getTrend_ZBXNEXT1193 = function(items, time_from, time_till) { - var self = this; - - // Group items by value type - var grouped_items = _.groupBy(items, 'value_type'); - - // Perform request for each value type - return $q.all(_.map(grouped_items, function (items, value_type) { - var itemids = _.map(items, 'itemid'); - var params = { - output: 'extend', - trend: value_type, - itemids: itemids, - sortfield: 'clock', - sortorder: 'ASC', - time_from: time_from - }; - - // Relative queries (e.g. last hour) don't include an end time - if (time_till) { - params.time_till = time_till; - } - - return self.request('trend.get', params); - })).then(_.flatten); - }; - - p.getTrend_30 = function(items, time_from, time_till, value_type) { - var self = this; - var itemids = _.map(items, 'itemid'); - - var params = { - output: ["itemid", - "clock", - value_type - ], - itemids: itemids, - time_from: time_from - }; - - // Relative queries (e.g. last hour) don't include an end time - if (time_till) { - params.time_till = time_till; - } - - return self.request('trend.get', params); - }; - - p.getTrend = p.getTrend_ZBXNEXT1193; - //p.getTrend = p.getTrend_30; - - p.getITService = function(/* optional */ serviceids) { - var params = { - output: 'extend', - serviceids: serviceids - }; - return this.request('service.get', params); - }; - - p.getSLA = function(serviceids, from, to) { - var params = { - serviceids: serviceids, - intervals: [{ - from: from, - to: to - }] - }; - return this.request('service.getsla', params); - }; - - p.getTriggers = function(groupids, hostids, applicationids, showEvents) { - var params = { - output: 'extend', - groupids: groupids, - hostids: hostids, - applicationids: applicationids, - expandDescription: true, - expandData: true, - monitored: true, - skipDependent: true, - //only_true: true, - filter: { - value: 1 - }, - selectGroups: ['name'], - selectHosts: ['name'], - selectItems: ['name', 'key_', 'lastvalue'], - selectLastEvent: 'extend' - }; - - if (showEvents) { - params.filter.value = showEvents; - } - - return this.request('trigger.get', params); - }; - - p.getAcknowledges = function(eventids) { - var params = { - output: 'extend', - eventids: eventids, - preservekeys: true, - select_acknowledges: 'extend', - sortfield: 'clock', - sortorder: 'DESC' - }; - - return this.request('event.get', params) - .then(function (events) { - return _.filter(events, function(event) { - return event.acknowledges.length; - }); - }); - }; - - return ZabbixAPI; - - }); - -}); diff --git a/src/datasource-zabbix/zabbixAPICoreService.js b/src/datasource-zabbix/zabbixAPICoreService.js new file mode 100644 index 0000000..a1623e6 --- /dev/null +++ b/src/datasource-zabbix/zabbixAPICoreService.js @@ -0,0 +1,104 @@ +/** + * General Zabbix API methods + */ + +import angular from 'angular'; + +class ZabbixAPICoreService { + + /** @ngInject */ + constructor($q, backendSrv) { + this.$q = $q; + this.backendSrv = backendSrv; + } + + /** + * Request data from Zabbix API + * @return {object} response.result + */ + request(api_url, method, params, options, auth) { + var deferred = this.$q.defer(); + var requestData = { + jsonrpc: '2.0', + method: method, + params: params, + id: 1 + }; + + if (auth === "") { + // Reject immediately if not authenticated + deferred.reject({data: "Not authorised."}); + return deferred.promise; + } else if (auth) { + // Set auth parameter only if it needed + requestData.auth = auth; + } + + var requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + url: api_url, + data: requestData + }; + + // Set request options for basic auth + if (options.basicAuth || options.withCredentials) { + requestOptions.withCredentials = true; + } + if (options.basicAuth) { + requestOptions.headers.Authorization = options.basicAuth; + } + + this.backendSrv.datasourceRequest(requestOptions).then(function (response) { + // General connection issues + if (!response.data) { + deferred.reject(response); + } + + // Handle Zabbix API errors + else if (response.data.error) { + deferred.reject(response.data.error); + } + + deferred.resolve(response.data.result); + }); + return deferred.promise; + } + + /** + * Get authentication token. + * @return {string} auth token + */ + login(api_url, username, password, options) { + var params = { + user: username, + password: password + }; + return this.request(api_url, 'user.login', params, options, null); + } + + /** + * Get Zabbix API version + * Matches the version of Zabbix starting from Zabbix 2.0.4 + */ + getVersion(api_url, options) { + return this.request(api_url, 'apiinfo.version', [], options); + } +} + +// Define zabbix API exception type +function ZabbixException(error) { + this.code = error.code; + this.errorType = error.message; + this.message = error.data; +} + +ZabbixException.prototype.toString = function() { + return this.errorType + ": " + this.message; +}; + +angular + .module('grafana.services') + .service('zabbixAPICoreService', ZabbixAPICoreService); diff --git a/src/datasource-zabbix/zabbixAPIService.js b/src/datasource-zabbix/zabbixAPIService.js index 03f7789..d2f89f7 100644 --- a/src/datasource-zabbix/zabbixAPIService.js +++ b/src/datasource-zabbix/zabbixAPIService.js @@ -1,103 +1,371 @@ -/** - * General Zabbix API methods - */ +import angular from 'angular'; +import _ from 'lodash'; +import './zabbixAPICoreService'; -define([ - 'angular', -], -function (angular) { - 'use strict'; +/** @ngInject */ +function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { - var module = angular.module('grafana.services'); + /** + * Zabbix API Wrapper. + * Creates Zabbix API instance with given parameters (url, credentials and other). + * Wraps API calls and provides high-level methods. + */ + class ZabbixAPI { - module.service('ZabbixAPIService', function($q, backendSrv) { + constructor(api_url, username, password, basicAuth, withCredentials) { + this.url = api_url; + this.username = username; + this.password = password; + this.auth = ""; + + this.requestOptions = { + basicAuth: basicAuth, + withCredentials: withCredentials + }; + + this.loginPromise = null; + + this.$q = $q; + this.alertSrv = alertSrv; + this.zabbixAPICore = zabbixAPICoreService; + + this.getTrend = this.getTrend_ZBXNEXT1193; + //getTrend = getTrend_30; + } + + ////////////////////////// + // Core method wrappers // + ////////////////////////// + + request(method, params) { + var self = this; + + return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth) + .then(function(result) { + return result; + }, + // Handle API errors + function(error) { + if (isNotAuthorized(error.data)) { + return self.loginOnce().then( + function() { + return self.request(method, params); + }, + // Handle user.login method errors + function(error) { + self.alertAPIError(error.data); + }); + } + }); + } + + alertAPIError(message) { + this.alertSrv.set( + "Zabbix API Error", + message, + 'error' + ); + } /** - * Request data from Zabbix API - * @return {object} response.result + * When API unauthenticated or auth token expired each request produce login() + * call. But auth token is common to all requests. This function wraps login() method + * and call it once. If login() already called just wait for it (return its promise). + * @return login promise */ - this.request = function(api_url, method, params, options, auth) { - var deferred = $q.defer(); - var requestData = { - jsonrpc: '2.0', - method: method, - params: params, - id: 1 - }; - - if (auth === "") { - // Reject immediately if not authenticated - deferred.reject({data: "Not authorised."}); - return deferred.promise; - } else if (auth) { - // Set auth parameter only if it needed - requestData.auth = auth; + loginOnce() { + var self = this; + var deferred = this.$q.defer(); + if (!self.loginPromise) { + self.loginPromise = deferred.promise; + self.login().then( + function(auth) { + self.loginPromise = null; + self.auth = auth; + deferred.resolve(auth); + }, + function(error) { + self.loginPromise = null; + deferred.reject(error); + } + ); + } else { + return self.loginPromise; } - - var requestOptions = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - url: api_url, - data: requestData - }; - - // Set request options for basic auth - if (options.basicAuth || options.withCredentials) { - requestOptions.withCredentials = true; - } - if (options.basicAuth) { - requestOptions.headers.Authorization = options.basicAuth; - } - - backendSrv.datasourceRequest(requestOptions).then(function (response) { - // General connection issues - if (!response.data) { - deferred.reject(response); - } - - // Handle Zabbix API errors - else if (response.data.error) { - deferred.reject(response.data.error); - } - - deferred.resolve(response.data.result); - }); return deferred.promise; - }; + } /** * Get authentication token. - * @return {string} auth token */ - this.login = function(api_url, username, password, options) { - var params = { - user: username, - password: password - }; - return this.request(api_url, 'user.login', params, options, null); - }; + login() { + return this.zabbixAPICore.login(this.url, this.username, this.password, this.requestOptions); + } /** * Get Zabbix API version - * Matches the version of Zabbix starting from Zabbix 2.0.4 */ - this.getVersion = function(api_url, options) { - return this.request(api_url, 'apiinfo.version', [], options); - }; + getVersion() { + return this.zabbixAPICore.getVersion(this.url, this.requestOptions); + } - }); + //////////////////////////////// + // Zabbix API method wrappers // + //////////////////////////////// + + getGroups() { + var params = { + output: ['name'], + sortfield: 'name', + selectHosts: [] + }; + + return this.request('hostgroup.get', params); + } + + getHosts() { + var params = { + output: ['name', 'host'], + sortfield: 'name', + selectGroups: [] + }; + + return this.request('host.get', params); + } + + getApplications() { + var params = { + output: ['name'], + sortfield: 'name', + + // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) + selectHost: [], + selectHosts: [] + }; + + return this.request('application.get', params); + } + + getItems() { + var params = { + output: [ + 'name', 'key_', + 'value_type', + 'hostid', + 'status', + 'state' + ], + sortfield: 'name', + selectApplications: [] + }; + + return this.request('item.get', params); + } + + /** + * Get Hosts list with host's items. + * @return {[type]} [description] + */ + getHostsExtend() { + var params = { + output: ['name', 'host'], + sortfield: 'name', + selectGroups: [], + selectItems: [ + 'name', 'key_', + 'value_type', + 'hostid', + 'status', + 'state' + ] + }; + + return this.request('host.get', params); + } + + getLastValue(itemid) { + var params = { + output: ['lastvalue'], + itemids: itemid + }; + return this.request('item.get', params).then(function(items) { + if (items.length) { + return items[0].lastvalue; + } else { + return null; + } + }); + } + + /** + * Perform history query from Zabbix API + * + * @param {Array} items Array of Zabbix item objects + * @param {Number} time_from Time in seconds + * @param {Number} time_till Time in seconds + * @return {Array} Array of Zabbix history objects + */ + getHistory(items, time_from, time_till) { + var self = this; + + // Group items by value type + var grouped_items = _.groupBy(items, 'value_type'); + + // Perform request for each value type + return this.$q.all(_.map(grouped_items, function (items, value_type) { + var itemids = _.map(items, 'itemid'); + var params = { + output: 'extend', + history: value_type, + itemids: itemids, + sortfield: 'clock', + sortorder: 'ASC', + time_from: time_from + }; + + // Relative queries (e.g. last hour) don't include an end time + if (time_till) { + params.time_till = time_till; + } + + return self.request('history.get', params); + })).then(_.flatten); + } + + /** + * Perform trends query from Zabbix API + * Use trends api extension from ZBXNEXT-1193 patch. + * + * @param {Array} items Array of Zabbix item objects + * @param {Number} time_from Time in seconds + * @param {Number} time_till Time in seconds + * @return {Array} Array of Zabbix trend objects + */ + getTrend_ZBXNEXT1193(items, time_from, time_till) { + var self = this; + + // Group items by value type + var grouped_items = _.groupBy(items, 'value_type'); + + // Perform request for each value type + return this.$q.all(_.map(grouped_items, function (items, value_type) { + var itemids = _.map(items, 'itemid'); + var params = { + output: 'extend', + trend: value_type, + itemids: itemids, + sortfield: 'clock', + sortorder: 'ASC', + time_from: time_from + }; + + // Relative queries (e.g. last hour) don't include an end time + if (time_till) { + params.time_till = time_till; + } + + return self.request('trend.get', params); + })).then(_.flatten); + } + + getTrend_30(items, time_from, time_till, value_type) { + var self = this; + var itemids = _.map(items, 'itemid'); + + var params = { + output: ["itemid", + "clock", + value_type + ], + itemids: itemids, + time_from: time_from + }; + + // Relative queries (e.g. last hour) don't include an end time + if (time_till) { + params.time_till = time_till; + } + + return self.request('trend.get', params); + } + + getITService(/* optional */ serviceids) { + var params = { + output: 'extend', + serviceids: serviceids + }; + return this.request('service.get', params); + } + + getSLA(serviceids, from, to) { + var params = { + serviceids: serviceids, + intervals: [{ + from: from, + to: to + }] + }; + return this.request('service.getsla', params); + } + + getTriggers(groupids, hostids, applicationids, showEvents) { + var params = { + output: 'extend', + groupids: groupids, + hostids: hostids, + applicationids: applicationids, + expandDescription: true, + expandData: true, + monitored: true, + skipDependent: true, + //only_true: true, + filter: { + value: 1 + }, + selectGroups: ['name'], + selectHosts: ['name'], + selectItems: ['name', 'key_', 'lastvalue'], + selectLastEvent: 'extend' + }; + + if (showEvents) { + params.filter.value = showEvents; + } + + return this.request('trigger.get', params); + } + + getAcknowledges(eventids) { + var params = { + output: 'extend', + eventids: eventids, + preservekeys: true, + select_acknowledges: 'extend', + sortfield: 'clock', + sortorder: 'DESC' + }; + + return this.request('event.get', params) + .then(function (events) { + return _.filter(events, function(event) { + return event.acknowledges.length; + }); + }); + } - // Define zabbix API exception type - function ZabbixException(error) { - this.code = error.code; - this.errorType = error.message; - this.message = error.data; } - ZabbixException.prototype.toString = function() { - return this.errorType + ": " + this.message; - }; + return ZabbixAPI; +} -}); \ No newline at end of file +function isNotAuthorized(message) { + return ( + message === "Session terminated, re-login, please." || + message === "Not authorised." || + message === "Not authorized." + ); +} + +angular + .module('grafana.services') + .factory('zabbixAPIService', ZabbixAPIService); From def3f9115d2f2cfdfbd5354b4c1a249917a69d06 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 16 Mar 2016 21:00:11 +0300 Subject: [PATCH 04/24] Removed unused files. --- src/datasource-zabbix/directives.js | 21 -- src/datasource-zabbix/helperFunctions.js | 311 ----------------------- 2 files changed, 332 deletions(-) delete mode 100644 src/datasource-zabbix/directives.js delete mode 100644 src/datasource-zabbix/helperFunctions.js diff --git a/src/datasource-zabbix/directives.js b/src/datasource-zabbix/directives.js deleted file mode 100644 index 8d274f5..0000000 --- a/src/datasource-zabbix/directives.js +++ /dev/null @@ -1,21 +0,0 @@ -define([ - 'angular' - ], - function (angular) { - 'use strict'; - - var module = angular.module('grafana.directives'); - - module.directive('metricQueryEditorZabbix', function() { - return {controller: 'ZabbixAPIQueryCtrl', templateUrl: 'public/plugins/zabbix/partials/query.editor.html'}; - }); - - module.directive('metricQueryOptionsZabbix', function() { - return {templateUrl: 'public/plugins/zabbix/partials/query.options.html'}; - }); - - module.directive('annotationsQueryEditorZabbix', function() { - return {templateUrl: 'public/plugins/zabbix/partials/annotations.editor.html'}; - }); - - }); diff --git a/src/datasource-zabbix/helperFunctions.js b/src/datasource-zabbix/helperFunctions.js deleted file mode 100644 index d844d92..0000000 --- a/src/datasource-zabbix/helperFunctions.js +++ /dev/null @@ -1,311 +0,0 @@ -define([ - 'angular', - 'lodash' -], -function (angular, _) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('zabbixHelperSrv', function($q) { - var self = this; - - /** - * Convert Zabbix API history.get response to Grafana format - * - * @param {Array} items Array of Zabbix Items - * @param alias - * @param scale - * @param {Array} history Array of Zabbix History - * - * @return {Array} Array of timeseries in Grafana format - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * } - */ - this.handleHistoryResponse = function(items, alias, scale, history) { - /** - * Response should be in the format: - * data: [ - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * }, - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * }, - * ] - */ - - // Group items and history by itemid - var indexed_items = _.indexBy(items, 'itemid'); - var grouped_history = _.groupBy(history, 'itemid'); - - var self = this; - return $q.when(_.map(grouped_history, function (history, itemid) { - var item = indexed_items[itemid]; - return { - target: (item.host ? item.host + ': ' : '') - + (alias ? alias : self.expandItemName(item)), - datapoints: _.map(history, function (p) { - - // Value must be a number for properly work - var value = Number(p.value); - - // Apply scale - if (scale) { - value *= scale; - } - return [value, p.clock * 1000]; - }) - }; - })).then(function (result) { - return _.sortBy(result, 'target'); - }); - }; - - /** - * Convert Zabbix API trends.get response to Grafana format - * - * @param {Array} items Array of Zabbix Items - * @param alias - * @param scale - * @param {string} points Point value to return: min, max or avg - * @param {Array} trends Array of Zabbix Trends - * - * @return {Array} Array of timeseries in Grafana format - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * } - */ - this.handleTrendResponse = function (items, alias, scale, points, trends) { - - // Group items and trends by itemid - var indexed_items = _.indexBy(items, 'itemid'); - var grouped_trends = _.groupBy(trends, 'itemid'); - - var self = this; - return $q.when(_.map(grouped_trends, function (trends, itemid) { - var item = indexed_items[itemid]; - return { - target: (item.hosts ? item.hosts[0].name+': ' : '') - + (alias ? alias : self.expandItemName(item)), - datapoints: _.map(trends, function (p) { - - // Value must be a number for properly work - var value; - if (points === "min") { - value = Number(p.value_min); - } - else if (points === "max") { - value = Number(p.value_max); - } - else { - value = Number(p.value_avg); - } - - // Apply scale - if (scale) { - value *= scale; - } - return [value, p.clock * 1000]; - }) - }; - })).then(function (result) { - return _.sortBy(result, 'target'); - }); - }; - - /** - * Convert Zabbix API service.getsla response to Grafana format - * - * @param itservice - * @param slaProperty - * @param slaObject - * @returns {{target: *, datapoints: *[]}} - */ - this.handleSLAResponse = function (itservice, slaProperty, slaObject) { - var targetSLA = slaObject[itservice.serviceid].sla[0]; - if (slaProperty.property === 'status') { - var targetStatus = parseInt(slaObject[itservice.serviceid].status); - return { - target: itservice.name + ' ' + slaProperty.name, - datapoints: [ - [targetStatus, targetSLA.to * 1000] - ] - }; - } else { - return { - target: itservice.name + ' ' + slaProperty.name, - datapoints: [ - [targetSLA[slaProperty.property], targetSLA.from * 1000], - [targetSLA[slaProperty.property], targetSLA.to * 1000] - ] - }; - } - }; - - /** - * Expand item parameters, for example: - * CPU $2 time ($3) --> CPU system time (avg1) - * - * @param item: zabbix api item object - * @return {string} expanded item name (string) - */ - this.expandItemName = function(item) { - var name = item.name; - var key = item.key_; - - // extract params from key: - // "system.cpu.util[,system,avg1]" --> ["", "system", "avg1"] - var key_params = key.substring(key.indexOf('[') + 1, key.lastIndexOf(']')).split(','); - - // replace item parameters - for (var i = key_params.length; i >= 1; i--) { - name = name.replace('$' + i, key_params[i - 1]); - } - return name; - }; - - /** - * Convert multiple mettrics to array - * "{metric1,metcic2,...,metricN}" --> [metric1, metcic2,..., metricN] - * - * @param {string} metrics "{metric1,metcic2,...,metricN}" - * @return {Array} [metric1, metcic2,..., metricN] - */ - this.splitMetrics = function(metrics) { - var remove_brackets_pattern = /^{|}$/g; - var metric_split_pattern = /,(?!\s)/g; - return metrics.replace(remove_brackets_pattern, '').split(metric_split_pattern); - }; - - /** - * Convert Date object to local time in format - * YYYY-MM-DD HH:mm:ss - * - * @param {Date} date Date object - * @return {string} formatted local time YYYY-MM-DD HH:mm:ss - */ - this.getShortTime = function(date) { - var MM = date.getMonth() < 10 ? '0' + date.getMonth() : date.getMonth(); - var DD = date.getDate() < 10 ? '0' + date.getDate() : date.getDate(); - var HH = date.getHours() < 10 ? '0' + date.getHours() : date.getHours(); - var mm = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(); - var ss = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds(); - return date.getFullYear() + '-' + MM + '-' + DD + ' ' + HH + ':' + mm + ':' + ss; - }; - - /** - * Format acknowledges. - * - * @param {array} acknowledges array of Zabbix acknowledge objects - * @return {string} HTML-formatted table - */ - this.formatAcknowledges = function(acknowledges) { - if (acknowledges.length) { - var formatted_acknowledges = '

Acknowledges:
' - + ''; - _.each(_.map(acknowledges, function (ack) { - var time = new Date(ack.clock * 1000); - return ''; - }), function (ack) { - formatted_acknowledges = formatted_acknowledges.concat(ack); - }); - formatted_acknowledges = formatted_acknowledges.concat('
TimeUserComments
' + self.getShortTime(time) + '' + ack.alias - + ' (' + ack.name + ' ' + ack.surname + ')' + '' + ack.message + '
'); - return formatted_acknowledges; - } else { - return ''; - } - }; - - /** - * Downsample datapoints series - * - * @param {Object[]} datapoints [[, ], ...] - * @param {integer} time_to Panel time to - * @param {integer} ms_interval Interval in milliseconds for grouping datapoints - * @param {string} func Value to return: min, max or avg - * @return {Object[]} [[, ], ...] - */ - this.downsampleSeries = function(datapoints, time_to, ms_interval, func) { - var downsampledSeries = []; - var timeWindow = { - from: time_to * 1000 - ms_interval, - to: time_to * 1000 - }; - - var points_sum = 0; - var points_num = 0; - var value_avg = 0; - var frame = []; - - for (var i = datapoints.length - 1; i >= 0; i -= 1) { - if (timeWindow.from < datapoints[i][1] && datapoints[i][1] <= timeWindow.to) { - points_sum += datapoints[i][0]; - points_num++; - frame.push(datapoints[i][0]); - } - else { - value_avg = points_num ? points_sum / points_num : 0; - - if (func === "max") { - downsampledSeries.push([_.max(frame), timeWindow.to]); - } - else if (func === "min") { - downsampledSeries.push([_.min(frame), timeWindow.to]); - } - - // avg by default - else { - downsampledSeries.push([value_avg, timeWindow.to]); - } - - // Shift time window - timeWindow.to = timeWindow.from; - timeWindow.from -= ms_interval; - - points_sum = 0; - points_num = 0; - frame = []; - - // Process point again - i++; - } - } - return downsampledSeries.reverse(); - }; - - /** - * Convert event age from Unix format (milliseconds sins 1970) - * to Zabbix format (like at Last 20 issues panel). - * @param {Date} AgeUnix time in Unix format - * @return {string} Formatted time - */ - this.toZabbixAgeFormat = function(ageUnix) { - var age = new Date(+ageUnix); - var ageZabbix = age.getSeconds() + 's'; - if (age.getMinutes()) { - ageZabbix = age.getMinutes() + 'm ' + ageZabbix; - } - if (age.getHours()) { - ageZabbix = age.getHours() + 'h ' + ageZabbix; - } - if (age.getDate() - 1) { - ageZabbix = age.getDate() - 1 + 'd ' + ageZabbix; - } - if (age.getMonth()) { - ageZabbix = age.getMonth() + 'M ' + ageZabbix; - } - if (age.getYear() - 70) { - ageZabbix = age.getYear() -70 + 'y ' + ageZabbix; - } - return ageZabbix; - }; - - }); -}); \ No newline at end of file From 816870136c14e053b46fc795b6bb5508100038e4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 16 Mar 2016 21:15:46 +0300 Subject: [PATCH 05/24] Modules renamed using John Papa Angular Style Guide. --- Gruntfile.js | 23 +- ...on.js => add-metric-function.directive.js} | 0 ...ngService.js => dataProcessing.service.js} | 2 +- src/datasource-zabbix/datasource.js | 18 +- ...js => metric-function-editor.directive.js} | 0 src/datasource-zabbix/metricFunctions.js | 228 +++++++++--------- src/datasource-zabbix/module.js | 22 +- .../{queryCtrl.js => query.controller.js} | 14 +- ...Processor.js => queryProcessor.service.js} | 23 +- ...bbixAPIService.js => zabbixAPI.service.js} | 2 +- ...oreService.js => zabbixAPICore.service.js} | 0 ...{zabbixCache.js => zabbixCache.service.js} | 0 12 files changed, 170 insertions(+), 162 deletions(-) rename src/datasource-zabbix/{addMetricFunction.js => add-metric-function.directive.js} (100%) rename src/datasource-zabbix/{dataProcessingService.js => dataProcessing.service.js} (99%) rename src/datasource-zabbix/{metricFunctionEditor.js => metric-function-editor.directive.js} (100%) rename src/datasource-zabbix/{queryCtrl.js => query.controller.js} (96%) rename src/datasource-zabbix/{queryProcessor.js => queryProcessor.service.js} (94%) rename src/datasource-zabbix/{zabbixAPIService.js => zabbixAPI.service.js} (99%) rename src/datasource-zabbix/{zabbixAPICoreService.js => zabbixAPICore.service.js} (100%) rename src/datasource-zabbix/{zabbixCache.js => zabbixCache.service.js} (100%) diff --git a/Gruntfile.js b/Gruntfile.js index 4b85222..9b20b76 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -17,18 +17,19 @@ module.exports = function(grunt) { '**/*', '!**/datasource.js', '!**/module.js', - '!**/queryCtrl.js', + '!**/query.controller.js', '!**/utils.js', - '!**/zabbixAPICoreService.js', - '!**/zabbixAPIService.js', + '!**/zabbixAPICore.service.js', + '!**/zabbixAPI.service.js', + '!**/metricFunctions.js', '!**/*.scss' ], - dest: 'dist/src' + dest: 'dist/' }, pluginDef: { expand: true, src: ['plugin.json', 'README.md'], - dest: 'dist', + dest: 'dist/', } }, @@ -42,7 +43,7 @@ module.exports = function(grunt) { babel: { options: { - sourceMap: true, + sourceMap: false, presets: ["es2015"], plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"], }, @@ -53,13 +54,13 @@ module.exports = function(grunt) { src: [ '**/**/module.js', '**/**/datasource.js', - '**/**/queryCtrl.js', + '**/**/query.controller.js', '**/**/utils.js', - '**/**/zabbixAPICoreService.js', - '**/**/zabbixAPIService.js', + '**/**/zabbixAPICore.service.js', + '**/**/zabbixAPI.service.js', + '**/**/metricFunctions.js' ], - dest: 'dist/src', - ext:'.js' + dest: 'dist/' }] }, }, diff --git a/src/datasource-zabbix/addMetricFunction.js b/src/datasource-zabbix/add-metric-function.directive.js similarity index 100% rename from src/datasource-zabbix/addMetricFunction.js rename to src/datasource-zabbix/add-metric-function.directive.js diff --git a/src/datasource-zabbix/dataProcessingService.js b/src/datasource-zabbix/dataProcessing.service.js similarity index 99% rename from src/datasource-zabbix/dataProcessingService.js rename to src/datasource-zabbix/dataProcessing.service.js index 5bf73a3..b7958ea 100644 --- a/src/datasource-zabbix/dataProcessingService.js +++ b/src/datasource-zabbix/dataProcessing.service.js @@ -238,4 +238,4 @@ function (angular, _, moment, utils) { }; }); -}); \ No newline at end of file +}); diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 77eddba..dce090a 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -3,16 +3,15 @@ import _ from 'lodash'; import * as dateMath from 'app/core/utils/datemath'; import * as utils from './utils'; import metricFunctions from './metricFunctions'; -import {zabbixHelperSrv} from './helperFunctions'; -import './zabbixAPIService'; -import {ZabbixCachingProxy} from './zabbixCache'; -import {QueryProcessor} from './queryProcessor'; -import {DataProcessingService} from './dataProcessingService'; +import './zabbixAPI.service.js'; +import './zabbixCache.service.js'; +import './queryProcessor.service.js'; +import './dataProcessing.service'; export class ZabbixAPIDatasource { /** @ngInject */ - constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor, zabbixHelperSrv, DataProcessingService) { + constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor, DataProcessingService) { // General data source settings this.name = instanceSettings.name; @@ -46,7 +45,6 @@ export class ZabbixAPIDatasource { this.q = $q; this.templateSrv = templateSrv; this.alertSrv = alertSrv; - this.zabbixHelperSrv = zabbixHelperSrv; this.DataProcessingService = DataProcessingService; console.log(this.zabbixCache); @@ -243,13 +241,13 @@ export class ZabbixAPIDatasource { return []; } else { return this.zabbixAPI.getSLA(target.itservice.serviceid, from, to) - .then(_.bind(zabbixHelperSrv.handleSLAResponse, zabbixHelperSrv, target.itservice, target.slaProperty)); + .then(slaObject => { + return self.queryProcessor.handleSLAResponse(target.itservice, target.slaProperty, slaObject); + }); } } }, this); - var self = this; - // Data for panel (all targets) return this.q.all(_.flatten(promises)) .then(_.flatten) diff --git a/src/datasource-zabbix/metricFunctionEditor.js b/src/datasource-zabbix/metric-function-editor.directive.js similarity index 100% rename from src/datasource-zabbix/metricFunctionEditor.js rename to src/datasource-zabbix/metric-function-editor.directive.js diff --git a/src/datasource-zabbix/metricFunctions.js b/src/datasource-zabbix/metricFunctions.js index c0cc2b3..acfef29 100644 --- a/src/datasource-zabbix/metricFunctions.js +++ b/src/datasource-zabbix/metricFunctions.js @@ -1,105 +1,102 @@ -define([ - 'lodash', - 'jquery' -], -function (_, $) { - 'use strict'; +import _ from 'lodash'; +import $ from 'jquery'; - var index = []; - var categories = { - Transform: [], - Aggregate: [], - Trends: [], - Alias: [] - }; +var index = []; +var categories = { + Transform: [], + Aggregate: [], + Trends: [], + Alias: [] +}; - function addFuncDef(funcDef) { - funcDef.params = funcDef.params || []; - funcDef.defaultParams = funcDef.defaultParams || []; +function addFuncDef(funcDef) { + funcDef.params = funcDef.params || []; + funcDef.defaultParams = funcDef.defaultParams || []; - if (funcDef.category) { - categories[funcDef.category].push(funcDef); - } - index[funcDef.name] = funcDef; - index[funcDef.shortName || funcDef.name] = funcDef; + if (funcDef.category) { + categories[funcDef.category].push(funcDef); } + index[funcDef.name] = funcDef; + index[funcDef.shortName || funcDef.name] = funcDef; +} - addFuncDef({ - name: 'groupBy', - category: 'Transform', - params: [ - { name: 'interval', type: 'string'}, - { name: 'function', type: 'string', options: ['avg', 'min', 'max', 'median'] } - ], - defaultParams: ['1m', 'avg'], - }); +addFuncDef({ + name: 'groupBy', + category: 'Transform', + params: [ + { name: 'interval', type: 'string'}, + { name: 'function', type: 'string', options: ['avg', 'min', 'max', 'median'] } + ], + defaultParams: ['1m', 'avg'], +}); - addFuncDef({ - name: 'sumSeries', - category: 'Aggregate', - params: [], - defaultParams: [], - }); +addFuncDef({ + name: 'sumSeries', + category: 'Aggregate', + params: [], + defaultParams: [], +}); - addFuncDef({ - name: 'median', - category: 'Aggregate', - params: [ - { name: 'interval', type: 'string'} - ], - defaultParams: ['1m'], - }); +addFuncDef({ + name: 'median', + category: 'Aggregate', + params: [ + { name: 'interval', type: 'string'} + ], + defaultParams: ['1m'], +}); - addFuncDef({ - name: 'average', - category: 'Aggregate', - params: [ - { name: 'interval', type: 'string' } - ], - defaultParams: ['1m'], - }); +addFuncDef({ + name: 'average', + category: 'Aggregate', + params: [ + { name: 'interval', type: 'string' } + ], + defaultParams: ['1m'], +}); - addFuncDef({ - name: 'min', - category: 'Aggregate', - params: [ - { name: 'interval', type: 'string' } - ], - defaultParams: ['1m'], - }); +addFuncDef({ + name: 'min', + category: 'Aggregate', + params: [ + { name: 'interval', type: 'string' } + ], + defaultParams: ['1m'], +}); - addFuncDef({ - name: 'max', - category: 'Aggregate', - params: [ - { name: 'interval', type: 'string' } - ], - defaultParams: ['1m'], - }); +addFuncDef({ + name: 'max', + category: 'Aggregate', + params: [ + { name: 'interval', type: 'string' } + ], + defaultParams: ['1m'], +}); - addFuncDef({ - name: 'trendValue', - category: 'Trends', - params: [ - { name: 'type', type: 'string', options: ['avg', 'min', 'max'] } - ], - defaultParams: ['avg'], - }); +addFuncDef({ + name: 'trendValue', + category: 'Trends', + params: [ + { name: 'type', type: 'string', options: ['avg', 'min', 'max'] } + ], + defaultParams: ['avg'], +}); - addFuncDef({ - name: 'setAlias', - category: 'Alias', - params: [ - { name: 'alias', type: 'string'} - ], - defaultParams: [], - }); +addFuncDef({ + name: 'setAlias', + category: 'Alias', + params: [ + { name: 'alias', type: 'string'} + ], + defaultParams: [], +}); - _.each(categories, function(funcList, catName) { - categories[catName] = _.sortBy(funcList, 'name'); - }); +_.each(categories, function(funcList, catName) { + categories[catName] = _.sortBy(funcList, 'name'); +}); - function FuncInstance(funcDef, params) { +class FuncInstance { + constructor(funcDef, params) { this.def = funcDef; if (params) { @@ -113,7 +110,7 @@ function (_, $) { this.updateText(); } - FuncInstance.prototype.bindFunction = function(metricFunctions) { + bindFunction(metricFunctions) { var func = metricFunctions[this.def.name]; if (func) { @@ -126,9 +123,9 @@ function (_, $) { } else { throw { message: 'Method not found ' + this.def.name }; } - }; + } - FuncInstance.prototype.render = function(metricExp) { + render(metricExp) { var str = this.def.name + '('; var parameters = _.map(this.params, function(value, index) { @@ -149,17 +146,17 @@ function (_, $) { } return str + parameters.join(', ') + ')'; - }; + } - FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) { + _hasMultipleParamsInString(strValue, index) { if (strValue.indexOf(',') === -1) { return false; } return this.def.params[index + 1] && this.def.params[index + 1].optional; - }; + } - FuncInstance.prototype.updateParam = function(strValue, index) { + updateParam(strValue, index) { // handle optional parameters // if string contains ',' and next param is optional, split and update both if (this._hasMultipleParamsInString(strValue, index)) { @@ -177,9 +174,9 @@ function (_, $) { } this.updateText(); - }; + } - FuncInstance.prototype.updateText = function () { + updateText() { if (this.params.length === 0) { this.text = this.def.name + '()'; return; @@ -189,26 +186,25 @@ function (_, $) { text += this.params.join(', '); text += ')'; this.text = text; - }; + } +} - return { - createFuncInstance: function(funcDef, params) { - if (_.isString(funcDef)) { - if (!index[funcDef]) { - throw { message: 'Method not found ' + name }; - } - funcDef = index[funcDef]; +export default { + createFuncInstance: function(funcDef, params) { + if (_.isString(funcDef)) { + if (!index[funcDef]) { + throw { message: 'Method not found ' + name }; } - return new FuncInstance(funcDef, params); - }, - - getFuncDef: function(name) { - return index[name]; - }, - - getCategories: function() { - return categories; + funcDef = index[funcDef]; } - }; + return new FuncInstance(funcDef, params); + }, -}); + getFuncDef: function(name) { + return index[name]; + }, + + getCategories: function() { + return categories; + } +}; diff --git a/src/datasource-zabbix/module.js b/src/datasource-zabbix/module.js index d947214..a7cc1d9 100644 --- a/src/datasource-zabbix/module.js +++ b/src/datasource-zabbix/module.js @@ -1,19 +1,19 @@ import {ZabbixAPIDatasource} from './datasource'; -import {ZabbixQueryCtrl} from './queryCtrl'; +import {ZabbixQueryController} from './query.controller'; -class ZabbixConfigCtrl {} -ZabbixConfigCtrl.templateUrl = 'partials/config.html'; +class ZabbixConfigController {} +ZabbixConfigController.templateUrl = 'partials/config.html'; -class ZabbixQueryOptionsCtrl {} -ZabbixQueryOptionsCtrl.templateUrl = 'partials/query.options.html'; +class ZabbixQueryOptionsController {} +ZabbixQueryOptionsController.templateUrl = 'partials/query.options.html'; -class ZabbixAnnotationsQueryCtrl {} -ZabbixAnnotationsQueryCtrl.templateUrl = 'partials/annotations.editor.html'; +class ZabbixAnnotationsQueryController {} +ZabbixAnnotationsQueryController.templateUrl = 'partials/annotations.editor.html'; export { ZabbixAPIDatasource as Datasource, - ZabbixQueryCtrl as QueryCtrl, - ZabbixConfigCtrl as ConfigCtrl, - ZabbixQueryOptionsCtrl as QueryOptionsCtrl, - ZabbixAnnotationsQueryCtrl as AnnotationsQueryCtrl + ZabbixConfigController as ConfigCtrl, + ZabbixQueryController as QueryCtrl, + ZabbixQueryOptionsController as QueryOptionsCtrl, + ZabbixAnnotationsQueryController as AnnotationsQueryCtrl }; diff --git a/src/datasource-zabbix/queryCtrl.js b/src/datasource-zabbix/query.controller.js similarity index 96% rename from src/datasource-zabbix/queryCtrl.js rename to src/datasource-zabbix/query.controller.js index 935fc7a..25f8ac9 100644 --- a/src/datasource-zabbix/queryCtrl.js +++ b/src/datasource-zabbix/query.controller.js @@ -1,17 +1,9 @@ -/*define([ - 'app/plugins/sdk', - 'angular', - 'lodash', - './metricFunctions', - './utils' -],*/ - import {QueryCtrl} from 'app/plugins/sdk'; import _ from 'lodash'; import * as utils from './utils'; import metricFunctions from './metricFunctions'; -export class ZabbixQueryCtrl extends QueryCtrl { +export class ZabbixQueryController extends QueryCtrl { // ZabbixQueryCtrl constructor constructor($scope, $injector, $sce, $q, templateSrv) { @@ -229,9 +221,9 @@ export class ZabbixQueryCtrl extends QueryCtrl { } // Set templateUrl as static property -ZabbixQueryCtrl.templateUrl = 'partials/query.editor.html'; +ZabbixQueryController.templateUrl = 'partials/query.editor.html'; // Get list of metric names for bs-typeahead directive function getMetricNames(scope, metricList) { return _.uniq(_.map(scope.metric[metricList], 'name')); -} \ No newline at end of file +} diff --git a/src/datasource-zabbix/queryProcessor.js b/src/datasource-zabbix/queryProcessor.service.js similarity index 94% rename from src/datasource-zabbix/queryProcessor.js rename to src/datasource-zabbix/queryProcessor.service.js index c5a78ec..ecedaee 100644 --- a/src/datasource-zabbix/queryProcessor.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -416,6 +416,27 @@ function (angular, _, utils) { return this.convertHistory(history, addHostName, convertPointCallback); }; + this.handleSLAResponse = function (itservice, slaProperty, slaObject) { + var targetSLA = slaObject[itservice.serviceid].sla[0]; + if (slaProperty.property === 'status') { + var targetStatus = parseInt(slaObject[itservice.serviceid].status); + return { + target: itservice.name + ' ' + slaProperty.name, + datapoints: [ + [targetStatus, targetSLA.to * 1000] + ] + }; + } else { + return { + target: itservice.name + ' ' + slaProperty.name, + datapoints: [ + [targetSLA[slaProperty.property], targetSLA.from * 1000], + [targetSLA[slaProperty.property], targetSLA.to * 1000] + ] + }; + } + }; + function convertHistoryPoint(point) { // Value must be a number for properly work return [ @@ -450,4 +471,4 @@ function (angular, _, utils) { return QueryProcessor; }); -}); \ No newline at end of file +}); diff --git a/src/datasource-zabbix/zabbixAPIService.js b/src/datasource-zabbix/zabbixAPI.service.js similarity index 99% rename from src/datasource-zabbix/zabbixAPIService.js rename to src/datasource-zabbix/zabbixAPI.service.js index d2f89f7..ca74d11 100644 --- a/src/datasource-zabbix/zabbixAPIService.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -1,6 +1,6 @@ import angular from 'angular'; import _ from 'lodash'; -import './zabbixAPICoreService'; +import './zabbixAPICore.service'; /** @ngInject */ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { diff --git a/src/datasource-zabbix/zabbixAPICoreService.js b/src/datasource-zabbix/zabbixAPICore.service.js similarity index 100% rename from src/datasource-zabbix/zabbixAPICoreService.js rename to src/datasource-zabbix/zabbixAPICore.service.js diff --git a/src/datasource-zabbix/zabbixCache.js b/src/datasource-zabbix/zabbixCache.service.js similarity index 100% rename from src/datasource-zabbix/zabbixCache.js rename to src/datasource-zabbix/zabbixCache.service.js From 17e85291275407db54a94e193cfe168bcf782dfc Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 16 Mar 2016 21:41:19 +0300 Subject: [PATCH 06/24] Fixed directives. --- src/datasource-zabbix/datasource.js | 2 +- src/datasource-zabbix/metricFunctions.js | 32 +++++++++++------------ src/datasource-zabbix/query.controller.js | 5 +++- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index dce090a..11183c7 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import * as dateMath from 'app/core/utils/datemath'; import * as utils from './utils'; -import metricFunctions from './metricFunctions'; +import * as metricFunctions from './metricFunctions'; import './zabbixAPI.service.js'; import './zabbixCache.service.js'; import './queryProcessor.service.js'; diff --git a/src/datasource-zabbix/metricFunctions.js b/src/datasource-zabbix/metricFunctions.js index acfef29..aa0351c 100644 --- a/src/datasource-zabbix/metricFunctions.js +++ b/src/datasource-zabbix/metricFunctions.js @@ -189,22 +189,20 @@ class FuncInstance { } } -export default { - createFuncInstance: function(funcDef, params) { - if (_.isString(funcDef)) { - if (!index[funcDef]) { - throw { message: 'Method not found ' + name }; - } - funcDef = index[funcDef]; +export function createFuncInstance(funcDef, params) { + if (_.isString(funcDef)) { + if (!index[funcDef]) { + throw { message: 'Method not found ' + name }; } - return new FuncInstance(funcDef, params); - }, - - getFuncDef: function(name) { - return index[name]; - }, - - getCategories: function() { - return categories; + funcDef = index[funcDef]; } -}; + return new FuncInstance(funcDef, params); +} + +export function getFuncDef(name) { + return index[name]; +} + +export function getCategories() { + return categories; +} diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index 25f8ac9..98354e2 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -1,7 +1,10 @@ import {QueryCtrl} from 'app/plugins/sdk'; import _ from 'lodash'; import * as utils from './utils'; -import metricFunctions from './metricFunctions'; +import * as metricFunctions from './metricFunctions'; + +import './add-metric-function.directive'; +import './metric-function-editor.directive'; export class ZabbixQueryController extends QueryCtrl { From fbe7480c073101a5a0fd7bf2fda75a8913de1a35 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 17 Mar 2016 22:36:22 +0300 Subject: [PATCH 07/24] Improved performance of metric filtering and building history query. --- Gruntfile.js | 4 +- .../queryProcessor.service.js | 127 ++++++++++++------ src/datasource-zabbix/zabbixAPI.service.js | 13 +- src/datasource-zabbix/zabbixCache.service.js | 71 +++++++--- 4 files changed, 149 insertions(+), 66 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9b20b76..672b82d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -21,6 +21,7 @@ module.exports = function(grunt) { '!**/utils.js', '!**/zabbixAPICore.service.js', '!**/zabbixAPI.service.js', + //'!**/dataProcessing.service.js', '!**/metricFunctions.js', '!**/*.scss' ], @@ -43,7 +44,7 @@ module.exports = function(grunt) { babel: { options: { - sourceMap: false, + sourceMap: true, presets: ["es2015"], plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"], }, @@ -58,6 +59,7 @@ module.exports = function(grunt) { '**/**/utils.js', '**/**/zabbixAPICore.service.js', '**/**/zabbixAPI.service.js', + //'**/**/dataProcessing.service.js', '**/**/metricFunctions.js' ], dest: 'dist/' diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index ecedaee..05f5cd1 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -70,7 +70,7 @@ function (angular, _, utils) { var hostids = _.flatten(_.map(groups, 'hosts')); if (hostids.length) { - return self.cache.getHostsExtend().then(function(hosts) { + return self.cache.getIndexedHosts().then(function(hosts) { return _.map(hostids, function(hostid) { return hosts[hostid]; }); @@ -121,65 +121,91 @@ function (angular, _, utils) { }); }; + /** + * Find group, host, app or item by given name. + * @param list list of groups, apps or other + * @param name visible name + * @return array with finded element or undefined + */ + function findByName(list, name) { + var finded = _.find(list, {'name': name}); + if (finded) { + return [finded]; + } else { + return undefined; + } + } + + function findByRegex(list, regex) { + var filterPattern = utils.buildRegex(regex); + return _.filter(list, function (zbx_obj) { + return filterPattern.test(zbx_obj.name); + }); + } + + function findByFilter(list, filter) { + if (utils.isRegex(filter)) { + return findByRegex(list, filter); + } else { + return findByName(list, filter); + } + } + + function getFromIndex(index, objids) { + return _.map(objids, function(id) { + return index[id]; + }); + } + this.filterItems = function (groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { - var hosts = []; - var apps = []; - var items = []; + var hosts; + var apps; + var items; var promises = [ this.filterHosts(groupFilter), - this.filterApplications(groupFilter, hostFilter) + this.filterApplications(groupFilter, hostFilter), + this.cache.getIndexedHosts(), + this.cache.getIndexedApplications() ]; return $q.all(promises).then(function(results) { var hostList = results[0]; var applicationList = results[1]; + var idx_hosts = results[2]; + var idx_apps = results[3]; - // Filter hosts by regex - if (utils.isRegex(hostFilter)) { - var hostFilterPattern = utils.buildRegex(hostFilter); - hosts = _.filter(hostList, function (hostObj) { - return hostFilterPattern.test(hostObj.name); - }); - } else { - var findedHosts = _.find(hostList, {'name': hostFilter}); - if (findedHosts) { - hosts.push(findedHosts); - } else { - hosts = undefined; - } - } + // Filter hosts + hosts = findByFilter(hostList, hostFilter); + idx_hosts = getFromIndex(idx_hosts, _.map(hosts, 'hostid')); - // Filter applications by regex - if (utils.isRegex(appFilter)) { - var filterPattern = utils.buildRegex(appFilter); - apps = _.filter(applicationList, function (appObj) { - return filterPattern.test(appObj.name); - }); - } - // Find items in selected application - else if (appFilter) { - var finded = _.find(applicationList, {'name': appFilter}); - if (finded) { - apps.push(finded); - } else { - apps = undefined; - } - } else { + // Filter applications + if (appFilter === "") { + // Get all items apps = undefined; if (hosts) { - items = _.flatten(_.map(hosts, 'items'), true); + // Get all items in given hosts + items = _.flatten(_.map(idx_hosts, function(host) { + return _.values(host.idx_items); + }), true); } + } else { + apps = findByFilter(applicationList, appFilter); } if (apps) { - /*var appids = _.flatten(_.map(apps, 'applicationids')); - items = _.filter(cachedItems, function (itemObj) { - return _.intersection(appids, itemObj.applications).length; - }); - items = _.filter(items, function (itemObj) { - return _.find(hosts, {'hostid': itemObj.hostid }); - });*/ + // Get ids for finded applications + var appids = _.flatten(_.map(apps, 'applicationids')); + appids = _.flatten(_.map(_.map(hosts, 'applications'), function(apps) { + return _.intersection(apps, appids); + })); + + // For each finded host get list of items in finded applications + items = _.flatten(_.map(idx_hosts, function(host) { + var host_apps = _.intersection(appids, host.applications); + var host_itemids = _.flatten(_.map(getFromIndex(idx_apps, host_apps), 'itemids')); + return _.values(getFromIndex(host.idx_items, host_itemids)); + }), true); } if (!showDisabledItems) { @@ -194,6 +220,21 @@ function (angular, _, utils) { * Build query - convert target filters to array of Zabbix items */ this.buildFromCache = function (groupFilter, hostFilter, appFilter, itemFilter) { + return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { + if (items.length) { + if (utils.isRegex(itemFilter)) { + return findByFilter(items, itemFilter); + } else { + return _.filter(items, {'name': itemFilter}); + } + } else { + return []; + } + }); + }; + + // DEPRECATED + this._buildFromCache = function (groupFilter, hostFilter, appFilter, itemFilter) { // Find items by item names and perform queries var groups = []; diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index ca74d11..3047a7a 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -127,7 +127,8 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { var params = { output: ['name', 'host'], sortfield: 'name', - selectGroups: [] + selectGroups: [], + selectApplications: ['applicationid'] }; return this.request('host.get', params); @@ -140,7 +141,8 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) selectHost: [], - selectHosts: [] + selectHosts: [], + selectItems: ['itemid'] }; return this.request('application.get', params); @@ -170,9 +172,12 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { var params = { output: ['name', 'host'], sortfield: 'name', - selectGroups: [], + selectGroups: ['groupid'], + selectApplications: ['applicationid'], selectItems: [ - 'name', 'key_', + 'itemid', + 'name', + 'key_', 'value_type', 'hostid', 'status', diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 6427d07..4369d47 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -62,7 +62,8 @@ function (angular, _, utils) { self._hosts = convertHosts(results[1]); self._applications = convertApplications(results[2]); self._items = convertItems(results[3]); - self._hostsExtend = convertHostsExtend(results[4]); + self._idx_apps = indexApps(results[2]); + self._idx_hosts = indexHosts(results[4]); } self._initialized = true; }); @@ -90,13 +91,24 @@ function (angular, _, utils) { } }; - p.getHostsExtend = function() { + p.getIndexedHosts = function() { var self = this; - if (this._hostsExtend) { - return $q.when(self._hostsExtend); + if (this._idx_hosts) { + return $q.when(self._idx_hosts); } else { return this.refresh().then(function() { - return self._hostsExtend; + return self._idx_hosts; + }); + } + }; + + p.getIndexedApplications = function() { + var self = this; + if (this._idx_apps) { + return $q.when(self._idx_apps); + } else { + return this.refresh().then(function() { + return self._idx_apps; }); } }; @@ -202,25 +214,13 @@ function (angular, _, utils) { }); } - function convertHostsExtend(hosts) { - return _.indexBy(_.map(hosts, function(host) { - host.items = _.forEach(host.items, function(item) { - item.applications = _.map(item.applications, 'applicationid'); - item.item = item.name; - item.name = utils.expandItemName(item.item, item.key_); - return item; - }); - return host; - }), 'hostid'); - } - /** * Group Zabbix applications by name * host.hosts - array of host ids */ function convertApplications(applications) { return _.map(_.groupBy(applications, 'name'), function(value, key) { - + //console.log(value); // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) var hostField = 'host'; if (value[0] && value[0]['hosts']) { @@ -231,11 +231,46 @@ function (angular, _, utils) { return { name: key, applicationids: _.map(value, 'applicationid'), + itemids: _.uniq(_.map(_.flatten(value, 'items'), 'itemid')), hosts: _.uniq(_.map(_.flatten(value, hostField), 'hostid')) }; }); } + function indexHosts(hosts) { + return _.indexBy(_.map(hosts, function(host) { + + // Expand item names + host.items = _.forEach(host.items, function(item) { + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; + }); + + host.applications = _.map(host.applications, 'applicationid'); + host.idx_items = indexItems(host.items); + host.items = _.map(host.items, 'itemid'); + return host; + }), 'hostid'); + } + + function indexApps(applications) { + return _.indexBy(_.map(applications, function(app) { + return { + name: app.name, + applicationid: app.applicationid, + host: _.first(_.map(app.hosts, 'hostid')), + itemids: _.map(app.items, 'itemid') + }; + }), 'applicationid'); + } + + function indexItems(items) { + return _.indexBy(_.map(items, function(item) { + return item; + }), 'itemid'); + } + /** * Convert item.get response to cache format * item.applications - array of application ids From 1ba30bc902edaca476b4e0908c9c8f17df7c4b20 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 10:42:45 +0300 Subject: [PATCH 08/24] Fixed templateSrv error in datasource.js --- src/datasource-zabbix/datasource.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 11183c7..0d4c1c2 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -279,9 +279,10 @@ export class ZabbixAPIDatasource { metricFindQuery(query) { // Split query. Query structure: // group.host.app.item + var self = this; var parts = []; _.each(query.split('.'), function (part) { - part = this.templateSrv.replace(part); + part = self.templateSrv.replace(part); // Replace wildcard to regex if (part === '*') { From 30ae203f7387dc4e063c3d7ef51459fcd18d811b Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 11:22:44 +0300 Subject: [PATCH 09/24] Hosts and applications filtering refactor. --- .../queryProcessor.service.js | 316 +++++------------- 1 file changed, 82 insertions(+), 234 deletions(-) diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 05f5cd1..4794bef 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -42,32 +42,18 @@ function (angular, _, utils) { }; this.filterGroups = function(groupFilter) { - return self.cache.getGroups().then(function(groupList) { + return this.cache.getGroups().then(function(groupList) { return groupList; }); }; + /** + * Get list of host belonging to given groups. + * @return list of hosts + */ this.filterHosts = function(groupFilter) { - var groups = []; - - return self.cache.getGroups().then(function(groupList) { - // Filter groups by regex - if (utils.isRegex(groupFilter)) { - var filterPattern = utils.buildRegex(groupFilter); - groups = _.filter(groupList, function (groupObj) { - return filterPattern.test(groupObj.name); - }); - } - // Find hosts in selected group - else { - var finded = _.find(groupList, {'name': groupFilter}); - if (finded) { - groups.push(finded); - } else { - groups = undefined; - } - } - + return this.cache.getGroups().then(function(groups) { + groups = findByFilter(groups, groupFilter); var hostids = _.flatten(_.map(groups, 'hosts')); if (hostids.length) { return self.cache.getIndexedHosts().then(function(hosts) { @@ -81,10 +67,11 @@ function (angular, _, utils) { }); }; + /** + * Get list of applications belonging to given groups and hosts. + * @return list of applications belonging to given hosts + */ this.filterApplications = function(groupFilter, hostFilter) { - var hosts = []; - var apps = []; - var promises = [ this.filterHosts(groupFilter), this.cache.getApplications() @@ -94,69 +81,18 @@ function (angular, _, utils) { var hostList = results[0]; var applicationList = results[1]; - // Filter hosts by regex - if (utils.isRegex(hostFilter)) { - var filterPattern = utils.buildRegex(hostFilter); - hosts = _.filter(hostList, function (hostObj) { - return filterPattern.test(hostObj.name); - }); - } - // Find applications in selected host - else { - var finded = _.find(hostList, {'name': hostFilter}); - if (finded) { - hosts.push(finded); - } else { - hosts = undefined; - } - } - + var hosts = findByFilter(hostList, hostFilter); if (hosts) { var hostsids = _.map(hosts, 'hostid'); - apps = _.filter(applicationList, function (appObj) { + return _.filter(applicationList, function (appObj) { return _.intersection(hostsids, appObj.hosts).length; }); + } else { + return []; } - return apps; }); }; - /** - * Find group, host, app or item by given name. - * @param list list of groups, apps or other - * @param name visible name - * @return array with finded element or undefined - */ - function findByName(list, name) { - var finded = _.find(list, {'name': name}); - if (finded) { - return [finded]; - } else { - return undefined; - } - } - - function findByRegex(list, regex) { - var filterPattern = utils.buildRegex(regex); - return _.filter(list, function (zbx_obj) { - return filterPattern.test(zbx_obj.name); - }); - } - - function findByFilter(list, filter) { - if (utils.isRegex(filter)) { - return findByRegex(list, filter); - } else { - return findByName(list, filter); - } - } - - function getFromIndex(index, objids) { - return _.map(objids, function(id) { - return index[id]; - }); - } - this.filterItems = function (groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { var hosts; var apps; @@ -233,136 +169,12 @@ function (angular, _, utils) { }); }; - // DEPRECATED - this._buildFromCache = function (groupFilter, hostFilter, appFilter, itemFilter) { - - // Find items by item names and perform queries - var groups = []; - var hosts = []; - var apps = []; - var items = []; - var promises = [ - this.cache.getGroups(), - this.cache.getHosts(), - this.cache.getApplications(), - this.cache.getItems() - ]; - - return $q.all(promises).then(function(results) { - var cachedGroups = results[0]; - var cachedHosts = results[1]; - var cachedApps = results[2]; - var cachedItems = results[3]; - - if (utils.isRegex(hostFilter)) { - - // Filter groups - if (utils.isRegex(groupFilter)) { - var groupPattern = utils.buildRegex(groupFilter); - groups = _.filter(cachedGroups, function (groupObj) { - return groupPattern.test(groupObj.name); - }); - } else { - var findedGroup = _.find(cachedGroups, {'name': groupFilter}); - if (findedGroup) { - groups.push(findedGroup); - } else { - groups = undefined; - } - } - if (groups) { - var groupids = _.map(groups, 'groupid'); - hosts = _.filter(cachedHosts, function (hostObj) { - return _.intersection(groupids, hostObj.groups).length; - }); - } else { - // No groups finded - return []; - } - - // Filter hosts - var hostPattern = utils.buildRegex(hostFilter); - hosts = _.filter(hosts, function (hostObj) { - return hostPattern.test(hostObj.name); - }); - } else { - var findedHost = _.find(cachedHosts, {'name': hostFilter}); - if (findedHost) { - hosts.push(findedHost); - } else { - // No hosts finded - return []; - } - } - - // Find items belongs to selected hosts - items = _.filter(cachedItems, function (itemObj) { - return _.contains(_.map(hosts, 'hostid'), itemObj.hostid); - }); - - if (utils.isRegex(itemFilter)) { - - // Filter applications - if (utils.isRegex(appFilter)) { - var appPattern = utils.buildRegex(appFilter); - apps = _.filter(cachedApps, function (appObj) { - return appPattern.test(appObj.name); - }); - } - // Don't use application filter if it empty - else if (appFilter === "") { - apps = undefined; - } - else { - var findedApp = _.find(cachedApps, {'name': appFilter}); - if (findedApp) { - apps.push(findedApp); - } else { - // No applications finded - return []; - } - } - - // Find items belongs to selected applications - if (apps) { - var appids = _.flatten(_.map(apps, 'applicationids')); - items = _.filter(items, function (itemObj) { - return _.intersection(appids, itemObj.applications).length; - }); - } - - if (items) { - var itemPattern = utils.buildRegex(itemFilter); - items = _.filter(items, function (itemObj) { - return itemPattern.test(itemObj.name); - }); - } else { - // No items finded - return []; - } - } else { - items = _.filter(items, {'name': itemFilter}); - if (!items.length) { - // No items finded - return []; - } - } - - // Set host as host name for each item - items = _.each(items, function (itemObj) { - itemObj.host = _.find(hosts, {'hostid': itemObj.hostid}).name; - }); - - return items; - }); - }; - /** * Build query - convert target filters to array of Zabbix items */ this.buildTriggerQueryFromCache = function (groupFilter, hostFilter, appFilter) { var promises = [ - this.filterGroups(groupFilter).then(function(groups) { + this.cache.getGroups().then(function(groups) { return _.filter(groups, function(group) { if (utils.isRegex(groupFilter)) { return utils.buildRegex(groupFilter).test(group.name); @@ -477,39 +289,75 @@ function (angular, _, utils) { }; } }; - - function convertHistoryPoint(point) { - // Value must be a number for properly work - return [ - Number(point.value), - point.clock * 1000 - ]; - } - - function convertTrendPoint(valueType, point) { - var value; - switch (valueType) { - case "min": - value = point.value_min; - break; - case "max": - value = point.value_max; - break; - case "avg": - value = point.value_avg; - break; - default: - value = point.value_avg; - } - - return [ - Number(value), - point.clock * 1000 - ]; - } } return QueryProcessor; }); + /** + * Find group, host, app or item by given name. + * @param list list of groups, apps or other + * @param name visible name + * @return array with finded element or undefined + */ + function findByName(list, name) { + var finded = _.find(list, {'name': name}); + if (finded) { + return [finded]; + } else { + return undefined; + } + } + + function findByRegex(list, regex) { + var filterPattern = utils.buildRegex(regex); + return _.filter(list, function (zbx_obj) { + return filterPattern.test(zbx_obj.name); + }); + } + + function findByFilter(list, filter) { + if (utils.isRegex(filter)) { + return findByRegex(list, filter); + } else { + return findByName(list, filter); + } + } + + function getFromIndex(index, objids) { + return _.map(objids, function(id) { + return index[id]; + }); + } + + function convertHistoryPoint(point) { + // Value must be a number for properly work + return [ + Number(point.value), + point.clock * 1000 + ]; + } + + function convertTrendPoint(valueType, point) { + var value; + switch (valueType) { + case "min": + value = point.value_min; + break; + case "max": + value = point.value_max; + break; + case "avg": + value = point.value_avg; + break; + default: + value = point.value_avg; + } + + return [ + Number(value), + point.clock * 1000 + ]; + } + }); From d1c503e71afa091e750ede619082942bf231c87e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 11:56:21 +0300 Subject: [PATCH 10/24] Fixed application.get for 2.2 --- src/datasource-zabbix/zabbixAPI.service.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index 3047a7a..e57a436 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -136,12 +136,11 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { getApplications() { var params = { - output: ['name'], - sortfield: 'name', + output: ['applicationid', 'name'], // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) - selectHost: [], - selectHosts: [], + selectHost: ['hostid'], + selectHosts: ['hostid'], selectItems: ['itemid'] }; From e8b4a4319c930b0badd1f8502c969cb25927b112 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 14:07:21 +0300 Subject: [PATCH 11/24] Services refactor - using ES6 modules. --- Gruntfile.js | 8 +- src/datasource-zabbix/DataProcessor.js | 234 ++++++ .../dataProcessing.service.js | 241 ------- src/datasource-zabbix/datasource.js | 19 +- .../queryProcessor.service.js | 678 +++++++++--------- src/datasource-zabbix/zabbixCache.service.js | 392 +++++----- 6 files changed, 780 insertions(+), 792 deletions(-) create mode 100644 src/datasource-zabbix/DataProcessor.js delete mode 100644 src/datasource-zabbix/dataProcessing.service.js diff --git a/Gruntfile.js b/Gruntfile.js index 672b82d..58c59c0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,9 +19,11 @@ module.exports = function(grunt) { '!**/module.js', '!**/query.controller.js', '!**/utils.js', + '!**/DataProcessor.js', '!**/zabbixAPICore.service.js', '!**/zabbixAPI.service.js', - //'!**/dataProcessing.service.js', + '!**/queryProcessor.service.js', + '!**/zabbixCache.service.js', '!**/metricFunctions.js', '!**/*.scss' ], @@ -57,9 +59,11 @@ module.exports = function(grunt) { '**/**/datasource.js', '**/**/query.controller.js', '**/**/utils.js', + '**/**/DataProcessor.js', '**/**/zabbixAPICore.service.js', '**/**/zabbixAPI.service.js', - //'**/**/dataProcessing.service.js', + '**/**/queryProcessor.service.js', + '**/**/zabbixCache.service.js', '**/**/metricFunctions.js' ], dest: 'dist/' diff --git a/src/datasource-zabbix/DataProcessor.js b/src/datasource-zabbix/DataProcessor.js new file mode 100644 index 0000000..c47965e --- /dev/null +++ b/src/datasource-zabbix/DataProcessor.js @@ -0,0 +1,234 @@ +import _ from 'lodash'; +import * as utils from './utils'; + +export default class DataProcessor { + + /** + * Downsample datapoints series + */ + static downsampleSeries(datapoints, time_to, ms_interval, func) { + var downsampledSeries = []; + var timeWindow = { + from: time_to * 1000 - ms_interval, + to: time_to * 1000 + }; + + var points_sum = 0; + var points_num = 0; + var value_avg = 0; + var frame = []; + + for (var i = datapoints.length - 1; i >= 0; i -= 1) { + if (timeWindow.from < datapoints[i][1] && datapoints[i][1] <= timeWindow.to) { + points_sum += datapoints[i][0]; + points_num++; + frame.push(datapoints[i][0]); + } + else { + value_avg = points_num ? points_sum / points_num : 0; + + if (func === "max") { + downsampledSeries.push([_.max(frame), timeWindow.to]); + } + else if (func === "min") { + downsampledSeries.push([_.min(frame), timeWindow.to]); + } + + // avg by default + else { + downsampledSeries.push([value_avg, timeWindow.to]); + } + + // Shift time window + timeWindow.to = timeWindow.from; + timeWindow.from -= ms_interval; + + points_sum = 0; + points_num = 0; + frame = []; + + // Process point again + i++; + } + } + return downsampledSeries.reverse(); + } + + /** + * Group points by given time interval + * datapoints: [[, ], ...] + */ + static groupBy(interval, groupByCallback, datapoints) { + var ms_interval = utils.parseInterval(interval); + + // Calculate frame timestamps + var frames = _.groupBy(datapoints, function(point) { + // Calculate time for group of points + return Math.floor(point[1] / ms_interval) * ms_interval; + }); + + // frame: { '': [[, ], ...] } + // return [{ '': }, { '': }, ...] + var grouped = _.mapValues(frames, function(frame) { + var points = _.map(frame, function(point) { + return point[0]; + }); + return groupByCallback(points); + }); + + // Convert points to Grafana format + return sortByTime(_.map(grouped, function(value, timestamp) { + return [Number(value), Number(timestamp)]; + })); + } + + static sumSeries(timeseries) { + + // Calculate new points for interpolation + var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function(point) { + return point[1]; + })); + new_timestamps = _.sortBy(new_timestamps); + + var interpolated_timeseries = _.map(timeseries, function(series) { + var timestamps = _.map(series, function(point) { + return point[1]; + }); + var new_points = _.map(_.difference(new_timestamps, timestamps), function(timestamp) { + return [null, timestamp]; + }); + var new_series = series.concat(new_points); + return sortByTime(new_series); + }); + + _.each(interpolated_timeseries, interpolateSeries); + + var new_timeseries = []; + var sum; + for (var i = new_timestamps.length - 1; i >= 0; i--) { + sum = 0; + for (var j = interpolated_timeseries.length - 1; j >= 0; j--) { + sum += interpolated_timeseries[j][i][0]; + } + new_timeseries.push([sum, new_timestamps[i]]); + } + + return sortByTime(new_timeseries); + } + + static AVERAGE(values) { + var sum = 0; + _.each(values, function(value) { + sum += value; + }); + return sum / values.length; + } + + static MIN(values) { + return _.min(values); + } + + static MAX(values) { + return _.max(values); + } + + static MEDIAN(values) { + var sorted = _.sortBy(values); + return sorted[Math.floor(sorted.length / 2)]; + } + + static setAlias(alias, timeseries) { + timeseries.target = alias; + return timeseries; + } + + static groupByWrapper(interval, groupFunc, datapoints) { + var groupByCallback = DataProcessor.aggregationFunctions[groupFunc]; + return DataProcessor.groupBy(interval, groupByCallback, datapoints); + } + + static aggregateWrapper(groupByCallback, interval, datapoints) { + var flattenedPoints = _.flatten(datapoints, true); + return DataProcessor.groupBy(interval, groupByCallback, flattenedPoints); + } + + static get aggregationFunctions() { + return { + avg: this.AVERAGE, + min: this.MIN, + max: this.MAX, + median: this.MEDIAN + }; + } + + static get metricFunctions() { + return { + groupBy: this.groupByWrapper, + average: _.partial(this.aggregateWrapper, this.AVERAGE), + min: _.partial(this.aggregateWrapper, this.MIN), + max: _.partial(this.aggregateWrapper, this.MAX), + median: _.partial(this.aggregateWrapper, this.MEDIAN), + sumSeries: this.sumSeries, + setAlias: this.setAlias, + }; + } +} + +function sortByTime(series) { + return _.sortBy(series, function(point) { + return point[1]; + }); +} + +/** + * Interpolate series with gaps + */ +function interpolateSeries(series) { + var left, right; + + // Interpolate series + for (var i = series.length - 1; i >= 0; i--) { + if (!series[i][0]) { + left = findNearestLeft(series, series[i]); + right = findNearestRight(series, series[i]); + if (!left) { + left = right; + } + if (!right) { + right = left; + } + series[i][0] = linearInterpolation(series[i][1], left, right); + } + } + return series; +} + +function linearInterpolation(timestamp, left, right) { + if (left[1] === right[1]) { + return (left[0] + right[0]) / 2; + } else { + return (left[0] + (right[0] - left[0]) / (right[1] - left[1]) * (timestamp - left[1])); + } +} + +function findNearestRight(series, point) { + var point_index = _.indexOf(series, point); + var nearestRight; + for (var i = point_index; i < series.length; i++) { + if (series[i][0]) { + return series[i]; + } + } + return nearestRight; +} + +function findNearestLeft(series, point) { + var point_index = _.indexOf(series, point); + var nearestLeft; + for (var i = point_index; i > 0; i--) { + if (series[i][0]) { + return series[i]; + } + } + return nearestLeft; +} diff --git a/src/datasource-zabbix/dataProcessing.service.js b/src/datasource-zabbix/dataProcessing.service.js deleted file mode 100644 index b7958ea..0000000 --- a/src/datasource-zabbix/dataProcessing.service.js +++ /dev/null @@ -1,241 +0,0 @@ -define([ - 'angular', - 'lodash', - 'moment', - './utils' -], -function (angular, _, moment, utils) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('DataProcessingService', function() { - var self = this; - - /** - * Downsample datapoints series - */ - this.downsampleSeries = function(datapoints, time_to, ms_interval, func) { - var downsampledSeries = []; - var timeWindow = { - from: time_to * 1000 - ms_interval, - to: time_to * 1000 - }; - - var points_sum = 0; - var points_num = 0; - var value_avg = 0; - var frame = []; - - for (var i = datapoints.length - 1; i >= 0; i -= 1) { - if (timeWindow.from < datapoints[i][1] && datapoints[i][1] <= timeWindow.to) { - points_sum += datapoints[i][0]; - points_num++; - frame.push(datapoints[i][0]); - } - else { - value_avg = points_num ? points_sum / points_num : 0; - - if (func === "max") { - downsampledSeries.push([_.max(frame), timeWindow.to]); - } - else if (func === "min") { - downsampledSeries.push([_.min(frame), timeWindow.to]); - } - - // avg by default - else { - downsampledSeries.push([value_avg, timeWindow.to]); - } - - // Shift time window - timeWindow.to = timeWindow.from; - timeWindow.from -= ms_interval; - - points_sum = 0; - points_num = 0; - frame = []; - - // Process point again - i++; - } - } - return downsampledSeries.reverse(); - }; - - /** - * Group points by given time interval - * datapoints: [[, ], ...] - */ - this.groupBy = function(interval, groupByCallback, datapoints) { - var ms_interval = utils.parseInterval(interval); - - // Calculate frame timestamps - var frames = _.groupBy(datapoints, function(point) { - // Calculate time for group of points - return Math.floor(point[1] / ms_interval) * ms_interval; - }); - - // frame: { '': [[, ], ...] } - // return [{ '': }, { '': }, ...] - var grouped = _.mapValues(frames, function(frame) { - var points = _.map(frame, function(point) { - return point[0]; - }); - return groupByCallback(points); - }); - - // Convert points to Grafana format - return sortByTime(_.map(grouped, function(value, timestamp) { - return [Number(value), Number(timestamp)]; - })); - }; - - this.sumSeries = function(timeseries) { - - // Calculate new points for interpolation - var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function(point) { - return point[1]; - })); - new_timestamps = _.sortBy(new_timestamps); - - var interpolated_timeseries = _.map(timeseries, function(series) { - var timestamps = _.map(series, function(point) { - return point[1]; - }); - var new_points = _.map(_.difference(new_timestamps, timestamps), function(timestamp) { - return [null, timestamp]; - }); - var new_series = series.concat(new_points); - return sortByTime(new_series); - }); - - _.each(interpolated_timeseries, interpolateSeries); - - var new_timeseries = []; - var sum; - for (var i = new_timestamps.length - 1; i >= 0; i--) { - sum = 0; - for (var j = interpolated_timeseries.length - 1; j >= 0; j--) { - sum += interpolated_timeseries[j][i][0]; - } - new_timeseries.push([sum, new_timestamps[i]]); - } - - return sortByTime(new_timeseries); - }; - - function sortByTime(series) { - return _.sortBy(series, function(point) { - return point[1]; - }); - } - - /** - * Interpolate series with gaps - */ - function interpolateSeries(series) { - var left, right; - - // Interpolate series - for (var i = series.length - 1; i >= 0; i--) { - if (!series[i][0]) { - left = findNearestLeft(series, series[i]); - right = findNearestRight(series, series[i]); - if (!left) { - left = right; - } - if (!right) { - right = left; - } - series[i][0] = linearInterpolation(series[i][1], left, right); - } - } - return series; - } - - function linearInterpolation(timestamp, left, right) { - if (left[1] === right[1]) { - return (left[0] + right[0]) / 2; - } else { - return (left[0] + (right[0] - left[0]) / (right[1] - left[1]) * (timestamp - left[1])); - } - } - - function findNearestRight(series, point) { - var point_index = _.indexOf(series, point); - var nearestRight; - for (var i = point_index; i < series.length; i++) { - if (series[i][0]) { - return series[i]; - } - } - return nearestRight; - } - - function findNearestLeft(series, point) { - var point_index = _.indexOf(series, point); - var nearestLeft; - for (var i = point_index; i > 0; i--) { - if (series[i][0]) { - return series[i]; - } - } - return nearestLeft; - } - - this.AVERAGE = function(values) { - var sum = 0; - _.each(values, function(value) { - sum += value; - }); - return sum / values.length; - }; - - this.MIN = function(values) { - return _.min(values); - }; - - this.MAX = function(values) { - return _.max(values); - }; - - this.MEDIAN = function(values) { - var sorted = _.sortBy(values); - return sorted[Math.floor(sorted.length / 2)]; - }; - - this.setAlias = function(alias, timeseries) { - timeseries.target = alias; - return timeseries; - }; - - this.aggregationFunctions = { - avg: this.AVERAGE, - min: this.MIN, - max: this.MAX, - median: this.MEDIAN - }; - - this.groupByWrapper = function(interval, groupFunc, datapoints) { - var groupByCallback = self.aggregationFunctions[groupFunc]; - return self.groupBy(interval, groupByCallback, datapoints); - }; - - this.aggregateWrapper = function(groupByCallback, interval, datapoints) { - var flattenedPoints = _.flatten(datapoints, true); - return self.groupBy(interval, groupByCallback, flattenedPoints); - }; - - this.metricFunctions = { - groupBy: this.groupByWrapper, - average: _.partial(this.aggregateWrapper, this.AVERAGE), - min: _.partial(this.aggregateWrapper, this.MIN), - max: _.partial(this.aggregateWrapper, this.MAX), - median: _.partial(this.aggregateWrapper, this.MEDIAN), - sumSeries: this.sumSeries, - setAlias: this.setAlias, - }; - - }); -}); diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 0d4c1c2..8e5d250 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -3,15 +3,15 @@ import _ from 'lodash'; import * as dateMath from 'app/core/utils/datemath'; import * as utils from './utils'; import * as metricFunctions from './metricFunctions'; +import DataProcessor from './DataProcessor'; import './zabbixAPI.service.js'; import './zabbixCache.service.js'; import './queryProcessor.service.js'; -import './dataProcessing.service'; export class ZabbixAPIDatasource { /** @ngInject */ - constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor, DataProcessingService) { + constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) { // General data source settings this.name = instanceSettings.name; @@ -45,7 +45,6 @@ export class ZabbixAPIDatasource { this.q = $q; this.templateSrv = templateSrv; this.alertSrv = alertSrv; - this.DataProcessingService = DataProcessingService; console.log(this.zabbixCache); } @@ -159,7 +158,7 @@ export class ZabbixAPIDatasource { timeseries_data = _.map(timeseries_data, function (timeseries) { // Filter only transform functions - var transformFunctions = bindFunctionDefs(target.functions, 'Transform', self.DataProcessingService); + var transformFunctions = bindFunctionDefs(target.functions, 'Transform', DataProcessor); // Metric data processing var dp = timeseries.datapoints; @@ -172,7 +171,7 @@ export class ZabbixAPIDatasource { }); // Aggregations - var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate', self.DataProcessingService); + var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate', DataProcessor); var dp = _.map(timeseries_data, 'datapoints'); if (aggregationFunctions.length) { for (var i = 0; i < aggregationFunctions.length; i++) { @@ -189,7 +188,7 @@ export class ZabbixAPIDatasource { } // Apply alias functions - var aliasFunctions = bindFunctionDefs(target.functions, 'Alias', self.DataProcessingService); + var aliasFunctions = bindFunctionDefs(target.functions, 'Alias', DataProcessor); for (var j = 0; j < aliasFunctions.length; j++) { _.each(timeseries_data, aliasFunctions[j]); } @@ -255,9 +254,9 @@ export class ZabbixAPIDatasource { // Series downsampling var data = _.map(timeseries_data, function(timeseries) { - var DPS = self.DataProcessingService; if (timeseries.datapoints.length > options.maxDataPoints) { - timeseries.datapoints = DPS.groupBy(options.interval, DPS.AVERAGE, timeseries.datapoints); + timeseries.datapoints = + DataProcessor.groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints); } return timeseries; }); @@ -409,7 +408,7 @@ export class ZabbixAPIDatasource { } -function bindFunctionDefs(functionDefs, category, DataProcessingService) { +function bindFunctionDefs(functionDefs, category, DataProcessor) { 'use strict'; var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); var aggFuncDefs = _.filter(functionDefs, function(func) { @@ -418,7 +417,7 @@ function bindFunctionDefs(functionDefs, category, DataProcessingService) { return _.map(aggFuncDefs, function(func) { var funcInstance = metricFunctions.createFuncInstance(func.def, func.params); - return funcInstance.bindFunction(DataProcessingService.metricFunctions); + return funcInstance.bindFunction(DataProcessor.metricFunctions); }); } diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 4794bef..d447e55 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -1,363 +1,361 @@ -define([ - 'angular', - 'lodash', - './utils' -], -function (angular, _, utils) { - 'use strict'; +import angular from 'angular'; +import _ from 'lodash'; +import * as utils from './utils'; - var module = angular.module('grafana.services'); - - module.factory('QueryProcessor', function($q) { - - function QueryProcessor(zabbixCacheInstance) { - var self = this; +/** @ngInject */ +angular.module('grafana.services').factory('QueryProcessor', function($q) { + class QueryProcessor { + constructor(zabbixCacheInstance) { this.cache = zabbixCacheInstance; + this.$q = $q; + } - /** - * Build query in asynchronous manner - */ - this.build = function (groupFilter, hostFilter, appFilter, itemFilter) { - if (this.cache._initialized) { - return $q.when(self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter)); - } else { - return this.cache.refresh().then(function() { - return self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter); - }); - } - }; - - /** - * Build trigger query in asynchronous manner - */ - this.buildTriggerQuery = function (groupFilter, hostFilter, appFilter) { - if (this.cache._initialized) { - return $q.when(self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter)); - } else { - return this.cache.refresh().then(function() { - return self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter); - }); - } - }; - - this.filterGroups = function(groupFilter) { - return this.cache.getGroups().then(function(groupList) { - return groupList; + /** + * Build query in asynchronous manner + */ + build(groupFilter, hostFilter, appFilter, itemFilter) { + var self = this; + if (this.cache._initialized) { + return this.$q.when(self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter)); + } else { + return this.cache.refresh().then(function() { + return self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter); }); - }; + } + } - /** - * Get list of host belonging to given groups. - * @return list of hosts - */ - this.filterHosts = function(groupFilter) { - return this.cache.getGroups().then(function(groups) { - groups = findByFilter(groups, groupFilter); - var hostids = _.flatten(_.map(groups, 'hosts')); - if (hostids.length) { - return self.cache.getIndexedHosts().then(function(hosts) { - return _.map(hostids, function(hostid) { - return hosts[hostid]; - }); + /** + * Build trigger query in asynchronous manner + */ + buildTriggerQuery(groupFilter, hostFilter, appFilter) { + var self = this; + if (this.cache._initialized) { + return this.$q.when(self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter)); + } else { + return this.cache.refresh().then(function() { + return self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter); + }); + } + } + + filterGroups(groupFilter) { + return this.cache.getGroups().then(function(groupList) { + return groupList; + }); + } + + /** + * Get list of host belonging to given groups. + * @return list of hosts + */ + filterHosts(groupFilter) { + var self = this; + return this.cache.getGroups().then(function(groups) { + groups = findByFilter(groups, groupFilter); + var hostids = _.flatten(_.map(groups, 'hosts')); + if (hostids.length) { + return self.cache.getIndexedHosts().then(function(hosts) { + return _.map(hostids, function(hostid) { + return hosts[hostid]; }); - } else { - return []; - } - }); - }; + }); + } else { + return []; + } + }); + } - /** - * Get list of applications belonging to given groups and hosts. - * @return list of applications belonging to given hosts - */ - this.filterApplications = function(groupFilter, hostFilter) { - var promises = [ - this.filterHosts(groupFilter), - this.cache.getApplications() - ]; + /** + * Get list of applications belonging to given groups and hosts. + * @return list of applications belonging to given hosts + */ + filterApplications(groupFilter, hostFilter) { + var promises = [ + this.filterHosts(groupFilter), + this.cache.getApplications() + ]; - return $q.all(promises).then(function(results) { - var hostList = results[0]; - var applicationList = results[1]; + return this.$q.all(promises).then(function(results) { + var hostList = results[0]; + var applicationList = results[1]; - var hosts = findByFilter(hostList, hostFilter); + var hosts = findByFilter(hostList, hostFilter); + if (hosts) { + var hostsids = _.map(hosts, 'hostid'); + return _.filter(applicationList, function (appObj) { + return _.intersection(hostsids, appObj.hosts).length; + }); + } else { + return []; + } + }); + } + + filterItems(groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { + var hosts; + var apps; + var items; + + var promises = [ + this.filterHosts(groupFilter), + this.filterApplications(groupFilter, hostFilter), + this.cache.getIndexedHosts(), + this.cache.getIndexedApplications() + ]; + + return this.$q.all(promises).then(function(results) { + var hostList = results[0]; + var applicationList = results[1]; + var idx_hosts = results[2]; + var idx_apps = results[3]; + + // Filter hosts + hosts = findByFilter(hostList, hostFilter); + idx_hosts = getFromIndex(idx_hosts, _.map(hosts, 'hostid')); + + // Filter applications + if (appFilter === "") { + // Get all items + apps = undefined; if (hosts) { - var hostsids = _.map(hosts, 'hostid'); - return _.filter(applicationList, function (appObj) { - return _.intersection(hostsids, appObj.hosts).length; - }); - } else { - return []; - } - }); - }; - - this.filterItems = function (groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { - var hosts; - var apps; - var items; - - var promises = [ - this.filterHosts(groupFilter), - this.filterApplications(groupFilter, hostFilter), - this.cache.getIndexedHosts(), - this.cache.getIndexedApplications() - ]; - - return $q.all(promises).then(function(results) { - var hostList = results[0]; - var applicationList = results[1]; - var idx_hosts = results[2]; - var idx_apps = results[3]; - - // Filter hosts - hosts = findByFilter(hostList, hostFilter); - idx_hosts = getFromIndex(idx_hosts, _.map(hosts, 'hostid')); - - // Filter applications - if (appFilter === "") { - // Get all items - apps = undefined; - if (hosts) { - // Get all items in given hosts - items = _.flatten(_.map(idx_hosts, function(host) { - return _.values(host.idx_items); - }), true); - } - } else { - apps = findByFilter(applicationList, appFilter); - } - - if (apps) { - // Get ids for finded applications - var appids = _.flatten(_.map(apps, 'applicationids')); - appids = _.flatten(_.map(_.map(hosts, 'applications'), function(apps) { - return _.intersection(apps, appids); - })); - - // For each finded host get list of items in finded applications + // Get all items in given hosts items = _.flatten(_.map(idx_hosts, function(host) { - var host_apps = _.intersection(appids, host.applications); - var host_itemids = _.flatten(_.map(getFromIndex(idx_apps, host_apps), 'itemids')); - return _.values(getFromIndex(host.idx_items, host_itemids)); + return _.values(host.idx_items); }), true); } - - if (!showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - - return items; - }); - }; - - /** - * Build query - convert target filters to array of Zabbix items - */ - this.buildFromCache = function (groupFilter, hostFilter, appFilter, itemFilter) { - return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { - if (items.length) { - if (utils.isRegex(itemFilter)) { - return findByFilter(items, itemFilter); - } else { - return _.filter(items, {'name': itemFilter}); - } - } else { - return []; - } - }); - }; - - /** - * Build query - convert target filters to array of Zabbix items - */ - this.buildTriggerQueryFromCache = function (groupFilter, hostFilter, appFilter) { - var promises = [ - this.cache.getGroups().then(function(groups) { - return _.filter(groups, function(group) { - if (utils.isRegex(groupFilter)) { - return utils.buildRegex(groupFilter).test(group.name); - } else { - return group.name === groupFilter; - } - }); - }), - this.filterHosts(groupFilter).then(function(hosts) { - return _.filter(hosts, function(host) { - if (utils.isRegex(hostFilter)) { - return utils.buildRegex(hostFilter).test(host.name); - } else { - return host.name === hostFilter; - } - }); - }), - this.filterApplications(groupFilter, hostFilter).then(function(apps) { - return _.filter(apps, function(app) { - if (utils.isRegex(appFilter)) { - return utils.buildRegex(appFilter).test(app.name); - } else { - return app.name === appFilter; - } - }); - }) - ]; - - return $q.all(promises).then(function(results) { - var filteredGroups = results[0]; - var filteredHosts = results[1]; - var filteredApps = results[2]; - var query = {}; - - if (appFilter) { - query.applicationids = _.flatten(_.map(filteredApps, 'applicationids')); - } - if (hostFilter) { - query.hostids = _.map(filteredHosts, 'hostid'); - } - if (groupFilter) { - query.groupids = _.map(filteredGroups, 'groupid'); - } - - return query; - }); - }; - - /** - * Convert Zabbix API history.get response to Grafana format - * - * @return {Array} Array of timeseries in Grafana format - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * } - */ - this.convertHistory = function(history, addHostName, convertPointCallback) { - /** - * Response should be in the format: - * data: [ - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * }, ... - * ] - */ - - // Group history by itemid - var grouped_history = _.groupBy(history, 'itemid'); - - return _.map(grouped_history, function(hist, itemid) { - var item = self.cache.getItem(itemid); - var alias = item.name; - if (addHostName) { - var host = self.cache.getHost(item.hostid); - alias = host.name + ": " + alias; - } - return { - target: alias, - datapoints: _.map(hist, convertPointCallback) - }; - }); - }; - - this.handleHistory = function(history, addHostName) { - return this.convertHistory(history, addHostName, convertHistoryPoint); - }; - - this.handleTrends = function(history, addHostName, valueType) { - var convertPointCallback = _.partial(convertTrendPoint, valueType); - return this.convertHistory(history, addHostName, convertPointCallback); - }; - - this.handleSLAResponse = function (itservice, slaProperty, slaObject) { - var targetSLA = slaObject[itservice.serviceid].sla[0]; - if (slaProperty.property === 'status') { - var targetStatus = parseInt(slaObject[itservice.serviceid].status); - return { - target: itservice.name + ' ' + slaProperty.name, - datapoints: [ - [targetStatus, targetSLA.to * 1000] - ] - }; } else { - return { - target: itservice.name + ' ' + slaProperty.name, - datapoints: [ - [targetSLA[slaProperty.property], targetSLA.from * 1000], - [targetSLA[slaProperty.property], targetSLA.to * 1000] - ] - }; + apps = findByFilter(applicationList, appFilter); } - }; + + if (apps) { + // Get ids for finded applications + var appids = _.flatten(_.map(apps, 'applicationids')); + appids = _.flatten(_.map(_.map(hosts, 'applications'), function(apps) { + return _.intersection(apps, appids); + })); + + // For each finded host get list of items in finded applications + items = _.flatten(_.map(idx_hosts, function(host) { + var host_apps = _.intersection(appids, host.applications); + var host_itemids = _.flatten(_.map(getFromIndex(idx_apps, host_apps), 'itemids')); + return _.values(getFromIndex(host.idx_items, host_itemids)); + }), true); + } + + if (!showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + + return items; + }); } - return QueryProcessor; - }); + /** + * Build query - convert target filters to array of Zabbix items + */ + buildFromCache(groupFilter, hostFilter, appFilter, itemFilter) { + return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { + if (items.length) { + if (utils.isRegex(itemFilter)) { + return findByFilter(items, itemFilter); + } else { + return _.filter(items, {'name': itemFilter}); + } + } else { + return []; + } + }); + } - /** - * Find group, host, app or item by given name. - * @param list list of groups, apps or other - * @param name visible name - * @return array with finded element or undefined - */ - function findByName(list, name) { - var finded = _.find(list, {'name': name}); - if (finded) { - return [finded]; - } else { - return undefined; + /** + * Build query - convert target filters to array of Zabbix items + */ + buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter) { + var promises = [ + this.cache.getGroups().then(function(groups) { + return _.filter(groups, function(group) { + if (utils.isRegex(groupFilter)) { + return utils.buildRegex(groupFilter).test(group.name); + } else { + return group.name === groupFilter; + } + }); + }), + this.filterHosts(groupFilter).then(function(hosts) { + return _.filter(hosts, function(host) { + if (utils.isRegex(hostFilter)) { + return utils.buildRegex(hostFilter).test(host.name); + } else { + return host.name === hostFilter; + } + }); + }), + this.filterApplications(groupFilter, hostFilter).then(function(apps) { + return _.filter(apps, function(app) { + if (utils.isRegex(appFilter)) { + return utils.buildRegex(appFilter).test(app.name); + } else { + return app.name === appFilter; + } + }); + }) + ]; + + return this.$q.all(promises).then(function(results) { + var filteredGroups = results[0]; + var filteredHosts = results[1]; + var filteredApps = results[2]; + var query = {}; + + if (appFilter) { + query.applicationids = _.flatten(_.map(filteredApps, 'applicationids')); + } + if (hostFilter) { + query.hostids = _.map(filteredHosts, 'hostid'); + } + if (groupFilter) { + query.groupids = _.map(filteredGroups, 'groupid'); + } + + return query; + }); + } + + /** + * Convert Zabbix API history.get response to Grafana format + * + * @return {Array} Array of timeseries in Grafana format + * { + * target: "Metric name", + * datapoints: [[, ], ...] + * } + */ + convertHistory(history, addHostName, convertPointCallback) { + /** + * Response should be in the format: + * data: [ + * { + * target: "Metric name", + * datapoints: [[, ], ...] + * }, ... + * ] + */ + var self = this; + + // Group history by itemid + var grouped_history = _.groupBy(history, 'itemid'); + + return _.map(grouped_history, function(hist, itemid) { + var item = self.cache.getItem(itemid); + var alias = item.name; + if (addHostName) { + var host = self.cache.getHost(item.hostid); + alias = host.name + ": " + alias; + } + return { + target: alias, + datapoints: _.map(hist, convertPointCallback) + }; + }); + } + + handleHistory(history, addHostName) { + return this.convertHistory(history, addHostName, convertHistoryPoint); + } + + handleTrends(history, addHostName, valueType) { + var convertPointCallback = _.partial(convertTrendPoint, valueType); + return this.convertHistory(history, addHostName, convertPointCallback); + } + + handleSLAResponse(itservice, slaProperty, slaObject) { + var targetSLA = slaObject[itservice.serviceid].sla[0]; + if (slaProperty.property === 'status') { + var targetStatus = parseInt(slaObject[itservice.serviceid].status); + return { + target: itservice.name + ' ' + slaProperty.name, + datapoints: [ + [targetStatus, targetSLA.to * 1000] + ] + }; + } else { + return { + target: itservice.name + ' ' + slaProperty.name, + datapoints: [ + [targetSLA[slaProperty.property], targetSLA.from * 1000], + [targetSLA[slaProperty.property], targetSLA.to * 1000] + ] + }; + } } } - function findByRegex(list, regex) { - var filterPattern = utils.buildRegex(regex); - return _.filter(list, function (zbx_obj) { - return filterPattern.test(zbx_obj.name); - }); - } - - function findByFilter(list, filter) { - if (utils.isRegex(filter)) { - return findByRegex(list, filter); - } else { - return findByName(list, filter); - } - } - - function getFromIndex(index, objids) { - return _.map(objids, function(id) { - return index[id]; - }); - } - - function convertHistoryPoint(point) { - // Value must be a number for properly work - return [ - Number(point.value), - point.clock * 1000 - ]; - } - - function convertTrendPoint(valueType, point) { - var value; - switch (valueType) { - case "min": - value = point.value_min; - break; - case "max": - value = point.value_max; - break; - case "avg": - value = point.value_avg; - break; - default: - value = point.value_avg; - } - - return [ - Number(value), - point.clock * 1000 - ]; - } - + return QueryProcessor; }); + +/** + * Find group, host, app or item by given name. + * @param list list of groups, apps or other + * @param name visible name + * @return array with finded element or undefined + */ +function findByName(list, name) { + var finded = _.find(list, {'name': name}); + if (finded) { + return [finded]; + } else { + return undefined; + } +} + +function findByRegex(list, regex) { + var filterPattern = utils.buildRegex(regex); + return _.filter(list, function (zbx_obj) { + return filterPattern.test(zbx_obj.name); + }); +} + +function findByFilter(list, filter) { + if (utils.isRegex(filter)) { + return findByRegex(list, filter); + } else { + return findByName(list, filter); + } +} + +function getFromIndex(index, objids) { + return _.map(objids, function(id) { + return index[id]; + }); +} + +function convertHistoryPoint(point) { + // Value must be a number for properly work + return [ + Number(point.value), + point.clock * 1000 + ]; +} + +function convertTrendPoint(valueType, point) { + var value; + switch (valueType) { + case "min": + value = point.value_min; + break; + case "max": + value = point.value_max; + break; + case "avg": + value = point.value_avg; + break; + default: + value = point.value_avg; + } + + return [ + Number(value), + point.clock * 1000 + ]; +} diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 4369d47..65a00cf 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -1,27 +1,25 @@ -define([ - 'angular', - 'lodash', - './utils' -], -function (angular, _, utils) { - 'use strict'; +import angular from 'angular'; +import _ from 'lodash'; +import * as utils from './utils'; - var module = angular.module('grafana.services'); +// Use factory() instead service() for multiple datasources support. +// Each datasource instance must initialize its own cache. - // Use factory() instead service() for multiple datasources support. - // Each datasource instance must initialize its own cache. - module.factory('ZabbixCachingProxy', function($q, $interval) { +/** @ngInject */ +angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $interval) { - function ZabbixCachingProxy(zabbixAPI, ttl) { + class ZabbixCachingProxy { + constructor(zabbixAPI, ttl) { this.zabbixAPI = zabbixAPI; this.ttl = ttl; + this.$q = $q; + // Internal objects for data storing this._groups = undefined; this._hosts = undefined; this._applications = undefined; this._items = undefined; - this._hostsExtend = undefined; this.storage = { history: {}, trends: {} @@ -34,7 +32,7 @@ function (angular, _, utils) { this.historyPromises = {}; // Wrap _refresh() method to call it once. - this.refresh = callOnce(p._refresh, this.refreshPromise); + this.refresh = callOnce(this._refresh, this.refreshPromise); // Update cache periodically $interval(_.bind(this.refresh, this), this.ttl); @@ -44,9 +42,7 @@ function (angular, _, utils) { this.historyPromises); } - var p = ZabbixCachingProxy.prototype; - - p._refresh = function() { + _refresh() { var self = this; var promises = [ this.zabbixAPI.getGroups(), @@ -56,7 +52,7 @@ function (angular, _, utils) { this.zabbixAPI.getHostsExtend() ]; - return $q.all(promises).then(function(results) { + return this.$q.all(promises).then(function(results) { if (results.length) { self._groups = convertGroups(results[0]); self._hosts = convertHosts(results[1]); @@ -67,94 +63,76 @@ function (angular, _, utils) { } self._initialized = true; }); - }; + } - p.getGroups = function() { + getGroups() { var self = this; if (this._groups) { - return $q.when(self._groups); + return this.$q.when(self._groups); } else { return this.refresh().then(function() { return self._groups; }); } - }; + } - p.getHosts = function() { + getHosts() { var self = this; if (this._hosts) { - return $q.when(self._hosts); + return this.$q.when(self._hosts); } else { return this.refresh().then(function() { return self._hosts; }); } - }; + } - p.getIndexedHosts = function() { + getIndexedHosts() { var self = this; if (this._idx_hosts) { - return $q.when(self._idx_hosts); + return this.$q.when(self._idx_hosts); } else { return this.refresh().then(function() { return self._idx_hosts; }); } - }; + } - p.getIndexedApplications = function() { + getIndexedApplications() { var self = this; if (this._idx_apps) { - return $q.when(self._idx_apps); + return this.$q.when(self._idx_apps); } else { return this.refresh().then(function() { return self._idx_apps; }); } - }; + } - p.getApplications = function() { + getApplications() { var self = this; if (this._applications) { - return $q.when(self._applications); + return this.$q.when(self._applications); } else { return this.refresh().then(function() { return self._applications; }); } - }; + } - p.getItems = function(type) { + getItems(type) { var self = this; if (this._items) { - return $q.when(filterItems(self._items, type)); + return this.$q.when(filterItems(self._items, type)); } else { return this.refresh().then(function() { return filterItems(self._items, type); }); } - }; - - function filterItems(items, type) { - switch (type) { - case 'num': - return _.filter(items, function(item) { - return (item.value_type === '0' || - item.value_type === '3'); - }); - case 'text': - return _.filter(items, function(item) { - return (item.value_type === '1' || - item.value_type === '2' || - item.value_type === '4'); - }); - default: - return items; - } } - p.getHistoryFromCache = function(items, time_from, time_till) { - var deferred = $q.defer(); + getHistoryFromCache(items, time_from, time_till) { + var deferred = this.$q.defer(); var historyStorage = this.storage.history; var full_history; var expired = _.filter(_.indexBy(items, 'itemid'), function(item, itemid) { @@ -182,161 +160,177 @@ function (angular, _, utils) { deferred.resolve(_.flatten(full_history, true)); } return deferred.promise; - }; + } - p.getHistoryFromAPI = function(items, time_from, time_till) { + getHistoryFromAPI(items, time_from, time_till) { return this.zabbixAPI.getHistory(items, time_from, time_till); - }; + } - p.getHost = function(hostid) { + getHost(hostid) { return _.find(this._hosts, {'hostid': hostid}); - }; + } - p.getItem = function(itemid) { + getItem(itemid) { return _.find(this._items, {'itemid': itemid}); - }; - - /** - * Convert host.get response to cache format - * host.groups - array of group ids - */ - function convertHosts(hosts) { - return _.forEach(hosts, function(host) { - host.groups = _.map(host.groups, 'groupid'); - return host; - }); } + } - function convertGroups(groups) { - return _.forEach(groups, function(group) { - group.hosts = _.map(group.hosts, 'hostid'); - return group; - }); - } + function callHistoryOnce(func, promiseKeeper) { + return function() { + var itemids = _.map(arguments[0], 'itemid'); + var stamp = itemids.join() + arguments[1] + arguments[2]; + var hash = stamp.getHash(); - /** - * Group Zabbix applications by name - * host.hosts - array of host ids - */ - function convertApplications(applications) { - return _.map(_.groupBy(applications, 'name'), function(value, key) { - //console.log(value); - // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) - var hostField = 'host'; - if (value[0] && value[0]['hosts']) { - // For Zabbix 2.2 - hostField = 'hosts'; - } - - return { - name: key, - applicationids: _.map(value, 'applicationid'), - itemids: _.uniq(_.map(_.flatten(value, 'items'), 'itemid')), - hosts: _.uniq(_.map(_.flatten(value, hostField), 'hostid')) - }; - }); - } - - function indexHosts(hosts) { - return _.indexBy(_.map(hosts, function(host) { - - // Expand item names - host.items = _.forEach(host.items, function(item) { - item.item = item.name; - item.name = utils.expandItemName(item.item, item.key_); - return item; + var deferred = $q.defer(); + if (!promiseKeeper[hash]) { + promiseKeeper[hash] = deferred.promise; + func.apply(this, arguments).then(function(result) { + deferred.resolve(result); + promiseKeeper[hash] = null; }); - - host.applications = _.map(host.applications, 'applicationid'); - host.idx_items = indexItems(host.items); - host.items = _.map(host.items, 'itemid'); - return host; - }), 'hostid'); - } - - function indexApps(applications) { - return _.indexBy(_.map(applications, function(app) { - return { - name: app.name, - applicationid: app.applicationid, - host: _.first(_.map(app.hosts, 'hostid')), - itemids: _.map(app.items, 'itemid') - }; - }), 'applicationid'); - } - - function indexItems(items) { - return _.indexBy(_.map(items, function(item) { - return item; - }), 'itemid'); - } - - /** - * Convert item.get response to cache format - * item.applications - array of application ids - * item.item - original item name returned by api (ie "CPU $2 time") - * item.name - expanded name (ie "CPU system time") - */ - function convertItems(items) { - return _.forEach(items, function(item) { - item.applications = _.map(item.applications, 'applicationid'); - item.item = item.name; - item.name = utils.expandItemName(item.item, item.key_); - return item; - }); - } - - String.prototype.getHash = function() { - var hash = 0, i, chr, len; - if (this.length === 0) { - return hash; + } else { + return promiseKeeper[hash]; } - for (i = 0, len = this.length; i < len; i++) { - chr = this.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; // Convert to 32bit integer - } - return hash; + return deferred.promise; }; + } - function callHistoryOnce(func, promiseKeeper) { - return function() { - var itemids = _.map(arguments[0], 'itemid'); - var stamp = itemids.join() + arguments[1] + arguments[2]; - var hash = stamp.getHash(); - - var deferred = $q.defer(); - if (!promiseKeeper[hash]) { - promiseKeeper[hash] = deferred.promise; - func.apply(this, arguments).then(function(result) { - deferred.resolve(result); - promiseKeeper[hash] = null; - }); - } else { - return promiseKeeper[hash]; - } - return deferred.promise; - }; - } - - function callOnce(func, promiseKeeper) { - return function() { - var deferred = $q.defer(); - if (!promiseKeeper) { - promiseKeeper = deferred.promise; - func.apply(this, arguments).then(function(result) { - deferred.resolve(result); - promiseKeeper = null; - }); - } else { - return promiseKeeper; - } - return deferred.promise; - }; - } - - return ZabbixCachingProxy; - - }); + function callOnce(func, promiseKeeper) { + return function() { + var deferred = $q.defer(); + if (!promiseKeeper) { + promiseKeeper = deferred.promise; + func.apply(this, arguments).then(function(result) { + deferred.resolve(result); + promiseKeeper = null; + }); + } else { + return promiseKeeper; + } + return deferred.promise; + }; + } + return ZabbixCachingProxy; }); + +/** + * Convert host.get response to cache format + * host.groups - array of group ids + */ +function convertHosts(hosts) { + return _.forEach(hosts, function(host) { + host.groups = _.map(host.groups, 'groupid'); + return host; + }); +} + +function convertGroups(groups) { + return _.forEach(groups, function(group) { + group.hosts = _.map(group.hosts, 'hostid'); + return group; + }); +} + +/** + * Group Zabbix applications by name + * host.hosts - array of host ids + */ +function convertApplications(applications) { + return _.map(_.groupBy(applications, 'name'), function(value, key) { + //console.log(value); + // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) + var hostField = 'host'; + if (value[0] && value[0]['hosts']) { + // For Zabbix 2.2 + hostField = 'hosts'; + } + + return { + name: key, + applicationids: _.map(value, 'applicationid'), + itemids: _.uniq(_.map(_.flatten(value, 'items'), 'itemid')), + hosts: _.uniq(_.map(_.flatten(value, hostField), 'hostid')) + }; + }); +} + +function indexHosts(hosts) { + return _.indexBy(_.map(hosts, function(host) { + + // Expand item names + host.items = _.forEach(host.items, function(item) { + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; + }); + + host.applications = _.map(host.applications, 'applicationid'); + host.idx_items = indexItems(host.items); + host.items = _.map(host.items, 'itemid'); + return host; + }), 'hostid'); +} + +function indexApps(applications) { + return _.indexBy(_.map(applications, function(app) { + return { + name: app.name, + applicationid: app.applicationid, + host: _.first(_.map(app.hosts, 'hostid')), + itemids: _.map(app.items, 'itemid') + }; + }), 'applicationid'); +} + +function indexItems(items) { + return _.indexBy(_.map(items, function(item) { + return item; + }), 'itemid'); +} + +/** + * Convert item.get response to cache format + * item.applications - array of application ids + * item.item - original item name returned by api (ie "CPU $2 time") + * item.name - expanded name (ie "CPU system time") + */ +function convertItems(items) { + return _.forEach(items, function(item) { + item.applications = _.map(item.applications, 'applicationid'); + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; + }); +} + +function filterItems(items, type) { + switch (type) { + case 'num': + return _.filter(items, function(item) { + return (item.value_type === '0' || + item.value_type === '3'); + }); + case 'text': + return _.filter(items, function(item) { + return (item.value_type === '1' || + item.value_type === '2' || + item.value_type === '4'); + }); + default: + return items; + } +} + +String.prototype.getHash = function() { + var hash = 0, i, chr, len; + if (this.length === 0) { + return hash; + } + for (i = 0, len = this.length; i < len; i++) { + chr = this.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +}; From e74824f840a2dbcdbbfe290019924ec410f53535 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 14:20:55 +0300 Subject: [PATCH 12/24] Directives refactor. --- Gruntfile.js | 22 +- .../add-metric-function.directive.js | 179 ++++---- .../metric-function-editor.directive.js | 431 +++++++++--------- 3 files changed, 303 insertions(+), 329 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 58c59c0..f9b5273 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,16 +15,7 @@ module.exports = function(grunt) { expand: true, src: [ '**/*', - '!**/datasource.js', - '!**/module.js', - '!**/query.controller.js', - '!**/utils.js', - '!**/DataProcessor.js', - '!**/zabbixAPICore.service.js', - '!**/zabbixAPI.service.js', - '!**/queryProcessor.service.js', - '!**/zabbixCache.service.js', - '!**/metricFunctions.js', + '!**/*.js', '!**/*.scss' ], dest: 'dist/' @@ -55,16 +46,7 @@ module.exports = function(grunt) { cwd: 'src', expand: true, src: [ - '**/**/module.js', - '**/**/datasource.js', - '**/**/query.controller.js', - '**/**/utils.js', - '**/**/DataProcessor.js', - '**/**/zabbixAPICore.service.js', - '**/**/zabbixAPI.service.js', - '**/**/queryProcessor.service.js', - '**/**/zabbixCache.service.js', - '**/**/metricFunctions.js' + '**/**/*.js' ], dest: 'dist/' }] diff --git a/src/datasource-zabbix/add-metric-function.directive.js b/src/datasource-zabbix/add-metric-function.directive.js index 2001265..a3676ee 100644 --- a/src/datasource-zabbix/add-metric-function.directive.js +++ b/src/datasource-zabbix/add-metric-function.directive.js @@ -1,107 +1,104 @@ -define([ - 'angular', - 'lodash', - 'jquery', - './metricFunctions' -], -function (angular, _, $, metricFunctions) { - 'use strict'; +import angular from 'angular'; +import _ from 'lodash'; +import $ from 'jquery'; +import * as metricFunctions from './metricFunctions'; - angular - .module('grafana.directives') - .directive('addMetricFunction', function($compile) { - var inputTemplate = ''; +/** @ngInject */ +angular + .module('grafana.directives') + .directive('addMetricFunction', function($compile) { + var inputTemplate = ''; - var buttonTemplate = '' + - ''; + var buttonTemplate = '' + + ''; - return { - link: function($scope, elem) { - var categories = metricFunctions.getCategories(); - var allFunctions = getAllFunctionNames(categories); + return { + link: function($scope, elem) { + var categories = metricFunctions.getCategories(); + var allFunctions = getAllFunctionNames(categories); - $scope.functionMenu = createFunctionDropDownMenu(categories); + $scope.functionMenu = createFunctionDropDownMenu(categories); - var $input = $(inputTemplate); - var $button = $(buttonTemplate); - $input.appendTo(elem); - $button.appendTo(elem); + var $input = $(inputTemplate); + var $button = $(buttonTemplate); + $input.appendTo(elem); + $button.appendTo(elem); - $input.attr('data-provide', 'typeahead'); - $input.typeahead({ - source: allFunctions, - minLength: 1, - items: 10, - updater: function (value) { - var funcDef = metricFunctions.getFuncDef(value); - if (!funcDef) { - // try find close match - value = value.toLowerCase(); - funcDef = _.find(allFunctions, function(funcName) { - return funcName.toLowerCase().indexOf(value) === 0; - }); - - if (!funcDef) { return; } - } - - $scope.$apply(function() { - $scope.addFunction(funcDef); + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ + source: allFunctions, + minLength: 1, + items: 10, + updater: function (value) { + var funcDef = metricFunctions.getFuncDef(value); + if (!funcDef) { + // try find close match + value = value.toLowerCase(); + funcDef = _.find(allFunctions, function(funcName) { + return funcName.toLowerCase().indexOf(value) === 0; }); - $input.trigger('blur'); - return ''; + if (!funcDef) { return; } } - }); - $button.click(function() { - $button.hide(); - $input.show(); - $input.focus(); - }); + $scope.$apply(function() { + $scope.addFunction(funcDef); + }); - $input.keyup(function() { - elem.toggleClass('open', $input.val() === ''); - }); + $input.trigger('blur'); + return ''; + } + }); - $input.blur(function() { - // clicking the function dropdown menu wont - // work if you remove class at once - setTimeout(function() { - $input.val(''); - $input.hide(); - $button.show(); - elem.removeClass('open'); - }, 200); - }); + $button.click(function() { + $button.hide(); + $input.show(); + $input.focus(); + }); - $compile(elem.contents())($scope); - } - }; + $input.keyup(function() { + elem.toggleClass('open', $input.val() === ''); + }); + + $input.blur(function() { + // clicking the function dropdown menu wont + // work if you remove class at once + setTimeout(function() { + $input.val(''); + $input.hide(); + $button.show(); + elem.removeClass('open'); + }, 200); + }); + + $compile(elem.contents())($scope); + } + }; + }); + +function getAllFunctionNames(categories) { + return _.reduce(categories, function(list, category) { + _.each(category, function(func) { + list.push(func.name); }); + return list; + }, []); +} - function getAllFunctionNames(categories) { - return _.reduce(categories, function(list, category) { - _.each(category, function(func) { - list.push(func.name); - }); - return list; - }, []); - } +function createFunctionDropDownMenu(categories) { + return _.map(categories, function(list, category) { + return { + text: category, + submenu: _.map(list, function(value) { + return { + text: value.name, + click: "ctrl.addFunction('" + value.name + "')", + }; + }) + }; + }); +} - function createFunctionDropDownMenu(categories) { - return _.map(categories, function(list, category) { - return { - text: category, - submenu: _.map(list, function(value) { - return { - text: value.name, - click: "ctrl.addFunction('" + value.name + "')", - }; - }) - }; - }); - } -}); diff --git a/src/datasource-zabbix/metric-function-editor.directive.js b/src/datasource-zabbix/metric-function-editor.directive.js index a7af58c..e001194 100644 --- a/src/datasource-zabbix/metric-function-editor.directive.js +++ b/src/datasource-zabbix/metric-function-editor.directive.js @@ -1,247 +1,242 @@ -define([ - 'angular', - 'lodash', - 'jquery', -], -function (angular, _, $) { - 'use strict'; +import angular from 'angular'; +import _ from 'lodash'; +import $ from 'jquery'; - angular - .module('grafana.directives') - .directive('metricFunctionEditor', function($compile, templateSrv) { +/** @ngInject */ +angular + .module('grafana.directives') + .directive('metricFunctionEditor', function($compile, templateSrv) { - var funcSpanTemplate = '{{func.def.name}}('; - var paramTemplate = ''; + var funcSpanTemplate = '{{func.def.name}}('; + var paramTemplate = ''; - var funcControlsTemplate = - '
' + - '' + - '' + - '' + - '' + - '
'; + var funcControlsTemplate = + '
' + + '' + + '' + + '' + + '' + + '
'; - return { - restrict: 'A', - link: function postLink($scope, elem) { - var $funcLink = $(funcSpanTemplate); - var $funcControls = $(funcControlsTemplate); - var ctrl = $scope.ctrl; - var func = $scope.func; - var funcDef = func.def; - var scheduledRelink = false; - var paramCountAtLink = 0; + return { + restrict: 'A', + link: function postLink($scope, elem) { + var $funcLink = $(funcSpanTemplate); + var $funcControls = $(funcControlsTemplate); + var ctrl = $scope.ctrl; + var func = $scope.func; + var funcDef = func.def; + var scheduledRelink = false; + var paramCountAtLink = 0; - function clickFuncParam(paramIndex) { - /*jshint validthis:true */ + function clickFuncParam(paramIndex) { + /*jshint validthis:true */ - var $link = $(this); - var $input = $link.next(); + var $link = $(this); + var $input = $link.next(); - $input.val(func.params[paramIndex]); - $input.css('width', ($link.width() + 16) + 'px'); + $input.val(func.params[paramIndex]); + $input.css('width', ($link.width() + 16) + 'px'); - $link.hide(); - $input.show(); - $input.focus(); - $input.select(); + $link.hide(); + $input.show(); + $input.focus(); + $input.select(); - var typeahead = $input.data('typeahead'); - if (typeahead) { - $input.val(''); - typeahead.lookup(); - } + var typeahead = $input.data('typeahead'); + if (typeahead) { + $input.val(''); + typeahead.lookup(); + } + } + + function scheduledRelinkIfNeeded() { + if (paramCountAtLink === func.params.length) { + return; } - function scheduledRelinkIfNeeded() { - if (paramCountAtLink === func.params.length) { + if (!scheduledRelink) { + scheduledRelink = true; + setTimeout(function() { + relink(); + scheduledRelink = false; + }, 200); + } + } + + function inputBlur(paramIndex) { + /*jshint validthis:true */ + var $input = $(this); + var $link = $input.prev(); + var newValue = $input.val(); + + if (newValue !== '' || func.def.params[paramIndex].optional) { + $link.html(templateSrv.highlightVariablesAsHtml(newValue)); + + func.updateParam($input.val(), paramIndex); + scheduledRelinkIfNeeded(); + + $scope.$apply(function() { + ctrl.targetChanged(); + }); + + $input.hide(); + $link.show(); + } + } + + function inputKeyPress(paramIndex, e) { + /*jshint validthis:true */ + if(e.which === 13) { + inputBlur.call(this, paramIndex); + } + } + + function inputKeyDown() { + /*jshint validthis:true */ + this.style.width = (3 + this.value.length) * 8 + 'px'; + } + + function addTypeahead($input, paramIndex) { + $input.attr('data-provide', 'typeahead'); + + var options = funcDef.params[paramIndex].options; + if (funcDef.params[paramIndex].type === 'int') { + options = _.map(options, function(val) { return val.toString(); }); + } + + $input.typeahead({ + source: options, + minLength: 0, + items: 20, + updater: function (value) { + setTimeout(function() { + inputBlur.call($input[0], paramIndex); + }, 0); + return value; + } + }); + + var typeahead = $input.data('typeahead'); + typeahead.lookup = function () { + this.query = this.$element.val() || ''; + return this.process(this.source); + }; + } + + function toggleFuncControls() { + var targetDiv = elem.closest('.tight-form'); + + if (elem.hasClass('show-function-controls')) { + elem.removeClass('show-function-controls'); + targetDiv.removeClass('has-open-function'); + $funcControls.hide(); + return; + } + + elem.addClass('show-function-controls'); + targetDiv.addClass('has-open-function'); + + $funcControls.show(); + } + + function addElementsAndCompile() { + $funcControls.appendTo(elem); + $funcLink.appendTo(elem); + + _.each(funcDef.params, function(param, index) { + if (param.optional && func.params.length <= index) { return; } - if (!scheduledRelink) { - scheduledRelink = true; - setTimeout(function() { - relink(); - scheduledRelink = false; - }, 200); + if (index > 0) { + $(', ').appendTo(elem); } + + var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); + var $paramLink = $('' + paramValue + ''); + var $input = $(paramTemplate); + + paramCountAtLink++; + + $paramLink.appendTo(elem); + $input.appendTo(elem); + + $input.blur(_.partial(inputBlur, index)); + $input.keyup(inputKeyDown); + $input.keypress(_.partial(inputKeyPress, index)); + $paramLink.click(_.partial(clickFuncParam, index)); + + if (funcDef.params[index].options) { + addTypeahead($input, index); + } + + }); + + $(')').appendTo(elem); + + $compile(elem.contents())($scope); + } + + function ifJustAddedFocusFistParam() { + if ($scope.func.added) { + $scope.func.added = false; + setTimeout(function() { + elem.find('.graphite-func-param-link').first().click(); + }, 10); } + } - function inputBlur(paramIndex) { - /*jshint validthis:true */ - var $input = $(this); - var $link = $input.prev(); - var newValue = $input.val(); - - if (newValue !== '' || func.def.params[paramIndex].optional) { - $link.html(templateSrv.highlightVariablesAsHtml(newValue)); - - func.updateParam($input.val(), paramIndex); - scheduledRelinkIfNeeded(); + function registerFuncControlsToggle() { + $funcLink.click(toggleFuncControls); + } + function registerFuncControlsActions() { + $funcControls.click(function(e) { + var $target = $(e.target); + if ($target.hasClass('fa-remove')) { + toggleFuncControls(); $scope.$apply(function() { + ctrl.removeFunction($scope.func); + }); + return; + } + + if ($target.hasClass('fa-arrow-left')) { + $scope.$apply(function() { + _.move($scope.target.functions, $scope.$index, $scope.$index - 1); ctrl.targetChanged(); }); - - $input.hide(); - $link.show(); - } - } - - function inputKeyPress(paramIndex, e) { - /*jshint validthis:true */ - if(e.which === 13) { - inputBlur.call(this, paramIndex); - } - } - - function inputKeyDown() { - /*jshint validthis:true */ - this.style.width = (3 + this.value.length) * 8 + 'px'; - } - - function addTypeahead($input, paramIndex) { - $input.attr('data-provide', 'typeahead'); - - var options = funcDef.params[paramIndex].options; - if (funcDef.params[paramIndex].type === 'int') { - options = _.map(options, function(val) { return val.toString(); }); - } - - $input.typeahead({ - source: options, - minLength: 0, - items: 20, - updater: function (value) { - setTimeout(function() { - inputBlur.call($input[0], paramIndex); - }, 0); - return value; - } - }); - - var typeahead = $input.data('typeahead'); - typeahead.lookup = function () { - this.query = this.$element.val() || ''; - return this.process(this.source); - }; - } - - function toggleFuncControls() { - var targetDiv = elem.closest('.tight-form'); - - if (elem.hasClass('show-function-controls')) { - elem.removeClass('show-function-controls'); - targetDiv.removeClass('has-open-function'); - $funcControls.hide(); return; } - elem.addClass('show-function-controls'); - targetDiv.addClass('has-open-function'); - - $funcControls.show(); - } - - function addElementsAndCompile() { - $funcControls.appendTo(elem); - $funcLink.appendTo(elem); - - _.each(funcDef.params, function(param, index) { - if (param.optional && func.params.length <= index) { - return; - } - - if (index > 0) { - $(', ').appendTo(elem); - } - - var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); - var $paramLink = $('' + paramValue + ''); - var $input = $(paramTemplate); - - paramCountAtLink++; - - $paramLink.appendTo(elem); - $input.appendTo(elem); - - $input.blur(_.partial(inputBlur, index)); - $input.keyup(inputKeyDown); - $input.keypress(_.partial(inputKeyPress, index)); - $paramLink.click(_.partial(clickFuncParam, index)); - - if (funcDef.params[index].options) { - addTypeahead($input, index); - } - - }); - - $(')').appendTo(elem); - - $compile(elem.contents())($scope); - } - - function ifJustAddedFocusFistParam() { - if ($scope.func.added) { - $scope.func.added = false; - setTimeout(function() { - elem.find('.graphite-func-param-link').first().click(); - }, 10); + if ($target.hasClass('fa-arrow-right')) { + $scope.$apply(function() { + _.move($scope.target.functions, $scope.$index, $scope.$index + 1); + ctrl.targetChanged(); + }); + return; } - } - function registerFuncControlsToggle() { - $funcLink.click(toggleFuncControls); - } - - function registerFuncControlsActions() { - $funcControls.click(function(e) { - var $target = $(e.target); - if ($target.hasClass('fa-remove')) { - toggleFuncControls(); - $scope.$apply(function() { - ctrl.removeFunction($scope.func); - }); - return; - } - - if ($target.hasClass('fa-arrow-left')) { - $scope.$apply(function() { - _.move($scope.target.functions, $scope.$index, $scope.$index - 1); - ctrl.targetChanged(); - }); - return; - } - - if ($target.hasClass('fa-arrow-right')) { - $scope.$apply(function() { - _.move($scope.target.functions, $scope.$index, $scope.$index + 1); - ctrl.targetChanged(); - }); - return; - } - - if ($target.hasClass('fa-question-circle')) { - window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); - return; - } - }); - } - - function relink() { - elem.children().remove(); - - addElementsAndCompile(); - ifJustAddedFocusFistParam(); - registerFuncControlsToggle(); - registerFuncControlsActions(); - } - - relink(); + if ($target.hasClass('fa-question-circle')) { + window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); + return; + } + }); } - }; - }); + function relink() { + elem.children().remove(); -}); + addElementsAndCompile(); + ifJustAddedFocusFistParam(); + registerFuncControlsToggle(); + registerFuncControlsActions(); + } + + relink(); + } + }; + + }); From af59f87246ebbbcd522950d618d982a1ba59dda1 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 15:09:13 +0300 Subject: [PATCH 13/24] Fixed annotations. --- src/datasource-zabbix/datasource.js | 16 +++------------ .../partials/annotations.editor.html | 16 +++++++-------- src/datasource-zabbix/zabbixAPI.service.js | 20 ++++++++++++++++--- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 8e5d250..1291c93 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -334,7 +334,7 @@ export class ZabbixAPIDatasource { var to = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000); var annotation = options.annotation; var self = this; - var showEvents = annotation.showOkEvents ? [0, 1] : 1; + var showOkEvents = annotation.showOkEvents ? [0, 1] : 1; var buildQuery = self.queryProcessor.buildTriggerQuery(this.templateSrv.replace(annotation.group), this.templateSrv.replace(annotation.host), @@ -343,7 +343,7 @@ export class ZabbixAPIDatasource { return self.zabbixAPI.getTriggers(query.groupids, query.hostids, query.applicationids, - showEvents) + true) .then(function(triggers) { // Filter triggers by description @@ -363,17 +363,7 @@ export class ZabbixAPIDatasource { }); var objectids = _.map(triggers, 'triggerid'); - var params = { - output: 'extend', - time_from: from, - time_till: to, - objectids: objectids, - select_acknowledges: 'extend', - selectHosts: 'extend', - value: showEvents - }; - - return self.zabbixAPI.request('event.get', params) + return self.zabbixAPI.getEvents(objectids, from, to, showOkEvents) .then(function (events) { var indexedTriggers = _.indexBy(triggers, 'triggerid'); diff --git a/src/datasource-zabbix/partials/annotations.editor.html b/src/datasource-zabbix/partials/annotations.editor.html index 42e0517..c7676be 100644 --- a/src/datasource-zabbix/partials/annotations.editor.html +++ b/src/datasource-zabbix/partials/annotations.editor.html @@ -8,7 +8,7 @@
  • @@ -16,7 +16,7 @@
  • @@ -29,7 +29,7 @@
  • @@ -37,7 +37,7 @@
  • @@ -52,10 +52,10 @@
    - +
    - - - + + + diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index e57a436..7661c0c 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -312,7 +312,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { return this.request('service.getsla', params); } - getTriggers(groupids, hostids, applicationids, showEvents) { + getTriggers(groupids, hostids, applicationids, showAll) { var params = { output: 'extend', groupids: groupids, @@ -332,13 +332,27 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { selectLastEvent: 'extend' }; - if (showEvents) { - params.filter.value = showEvents; + if (showAll) { + params.filter = {}; } return this.request('trigger.get', params); } + getEvents(objectids, from, to, showOkEvents) { + var params = { + output: 'extend', + time_from: from, + time_till: to, + objectids: objectids, + select_acknowledges: 'extend', + selectHosts: 'extend', + value: showOkEvents + }; + + return this.request('event.get', params); + } + getAcknowledges(eventids) { var params = { output: 'extend', From c6f854e9489467a14eacb9ad9b82b15b81871758 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 15:28:24 +0300 Subject: [PATCH 14/24] Fixed grunt tasks (copy files for trigger panel). --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index f9b5273..766f87e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,7 +15,7 @@ module.exports = function(grunt) { expand: true, src: [ '**/*', - '!**/*.js', + '!datasource-zabbix/*.js', '!**/*.scss' ], dest: 'dist/' @@ -46,7 +46,7 @@ module.exports = function(grunt) { cwd: 'src', expand: true, src: [ - '**/**/*.js' + 'datasource-zabbix/*.js' ], dest: 'dist/' }] From a533f7f6b3ef4d8c69dc903fba1eb2ead0ce516c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 17:15:48 +0300 Subject: [PATCH 15/24] Trigger panel refactor. Migrated to ES6. --- Gruntfile.js | 4 +- src/panel-triggers/editor.js | 116 +++++------ src/panel-triggers/module.js | 381 +++++++++++++++++------------------ 3 files changed, 243 insertions(+), 258 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 766f87e..1211d9d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,6 +16,7 @@ module.exports = function(grunt) { src: [ '**/*', '!datasource-zabbix/*.js', + '!panel-triggers/*.js', '!**/*.scss' ], dest: 'dist/' @@ -46,7 +47,8 @@ module.exports = function(grunt) { cwd: 'src', expand: true, src: [ - 'datasource-zabbix/*.js' + 'datasource-zabbix/*.js', + 'panel-triggers/*.js', ], dest: 'dist/' }] diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index 55c77a7..160ce4f 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -11,15 +11,13 @@ * Licensed under the Apache License, Version 2.0 */ -define([ - 'angular', - 'lodash', - 'jquery' -], -function (angular, _, $) { - 'use strict'; +import _ from 'lodash'; +import $ from 'jquery'; - function TriggerPanelEditorCtrl($scope, $q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) { +class TriggerPanelEditorCtrl{ + + /** @ngInject */ + constructor($scope, $q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) { $scope.editor = this; this.panelCtrl = $scope.ctrl; this.panel = this.panelCtrl.panel; @@ -80,35 +78,28 @@ function (angular, _, $) { }); } - var p = TriggerPanelEditorCtrl.prototype; - - // Get list of metric names for bs-typeahead directive - function getMetricNames(scope, metricList) { - return _.uniq(_.map(scope.metric[metricList], 'name')); - } - - p.initFilters = function () { + initFilters() { this.filterGroups(); this.filterHosts(); this.filterApplications(); - }; + } - p.filterGroups = function() { + filterGroups() { var self = this; this.datasource.queryProcessor.filterGroups().then(function(groups) { self.metric.groupList = groups; }); - }; + } - p.filterHosts = function() { + filterHosts() { var self = this; var groupFilter = this.templateSrv.replace(this.panel.triggers.group.filter); this.datasource.queryProcessor.filterHosts(groupFilter).then(function(hosts) { self.metric.filteredHosts = hosts; }); - }; + } - p.filterApplications = function() { + filterApplications() { var self = this; var groupFilter = this.templateSrv.replace(this.panel.triggers.group.filter); var hostFilter = this.templateSrv.replace(this.panel.triggers.host.filter); @@ -116,51 +107,41 @@ function (angular, _, $) { .then(function(apps) { self.metric.filteredApplications = apps; }); - }; + } - p.onTargetPartChange = function(targetPart) { + onTargetPartChange(targetPart) { var regexStyle = {'color': '#CCA300'}; targetPart.isRegex = isRegex(targetPart.filter); targetPart.style = targetPart.isRegex ? regexStyle : {}; - }; - - function isRegex(str) { - // Pattern for testing regex - var regexPattern = /^\/(.*)\/([gmi]*)$/m; - return regexPattern.test(str); } - p.parseTarget = function() { + parseTarget() { this.initFilters(); var newTarget = _.cloneDeep(this.panel.triggers); if (!_.isEqual(this.oldTarget, this.panel.triggers)) { this.oldTarget = newTarget; this.panelCtrl.refreshData(); } - }; + } - p.refreshTriggerSeverity = function() { + refreshTriggerSeverity() { _.each(this.triggerList, function(trigger) { trigger.color = this.panel.triggerSeverity[trigger.priority].color; trigger.severity = this.panel.triggerSeverity[trigger.priority].severity; }); this.panelCtrl.refreshData(); - }; - - p.datasourceChanged = function() { - this.panelCtrl.refreshData(); - }; - - p.changeTriggerSeverityColor = function(trigger, color) { - this.panel.triggerSeverity[trigger.priority].color = color; - this.refreshTriggerSeverity(); - }; - - function getTriggerIndexForElement(el) { - return el.parents('[data-trigger-index]').data('trigger-index'); } - p.openTriggerColorSelector = function(event) { + datasourceChanged() { + this.panelCtrl.refreshData(); + } + + changeTriggerSeverityColor(trigger, color) { + this.panel.triggerSeverity[trigger.priority].color = color; + this.refreshTriggerSeverity(); + } + + openTriggerColorSelector(event) { var el = $(event.currentTarget); var index = getTriggerIndexForElement(el); var popoverScope = this.$new(); @@ -173,9 +154,9 @@ function (angular, _, $) { templateUrl: 'public/plugins/triggers/trigger.colorpicker.html', scope: popoverScope }); - }; + } - p.openOkEventColorSelector = function(event) { + openOkEventColorSelector(event) { var el = $(event.currentTarget); var popoverScope = this.$new(); popoverScope.trigger = {color: this.panel.okEventColor}; @@ -190,16 +171,31 @@ function (angular, _, $) { templateUrl: 'public/plugins/triggers/trigger.colorpicker.html', scope: popoverScope }); - }; + } +} - var triggerPanelEditor = function() { - return { - restrict: 'E', - scope: true, - templateUrl: 'public/plugins/triggers/editor.html', - controller: TriggerPanelEditorCtrl, - }; - }; +// Get list of metric names for bs-typeahead directive +function getMetricNames(scope, metricList) { + return _.uniq(_.map(scope.metric[metricList], 'name')); +} - return triggerPanelEditor; -}); \ No newline at end of file +function getTriggerIndexForElement(el) { + return el.parents('[data-trigger-index]').data('trigger-index'); +} + +function isRegex(str) { + // Pattern for testing regex + var regexPattern = /^\/(.*)\/([gmi]*)$/m; + return regexPattern.test(str); +} + +/** @ngInject */ +export function triggerPanelEditor($q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) { + 'use strict'; + return { + restrict: 'E', + scope: true, + templateUrl: 'public/plugins/triggers/editor.html', + controller: TriggerPanelEditorCtrl, + }; +} diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index 47d1b04..fa35e32 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -11,220 +11,207 @@ * Licensed under the Apache License, Version 2.0 */ -define([ - 'app/plugins/sdk', - 'angular', - 'lodash', - 'jquery', - 'moment', - './editor' -], -function (sdk, angular, _, $, moment, triggerPanelEditor) { - 'use strict'; +import _ from 'lodash'; +import moment from 'moment'; +import {PanelCtrl} from 'app/plugins/sdk'; +import {triggerPanelEditor} from './editor'; - var defaultSeverity = [ - { priority: 0, severity: 'Not classified', color: '#B7DBAB', show: true }, - { priority: 1, severity: 'Information', color: '#82B5D8', show: true }, - { priority: 2, severity: 'Warning', color: '#E5AC0E', show: true }, - { priority: 3, severity: 'Average', color: '#C15C17', show: true }, - { priority: 4, severity: 'High', color: '#BF1B00', show: true }, - { priority: 5, severity: 'Disaster', color: '#890F02', show: true } - ]; +var defaultSeverity = [ + { priority: 0, severity: 'Not classified', color: '#B7DBAB', show: true }, + { priority: 1, severity: 'Information', color: '#82B5D8', show: true }, + { priority: 2, severity: 'Warning', color: '#E5AC0E', show: true }, + { priority: 3, severity: 'Average', color: '#C15C17', show: true }, + { priority: 4, severity: 'High', color: '#BF1B00', show: true }, + { priority: 5, severity: 'Disaster', color: '#890F02', show: true } +]; - var panelDefaults = { - datasource: null, - triggers: { - group: {filter: ""}, - host: {filter: ""}, - application: {filter: ""}, - trigger: {filter: ""} - }, - hostField: true, - statusField: false, - severityField: false, - lastChangeField: true, - ageField: true, - infoField: true, - limit: 10, - showTriggers: 'all triggers', - sortTriggersBy: { text: 'last change', value: 'lastchange' }, - showEvents: { text: 'Problem events', value: '1' }, - triggerSeverity: defaultSeverity, - okEventColor: '#890F02', - }; +var panelDefaults = { + datasource: null, + triggers: { + group: {filter: ""}, + host: {filter: ""}, + application: {filter: ""}, + trigger: {filter: ""} + }, + hostField: true, + statusField: false, + severityField: false, + lastChangeField: true, + ageField: true, + infoField: true, + limit: 10, + showTriggers: 'all triggers', + sortTriggersBy: { text: 'last change', value: 'lastchange' }, + showEvents: { text: 'Problem events', value: '1' }, + triggerSeverity: defaultSeverity, + okEventColor: '#890F02', +}; - var triggerStatusMap = { - '0': 'OK', - '1': 'Problem' - }; +var triggerStatusMap = { + '0': 'OK', + '1': 'Problem' +}; - var TriggerPanelCtrl = (function(_super) { +class TriggerPanelCtrl extends PanelCtrl { - /** @ngInject */ - function TriggerPanelCtrl($scope, $injector, $q, $element, datasourceSrv) { - _super.call(this, $scope, $injector); - this.datasourceSrv = datasourceSrv; - this.triggerStatusMap = triggerStatusMap; + /** @ngInject */ + constructor($scope, $injector, $q, $element, datasourceSrv) { + super($scope, $injector); + this.datasourceSrv = datasourceSrv; + this.triggerStatusMap = triggerStatusMap; - // Load panel defaults - _.defaults(this.panel, panelDefaults); + // Load panel defaults + _.defaults(this.panel, panelDefaults); - this.triggerList = []; - this.refreshData(); - } + this.triggerList = []; + this.refreshData(); + } - TriggerPanelCtrl.templateUrl = 'module.html'; + // Add panel editor + initEditMode() { + super.initEditMode(); + this.icon = "fa fa-lightbulb-o"; + this.addEditorTab('Options', triggerPanelEditor, 2); + } - TriggerPanelCtrl.prototype = Object.create(_super.prototype); - TriggerPanelCtrl.prototype.constructor = TriggerPanelCtrl; + refreshData() { + var self = this; - // Add panel editor - TriggerPanelCtrl.prototype.initEditMode = function() { - _super.prototype.initEditMode(); - this.icon = "fa fa-lightbulb-o"; - this.addEditorTab('Options', triggerPanelEditor, 2); - }; + // Load datasource + return this.datasourceSrv.get(this.panel.datasource).then(datasource => { + var zabbix = datasource.zabbixAPI; + var queryProcessor = datasource.queryProcessor; + var triggerFilter = self.panel.triggers; + var showEvents = self.panel.showEvents.value; + var buildQuery = queryProcessor.buildTriggerQuery(triggerFilter.group.filter, + triggerFilter.host.filter, + triggerFilter.application.filter); + return buildQuery.then(query => { + return zabbix.getTriggers(query.groupids, + query.hostids, + query.applicationids, + showEvents) + .then(triggers => { + return _.map(triggers, trigger => { + var triggerObj = trigger; - TriggerPanelCtrl.prototype.refreshData = function() { - var self = this; + // Format last change and age + trigger.lastchangeUnix = Number(trigger.lastchange); + var timestamp = moment.unix(trigger.lastchangeUnix); + if (self.panel.customLastChangeFormat) { + // User defined format + triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat); + } else { + triggerObj.lastchange = timestamp.format(self.defaultTimeFormat); + } + triggerObj.age = timestamp.fromNow(true); - // Load datasource - return this.datasourceSrv.get(this.panel.datasource).then(function (datasource) { - var zabbix = datasource.zabbixAPI; - var queryProcessor = datasource.queryProcessor; - var triggerFilter = self.panel.triggers; - var showEvents = self.panel.showEvents.value; - var buildQuery = queryProcessor.buildTriggerQuery(triggerFilter.group.filter, - triggerFilter.host.filter, - triggerFilter.application.filter); - return buildQuery.then(function(query) { - return zabbix.getTriggers(query.groupids, - query.hostids, - query.applicationids, - showEvents) - .then(function(triggers) { - return _.map(triggers, function (trigger) { - var triggerObj = trigger; + // Set color + if (trigger.value === '1') { + triggerObj.color = self.panel.triggerSeverity[trigger.priority].color; + } else { + triggerObj.color = self.panel.okEventColor; + } - // Format last change and age - trigger.lastchangeUnix = Number(trigger.lastchange); - var timestamp = moment.unix(trigger.lastchangeUnix); - if (self.panel.customLastChangeFormat) { - // User defined format - triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat); - } else { - triggerObj.lastchange = timestamp.format(self.defaultTimeFormat); - } - triggerObj.age = timestamp.fromNow(true); - - // Set color - if (trigger.value === '1') { - triggerObj.color = self.panel.triggerSeverity[trigger.priority].color; - } else { - triggerObj.color = self.panel.okEventColor; - } - - triggerObj.severity = self.panel.triggerSeverity[trigger.priority].severity; - return triggerObj; - }); - }) - .then(function (triggerList) { - - // Request acknowledges for trigger - var eventids = _.map(triggerList, function(trigger) { - return trigger.lastEvent.eventid; - }); - - return zabbix.getAcknowledges(eventids) - .then(function (events) { - - // Map events to triggers - _.each(triggerList, function(trigger) { - var event = _.find(events, function(event) { - return event.eventid === trigger.lastEvent.eventid; - }); - - if (event) { - trigger.acknowledges = _.map(event.acknowledges, function (ack) { - var time = new Date(+ack.clock * 1000); - ack.time = time.toLocaleString(); - ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; - return ack; - }); - } - }); - - // Filter triggers by description - var triggerFilter = self.panel.triggers.trigger.filter; - if (triggerFilter) { - triggerList = filterTriggers(triggerList, triggerFilter); - } - - // Filter acknowledged triggers - if (self.panel.showTriggers === 'unacknowledged') { - triggerList = _.filter(triggerList, function (trigger) { - return !trigger.acknowledges; - }); - } else if (self.panel.showTriggers === 'acknowledged') { - triggerList = _.filter(triggerList, 'acknowledges'); - } else { - triggerList = triggerList; - } - - // Filter triggers by severity - triggerList = _.filter(triggerList, function (trigger) { - return self.panel.triggerSeverity[trigger.priority].show; - }); - - // Sort triggers - if (self.panel.sortTriggersBy.value === 'priority') { - triggerList = _.sortBy(triggerList, 'priority').reverse(); - } else { - triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse(); - } - - // Limit triggers number - self.triggerList = _.first(triggerList, self.panel.limit); - - self.renderingCompleted(); - }); + triggerObj.severity = self.panel.triggerSeverity[trigger.priority].severity; + return triggerObj; }); - }); + }) + .then(triggerList => { + + // Request acknowledges for trigger + var eventids = _.map(triggerList, trigger => { + return trigger.lastEvent.eventid; + }); + + return zabbix.getAcknowledges(eventids) + .then(events => { + + // Map events to triggers + _.each(triggerList, trigger => { + var event = _.find(events, event => { + return event.eventid === trigger.lastEvent.eventid; + }); + + if (event) { + trigger.acknowledges = _.map(event.acknowledges, ack => { + var time = new Date(+ack.clock * 1000); + ack.time = time.toLocaleString(); + ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; + return ack; + }); + } + }); + + // Filter triggers by description + var triggerFilter = self.panel.triggers.trigger.filter; + if (triggerFilter) { + triggerList = filterTriggers(triggerList, triggerFilter); + } + + // Filter acknowledged triggers + if (self.panel.showTriggers === 'unacknowledged') { + triggerList = _.filter(triggerList, trigger => { + return !trigger.acknowledges; + }); + } else if (self.panel.showTriggers === 'acknowledged') { + triggerList = _.filter(triggerList, 'acknowledges'); + } else { + triggerList = triggerList; + } + + // Filter triggers by severity + triggerList = _.filter(triggerList, trigger => { + return self.panel.triggerSeverity[trigger.priority].show; + }); + + // Sort triggers + if (self.panel.sortTriggersBy.value === 'priority') { + triggerList = _.sortBy(triggerList, 'priority').reverse(); + } else { + triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse(); + } + + // Limit triggers number + self.triggerList = _.first(triggerList, self.panel.limit); + + self.renderingCompleted(); + }); + }); }); - }; + }); + } +} - function filterTriggers(triggers, triggerFilter) { - if (isRegex(triggerFilter)) { - return _.filter(triggers, function(trigger) { - return buildRegex(triggerFilter).test(trigger.description); - }); - } else { - return _.filter(triggers, function(trigger) { - return trigger.description === triggerFilter; - }); - } - } +TriggerPanelCtrl.templateUrl = 'module.html'; - function isRegex(str) { - // Pattern for testing regex - var regexPattern = /^\/(.*)\/([gmi]*)$/m; - return regexPattern.test(str); - } +function filterTriggers(triggers, triggerFilter) { + if (isRegex(triggerFilter)) { + return _.filter(triggers, function(trigger) { + return buildRegex(triggerFilter).test(trigger.description); + }); + } else { + return _.filter(triggers, function(trigger) { + return trigger.description === triggerFilter; + }); + } +} - function buildRegex(str) { - var regexPattern = /^\/(.*)\/([gmi]*)$/m; - var matches = str.match(regexPattern); - var pattern = matches[1]; - var flags = matches[2] !== "" ? matches[2] : undefined; - return new RegExp(pattern, flags); - } +function isRegex(str) { + // Pattern for testing regex + var regexPattern = /^\/(.*)\/([gmi]*)$/m; + return regexPattern.test(str); +} - return TriggerPanelCtrl; +function buildRegex(str) { + var regexPattern = /^\/(.*)\/([gmi]*)$/m; + var matches = str.match(regexPattern); + var pattern = matches[1]; + var flags = matches[2] !== "" ? matches[2] : undefined; + return new RegExp(pattern, flags); +} - })(sdk.PanelCtrl); - - return { - PanelCtrl: TriggerPanelCtrl - }; - -}); +export { + TriggerPanelCtrl, + TriggerPanelCtrl as PanelCtrl +}; From 29fbfbe2f8258eb4fabe9ef1ae0f9593bf3248bc Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 17:27:32 +0300 Subject: [PATCH 16/24] Fixed default last change time format. --- src/panel-triggers/editor.js | 3 +-- src/panel-triggers/module.js | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index 160ce4f..8a16b69 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -53,8 +53,7 @@ class TriggerPanelEditorCtrl{ var scopeDefaults = { metric: {}, inputStyles: {}, - oldTarget: _.cloneDeep(this.panel.triggers), - defaultTimeFormat: "DD MMM YYYY HH:mm:ss" + oldTarget: _.cloneDeep(this.panel.triggers) }; _.defaults(this, scopeDefaults); diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index fa35e32..b263c7a 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -52,6 +52,8 @@ var triggerStatusMap = { '1': 'Problem' }; +var defaultTimeFormat = "DD MMM YYYY HH:mm:ss"; + class TriggerPanelCtrl extends PanelCtrl { /** @ngInject */ @@ -59,6 +61,7 @@ class TriggerPanelCtrl extends PanelCtrl { super($scope, $injector); this.datasourceSrv = datasourceSrv; this.triggerStatusMap = triggerStatusMap; + this.defaultTimeFormat = defaultTimeFormat; // Load panel defaults _.defaults(this.panel, panelDefaults); From 0aa425b33cf1fa0d50e437e9477ae48b3040bf24 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 17:31:17 +0300 Subject: [PATCH 17/24] Removed angular dependencies injection from triggerPanelEditor(). --- src/panel-triggers/editor.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index 8a16b69..8274570 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -188,9 +188,7 @@ function isRegex(str) { return regexPattern.test(str); } -/** @ngInject */ -export function triggerPanelEditor($q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) { - 'use strict'; +export function triggerPanelEditor() { return { restrict: 'E', scope: true, From 404c2afd461b50d6f1f28e86dc0f94f8d99e84a2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 18:09:40 +0300 Subject: [PATCH 18/24] Fixed annotation editor styles (used native Grafana 3.0). --- .../partials/annotations.editor.html | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/src/datasource-zabbix/partials/annotations.editor.html b/src/datasource-zabbix/partials/annotations.editor.html index c7676be..6e2e495 100644 --- a/src/datasource-zabbix/partials/annotations.editor.html +++ b/src/datasource-zabbix/partials/annotations.editor.html @@ -1,61 +1,65 @@ -
    -
    -
    Filter Triggers
    -
    -
      -
    • - Group -
    • -
    • - -
    • -
    • - Host -
    • -
    • - -
    • -
    -
    +
    +
    Filter Triggers
    +
    +
    + Group + +
    -
    -
      -
    • - Application -
    • -
    • - -
    • -
    • - Trigger -
    • -
    • - -
    • -
    -
    -
    -
    -
    -
    -
    -
    Options
    -
    - - +
    + Host + + +
    +
    +
    +
    + Application + + +
    + +
    + Trigger + +
    - - - +
    +
    +
    +
    + Minimum severity +
    + +
    +
    +
    +
    +
    Options
    +
    + + +
    From c9dfbe646c3a5f7aa1c90abd3d17c5dee28b25bb Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 19:17:21 +0300 Subject: [PATCH 19/24] Added custom sass styles for triggers panel. --- Gruntfile.js | 19 +++- package.json | 1 + src/panel-triggers/module.html | 24 ++--- src/panel-triggers/module.js | 1 + src/panel-triggers/sass/panel_triggers.scss | 108 ++++++++++++++++++++ 5 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 src/panel-triggers/sass/panel_triggers.scss diff --git a/Gruntfile.js b/Gruntfile.js index 1211d9d..3e7ff36 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -55,7 +55,24 @@ module.exports = function(grunt) { }, }, + sass: { + options: { + sourceMap: true + }, + dist: { + files: { + 'dist/panel-triggers/css/panel_triggers.css' : 'src/panel-triggers/sass/panel_triggers.scss', + } + } + } + }); - grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'babel']); + grunt.registerTask('default', [ + 'clean', + 'copy:src_to_dist', + 'copy:pluginDef', + 'babel', + 'sass' + ]); }; diff --git a/package.json b/package.json index 64cde5b..b4b96f1 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "grunt": "~0.4.5", "babel": "~6.5.1", "grunt-babel": "~6.0.0", + "grunt-sass": "^1.1.0", "grunt-contrib-copy": "~0.8.2", "grunt-contrib-watch": "^0.6.1", "grunt-contrib-uglify": "~0.11.0", diff --git a/src/panel-triggers/module.html b/src/panel-triggers/module.html index b8d2eb3..4aad09a 100644 --- a/src/panel-triggers/module.html +++ b/src/panel-triggers/module.html @@ -1,31 +1,31 @@ -
    -
    -
    - +
    +
    +
    +
    @@ -133,4 +133,4 @@
    -
    +
    Host
    -
    Status
    +
    Status
    -
    Severity
    +
    Severity
    -
    Issue
    +
    Issue
    -
    Last change
    +
    Last change
    -
    Age
    +
    Age
    -
    Info
    +
    Info
    - + diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index b263c7a..72cfdc9 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -15,6 +15,7 @@ import _ from 'lodash'; import moment from 'moment'; import {PanelCtrl} from 'app/plugins/sdk'; import {triggerPanelEditor} from './editor'; +import './css/panel_triggers.css!'; var defaultSeverity = [ { priority: 0, severity: 'Not classified', color: '#B7DBAB', show: true }, diff --git a/src/panel-triggers/sass/panel_triggers.scss b/src/panel-triggers/sass/panel_triggers.scss new file mode 100644 index 0000000..478f7dd --- /dev/null +++ b/src/panel-triggers/sass/panel_triggers.scss @@ -0,0 +1,108 @@ +$tight-form-func-bg: #333; +$blue: #33B5E5; +$dark-2: #1f1d1d; +$body-bg: rgb(20,20,20); + +$grafanaListAccent: lighten($dark-2, 2%); + +.triggers-panel-wrapper { + .panel-content { + padding: 0; + } + .panel-title-container { + padding-bottom: 4px; + } +} + +.triggers-panel-scroll { + overflow: auto; +} + +.triggers-panel-container { + padding-top: 2.2em; + position: relative; +} + +.triggers-panel-footer { + text-align: center; + font-size: 90%; + line-height: 2px; + + ul { + position: relative; + display: inline-block; + margin-left: 0; + margin-bottom: 0; + } + ul > li { + display: inline; // Remove list-style and block-level defaults + } + ul > li > a { + float: left; // Collapse white-space + padding: 4px 12px; + text-decoration: none; + border-left-width: 0; + + &:hover { + background-color: $tight-form-func-bg; + } + + &.active { + font-weight: bold; + color: $blue; + } + } +} + +.triggers-panel-table { + width: 100%; + border-collapse: collapse; + + th { + padding: 0; + + &:first-child { + .triggers-panel-table-header-inner { + padding-left: 15px; + } + } + } + + td { + padding: 0.45em 0 0.45em 1.1em; + border-bottom: 2px solid $body-bg; + border-right: 2px solid $body-bg; + + &:first-child { + padding-left: 15px; + } + &:last-child { + border-right: none; + } + } +} + +.triggers-panel-header-bg { + background: $grafanaListAccent; + border-top: 2px solid $body-bg; + border-bottom: 2px solid $body-bg; + height: 2.0em; + position: absolute; + top: 0; + right: 0; + left: 0; +} + +.triggers-panel-table-header-inner { + padding: 0.45em 0 0.45em 1.1em; + text-align: left; + color: $blue; + position: absolute; + top: 0; +} + +.triggers-panel-width-hack { + visibility: hidden; + height: 0px; + line-height: 0px; +} From cf07d7c2dfd58a5e704b2db6b97b075ce971c8d9 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 25 Mar 2016 19:40:22 +0300 Subject: [PATCH 20/24] Working on cache rewriting (remove config caching). --- src/datasource-zabbix/query.controller.js | 112 ++++++++++++----- .../queryProcessor.service.js | 115 +++++++++++++++--- src/datasource-zabbix/zabbixAPI.service.js | 49 ++++++-- src/datasource-zabbix/zabbixCache.service.js | 54 +++++--- 4 files changed, 252 insertions(+), 78 deletions(-) diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index 98354e2..921da2f 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -14,6 +14,10 @@ export class ZabbixQueryController extends QueryCtrl { // Call superclass constructor super($scope, $injector); + this.zabbix = this.datasource.zabbixAPI; + this.cache = this.datasource.zabbixCache; + this.$q = $q; + this.editorModes = { 0: 'num', 1: 'itservice', @@ -22,9 +26,9 @@ export class ZabbixQueryController extends QueryCtrl { // Map functions for bs-typeahead this.getGroupNames = _.partial(getMetricNames, this, 'groupList'); - this.getHostNames = _.partial(getMetricNames, this, 'filteredHosts'); - this.getApplicationNames = _.partial(getMetricNames, this, 'filteredApplications'); - this.getItemNames = _.partial(getMetricNames, this, 'filteredItems'); + this.getHostNames = _.partial(getMetricNames, this, 'hostList'); + this.getApplicationNames = _.partial(getMetricNames, this, 'appList'); + this.getItemNames = _.partial(getMetricNames, this, 'itemList'); this.init = function() { @@ -32,7 +36,8 @@ export class ZabbixQueryController extends QueryCtrl { var target = this.target; var scopeDefaults = { - metric: {} + metric: {}, + oldTarget: _.cloneDeep(this.target) }; _.defaults(this, scopeDefaults); @@ -85,49 +90,86 @@ export class ZabbixQueryController extends QueryCtrl { } initFilters() { - this.filterGroups(); - this.filterHosts(); - this.filterApplications(); - this.filterItems(); + var self = this; + return this.$q.when(this.suggestGroups()) + .then(() => {return self.suggestHosts();}) + .then(() => {return self.suggestApps();}) + .then(() => {return self.suggestItems();}); } - filterHosts() { + suggestGroups() { var self = this; - var groupFilter = this.templateSrv.replace(this.target.group.filter); - this.datasource.queryProcessor.filterHosts(groupFilter).then(function(hosts) { - self.metric.filteredHosts = hosts; - }); - } - - filterGroups() { - var self = this; - this.datasource.queryProcessor.filterGroups().then(function(groups) { + return this.cache.getGroups().then(groups => { self.metric.groupList = groups; + return groups; }); } - filterApplications() { + suggestHosts() { var self = this; var groupFilter = this.templateSrv.replace(this.target.group.filter); - var hostFilter = this.templateSrv.replace(this.target.host.filter); - this.datasource.queryProcessor.filterApplications(groupFilter, hostFilter) - .then(function(apps) { - self.metric.filteredApplications = apps; + return this.datasource.queryProcessor + .filterGroups(self.metric.groupList, groupFilter) + .then(groups => { + var groupids = _.map(groups, 'groupid'); + return self.zabbix + .getHostsByGroups(groupids) + .then(hosts => { + self.metric.hostList = hosts; + return hosts; + }); }); } - filterItems() { + suggestApps() { var self = this; - var item_type = this.editorModes[this.target.mode]; - var groupFilter = this.templateSrv.replace(this.target.group.filter); var hostFilter = this.templateSrv.replace(this.target.host.filter); - var appFilter = this.templateSrv.replace(this.target.application.filter); - this.datasource.queryProcessor.filterItems(groupFilter, hostFilter, appFilter, - item_type, this.target.showDisabledItems).then(function(items) { - self.metric.filteredItems = items; + return this.datasource.queryProcessor + .filterHosts(self.metric.hostList, hostFilter) + .then(hosts => { + var hostids = _.map(hosts, 'hostid'); + return self.zabbix + .getApps(hostids) + .then(apps => { + return self.metric.appList = apps; + }); }); } + suggestItems() { + var self = this; + var appFilter = this.templateSrv.replace(this.target.application.filter); + if (appFilter) { + // Filter by applications + return this.datasource.queryProcessor + .filterApps(self.metric.appList, appFilter) + .then(apps => { + var appids = _.map(apps, 'applicationid'); + return self.zabbix + .getItems(undefined, appids) + .then(items => { + if (!self.target.showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + self.metric.itemList = items; + return items; + }); + }); + } else { + // Return all items belonged to selected hosts + var hostids = _.map(self.metric.hostList, 'hostid'); + return self.zabbix + .getItems(hostids) + .then(items => { + if (!self.target.showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + self.metric.itemList = items; + return items; + }); + } + } + onTargetPartChange(targetPart) { var regexStyle = {'color': '#CCA300'}; targetPart.isRegex = utils.isRegex(targetPart.filter); @@ -135,9 +177,13 @@ export class ZabbixQueryController extends QueryCtrl { } onTargetBlur() { - this.initFilters(); - this.parseTarget(); - this.panelCtrl.refresh(); + var newTarget = _.cloneDeep(this.target); + if (!_.isEqual(this.oldTarget, this.target)) { + this.oldTarget = newTarget; + this.initFilters(); + this.parseTarget(); + this.panelCtrl.refresh(); + } } parseTarget() { diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index d447e55..b229190 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -39,31 +39,26 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { } } - filterGroups(groupFilter) { - return this.cache.getGroups().then(function(groupList) { - return groupList; - }); + filterGroups(groups, groupFilter) { + return this.$q.when( + findByFilter(groups, groupFilter) + ); } /** * Get list of host belonging to given groups. * @return list of hosts */ - filterHosts(groupFilter) { - var self = this; - return this.cache.getGroups().then(function(groups) { - groups = findByFilter(groups, groupFilter); - var hostids = _.flatten(_.map(groups, 'hosts')); - if (hostids.length) { - return self.cache.getIndexedHosts().then(function(hosts) { - return _.map(hostids, function(hostid) { - return hosts[hostid]; - }); - }); - } else { - return []; - } - }); + filterHosts(hosts, hostFilter) { + return this.$q.when( + findByFilter(hosts, hostFilter) + ); + } + + filterApps(apps, appFilter) { + return this.$q.when( + findByFilter(apps, appFilter) + ); } /** @@ -151,10 +146,65 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { }); } + buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, showDisabledItems) { + var self = this; + return this.cache + .getGroups() + .then(groups => { + return findByFilter(groups, groupFilter); + }) + .then(groups => { + var groupids = _.map(groups, 'groupid'); + return self.cache + .getHosts(groupids) + .then(hosts => { + return findByFilter(hosts, hostFilter); + }); + }) + .then(hosts => { + var hostids = _.map(hosts, 'hostid'); + if (appFilter) { + return self.cache + .getApps(hostids) + .then(apps => { + // Use getByFilter for proper item filtering + return getByFilter(apps, appFilter); + }); + } else { + return { + appFilterEmpty: true, + hostids: hostids + }; + } + }) + .then(apps => { + if (apps.appFilterEmpty) { + return self.cache + .getItems(apps.hostids) + .then(items => { + if (showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + return getByFilter(items, itemFilter); + }); + } else { + var appids = _.map(apps, 'applicationid'); + return self.cache + .getItems(undefined, appids) + .then(items => { + if (showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + return getByFilter(items, itemFilter); + }); + } + }); + } + /** * Build query - convert target filters to array of Zabbix items */ - buildFromCache(groupFilter, hostFilter, appFilter, itemFilter) { + _buildFromCache(groupFilter, hostFilter, appFilter, itemFilter) { return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { if (items.length) { if (utils.isRegex(itemFilter)) { @@ -309,6 +359,23 @@ function findByName(list, name) { } } +/** + * Different hosts can contains applications and items with same name. + * For this reason use _.filter, which return all elements instead _.find, + * which return only first finded. + * @param {[type]} list list of elements + * @param {[type]} name app name + * @return {[type]} array with finded element or undefined + */ +function filterByName(list, name) { + var finded = _.filter(list, {'name': name}); + if (finded) { + return finded; + } else { + return undefined; + } +} + function findByRegex(list, regex) { var filterPattern = utils.buildRegex(regex); return _.filter(list, function (zbx_obj) { @@ -324,6 +391,14 @@ function findByFilter(list, filter) { } } +function getByFilter(list, filter) { + if (utils.isRegex(filter)) { + return findByRegex(list, filter); + } else { + return filterByName(list, filter); + } +} + function getFromIndex(index, objids) { return _.map(objids, function(id) { return index[id]; diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index 7661c0c..b3cf98b 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -1,5 +1,6 @@ import angular from 'angular'; import _ from 'lodash'; +import * as utils from './utils'; import './zabbixAPICore.service'; /** @ngInject */ @@ -116,24 +117,42 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { getGroups() { var params = { output: ['name'], - sortfield: 'name', - selectHosts: [] + sortfield: 'name' }; return this.request('hostgroup.get', params); } - getHosts() { + getHosts(groupids) { var params = { output: ['name', 'host'], - sortfield: 'name', - selectGroups: [], - selectApplications: ['applicationid'] + sortfield: 'name' + }; + if (groupids) { + params.groupids = groupids; + } + + return this.request('host.get', params); + } + + getHostsByGroups(groupids) { + var params = { + output: ['name', 'host'], + groupids: groupids }; return this.request('host.get', params); } + getApps(hostids) { + var params = { + output: ['applicationid', 'name'], + hostids: hostids + }; + + return this.request('application.get', params); + } + getApplications() { var params = { output: ['applicationid', 'name'], @@ -147,7 +166,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { return this.request('application.get', params); } - getItems() { + getItems(hostids, appids) { var params = { output: [ 'name', 'key_', @@ -157,10 +176,22 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { 'state' ], sortfield: 'name', - selectApplications: [] }; + if (hostids) { + params.hostids = hostids; + } + if (appids) { + params.applicationids = appids; + } - return this.request('item.get', params); + return this.request('item.get', params) + .then(items => { + return _.forEach(items, item => { + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; + }); + }); } /** diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 65a00cf..ebac8e9 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -45,21 +45,12 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i _refresh() { var self = this; var promises = [ - this.zabbixAPI.getGroups(), - this.zabbixAPI.getHosts(), - this.zabbixAPI.getApplications(), - this.zabbixAPI.getItems(), - this.zabbixAPI.getHostsExtend() + this.zabbixAPI.getGroups() ]; return this.$q.all(promises).then(function(results) { if (results.length) { - self._groups = convertGroups(results[0]); - self._hosts = convertHosts(results[1]); - self._applications = convertApplications(results[2]); - self._items = convertItems(results[3]); - self._idx_apps = indexApps(results[2]); - self._idx_hosts = indexHosts(results[4]); + self._groups = results[0]; } self._initialized = true; }); @@ -70,13 +61,44 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i if (this._groups) { return this.$q.when(self._groups); } else { - return this.refresh().then(function() { - return self._groups; - }); + return this.zabbixAPI + .getGroups() + .then(groups => { + self._groups = groups; + return self._groups; + }); } } - getHosts() { + getApps(hostids) { + return this.zabbixAPI + .getApps(hostids) + .then(apps => { + return apps; + }); + } + + getHosts(groupids) { + var self = this; + return this.zabbixAPI + .getHosts(groupids) + .then(hosts => { + self._hosts = _.union(self._hosts, hosts); + return hosts; + }); + } + + getItems(hostids, appids) { + var self = this; + return this.zabbixAPI + .getItems(hostids, appids) + .then(items => { + self._items = _.union(self._items, items); + return items; + }); + } + + _getHosts() { var self = this; if (this._hosts) { return this.$q.when(self._hosts); @@ -120,7 +142,7 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i } } - getItems(type) { + _getItems(type) { var self = this; if (this._items) { return this.$q.when(filterItems(self._items, type)); From d15ffd4cd08339283c3b15641c60543b594ef1c6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 27 Mar 2016 18:57:20 +0300 Subject: [PATCH 21/24] Fixed template variable query with new cache model. --- src/datasource-zabbix/datasource.js | 17 +++-- .../queryProcessor.service.js | 73 +++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 1291c93..8f9a088 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -293,22 +293,25 @@ export class ZabbixAPIDatasource { // Get items if (parts.length === 4) { - return this.queryProcessor.filterItems(template.group, template.host, - template.app, 'all', true) - .then(function(items) { - return _.map(items, formatMetric); - }); + // Search for all items, even it's not belong to any application + if (template.app === '/.*/') { + template.app = ''; + } + return this.queryProcessor.getItems(template.group, template.host, template.app) + .then(function(items) { + return _.map(items, formatMetric); + }); } // Get applications else if (parts.length === 3) { - return this.queryProcessor.filterApplications(template.group, template.host) + return this.queryProcessor.getApps(template.group, template.host) .then(function(apps) { return _.map(apps, formatMetric); }); } // Get hosts else if (parts.length === 2) { - return this.queryProcessor.filterHosts(template.group) + return this.queryProcessor.getHosts(template.group) .then(function(hosts) { return _.map(hosts, formatMetric); }); diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index b229190..79047c3 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -201,6 +201,79 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { }); } + getHosts(groupFilter) { + var self = this; + return this.cache + .getGroups() + .then(groups => { + return findByFilter(groups, groupFilter); + }) + .then(groups => { + var groupids = _.map(groups, 'groupid'); + return self.cache.getHosts(groupids); + }); + } + + getApps(groupFilter, hostFilter) { + var self = this; + return this + .getHosts(groupFilter) + .then(hosts => { + return findByFilter(hosts, hostFilter); + }) + .then(hosts => { + var hostids = _.map(hosts, 'hostid'); + return self.cache.getApps(hostids); + }); + } + + getItems(groupFilter, hostFilter, appFilter, showDisabledItems) { + var self = this; + return this + .getHosts(groupFilter) + .then(hosts => { + return findByFilter(hosts, hostFilter); + }) + .then(hosts => { + var hostids = _.map(hosts, 'hostid'); + if (appFilter) { + return self.cache + .getApps(hostids) + .then(apps => { + // Use getByFilter for proper item filtering + return getByFilter(apps, appFilter); + }); + } else { + return { + appFilterEmpty: true, + hostids: hostids + }; + } + }) + .then(apps => { + if (apps.appFilterEmpty) { + return self.cache + .getItems(apps.hostids, undefined) + .then(items => { + if (showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + return items; + }); + } else { + var appids = _.map(apps, 'applicationid'); + return self.cache + .getItems(undefined, appids) + .then(items => { + if (showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + return items; + }); + } + }); + } + /** * Build query - convert target filters to array of Zabbix items */ From 539444b22de40c9122e845ed11297a51b93c08c4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 27 Mar 2016 19:02:56 +0300 Subject: [PATCH 22/24] buildFromCache method refactor. --- .../queryProcessor.service.js | 60 ++----------------- 1 file changed, 5 insertions(+), 55 deletions(-) diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 79047c3..98a8982 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -147,57 +147,9 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { } buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, showDisabledItems) { - var self = this; - return this.cache - .getGroups() - .then(groups => { - return findByFilter(groups, groupFilter); - }) - .then(groups => { - var groupids = _.map(groups, 'groupid'); - return self.cache - .getHosts(groupids) - .then(hosts => { - return findByFilter(hosts, hostFilter); - }); - }) - .then(hosts => { - var hostids = _.map(hosts, 'hostid'); - if (appFilter) { - return self.cache - .getApps(hostids) - .then(apps => { - // Use getByFilter for proper item filtering - return getByFilter(apps, appFilter); - }); - } else { - return { - appFilterEmpty: true, - hostids: hostids - }; - } - }) - .then(apps => { - if (apps.appFilterEmpty) { - return self.cache - .getItems(apps.hostids) - .then(items => { - if (showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - return getByFilter(items, itemFilter); - }); - } else { - var appids = _.map(apps, 'applicationid'); - return self.cache - .getItems(undefined, appids) - .then(items => { - if (showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - return getByFilter(items, itemFilter); - }); - } + return this.getItems(groupFilter, hostFilter, appFilter, showDisabledItems) + .then(items => { + return getByFilter(items, itemFilter); }); } @@ -216,8 +168,7 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { getApps(groupFilter, hostFilter) { var self = this; - return this - .getHosts(groupFilter) + return this.getHosts(groupFilter) .then(hosts => { return findByFilter(hosts, hostFilter); }) @@ -229,8 +180,7 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { getItems(groupFilter, hostFilter, appFilter, showDisabledItems) { var self = this; - return this - .getHosts(groupFilter) + return this.getHosts(groupFilter) .then(hosts => { return findByFilter(hosts, hostFilter); }) From c5e8776936e54152dd5c7626ffa60c0646f5bbf0 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 27 Mar 2016 19:08:56 +0300 Subject: [PATCH 23/24] Removed unused functions. --- .../queryProcessor.service.js | 113 ++---------------- 1 file changed, 11 insertions(+), 102 deletions(-) diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 98a8982..392d2e2 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -62,90 +62,8 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { } /** - * Get list of applications belonging to given groups and hosts. - * @return list of applications belonging to given hosts + * Build query - convert target filters to array of Zabbix items */ - filterApplications(groupFilter, hostFilter) { - var promises = [ - this.filterHosts(groupFilter), - this.cache.getApplications() - ]; - - return this.$q.all(promises).then(function(results) { - var hostList = results[0]; - var applicationList = results[1]; - - var hosts = findByFilter(hostList, hostFilter); - if (hosts) { - var hostsids = _.map(hosts, 'hostid'); - return _.filter(applicationList, function (appObj) { - return _.intersection(hostsids, appObj.hosts).length; - }); - } else { - return []; - } - }); - } - - filterItems(groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { - var hosts; - var apps; - var items; - - var promises = [ - this.filterHosts(groupFilter), - this.filterApplications(groupFilter, hostFilter), - this.cache.getIndexedHosts(), - this.cache.getIndexedApplications() - ]; - - return this.$q.all(promises).then(function(results) { - var hostList = results[0]; - var applicationList = results[1]; - var idx_hosts = results[2]; - var idx_apps = results[3]; - - // Filter hosts - hosts = findByFilter(hostList, hostFilter); - idx_hosts = getFromIndex(idx_hosts, _.map(hosts, 'hostid')); - - // Filter applications - if (appFilter === "") { - // Get all items - apps = undefined; - if (hosts) { - // Get all items in given hosts - items = _.flatten(_.map(idx_hosts, function(host) { - return _.values(host.idx_items); - }), true); - } - } else { - apps = findByFilter(applicationList, appFilter); - } - - if (apps) { - // Get ids for finded applications - var appids = _.flatten(_.map(apps, 'applicationids')); - appids = _.flatten(_.map(_.map(hosts, 'applications'), function(apps) { - return _.intersection(apps, appids); - })); - - // For each finded host get list of items in finded applications - items = _.flatten(_.map(idx_hosts, function(host) { - var host_apps = _.intersection(appids, host.applications); - var host_itemids = _.flatten(_.map(getFromIndex(idx_apps, host_apps), 'itemids')); - return _.values(getFromIndex(host.idx_items, host_itemids)); - }), true); - } - - if (!showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - - return items; - }); - } - buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, showDisabledItems) { return this.getItems(groupFilter, hostFilter, appFilter, showDisabledItems) .then(items => { @@ -153,6 +71,10 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { }); } + /** + * Get list of host belonging to given groups. + * @return list of hosts + */ getHosts(groupFilter) { var self = this; return this.cache @@ -166,6 +88,10 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { }); } + /** + * Get list of applications belonging to given groups and hosts. + * @return list of applications belonging to given hosts + */ getApps(groupFilter, hostFilter) { var self = this; return this.getHosts(groupFilter) @@ -224,23 +150,6 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { }); } - /** - * Build query - convert target filters to array of Zabbix items - */ - _buildFromCache(groupFilter, hostFilter, appFilter, itemFilter) { - return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { - if (items.length) { - if (utils.isRegex(itemFilter)) { - return findByFilter(items, itemFilter); - } else { - return _.filter(items, {'name': itemFilter}); - } - } else { - return []; - } - }); - } - /** * Build query - convert target filters to array of Zabbix items */ @@ -255,7 +164,7 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { } }); }), - this.filterHosts(groupFilter).then(function(hosts) { + this.getHosts(groupFilter).then(function(hosts) { return _.filter(hosts, function(host) { if (utils.isRegex(hostFilter)) { return utils.buildRegex(hostFilter).test(host.name); @@ -264,7 +173,7 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { } }); }), - this.filterApplications(groupFilter, hostFilter).then(function(apps) { + this.getApps(groupFilter, hostFilter).then(function(apps) { return _.filter(apps, function(app) { if (utils.isRegex(appFilter)) { return utils.buildRegex(appFilter).test(app.name); From 935f43ae1b9e006a59efa9c21daca0ebc5fb0a48 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 27 Mar 2016 19:13:01 +0300 Subject: [PATCH 24/24] Fixed metric editor in triggers panel. --- src/datasource-zabbix/queryProcessor.service.js | 4 ++++ src/panel-triggers/editor.js | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 392d2e2..49ab271 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -71,6 +71,10 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { }); } + getGroups() { + return this.cache.getGroups(); + } + /** * Get list of host belonging to given groups. * @return list of hosts diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index 8274570..186307e 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -85,7 +85,7 @@ class TriggerPanelEditorCtrl{ filterGroups() { var self = this; - this.datasource.queryProcessor.filterGroups().then(function(groups) { + this.datasource.queryProcessor.getGroups().then(function(groups) { self.metric.groupList = groups; }); } @@ -93,7 +93,7 @@ class TriggerPanelEditorCtrl{ filterHosts() { var self = this; var groupFilter = this.templateSrv.replace(this.panel.triggers.group.filter); - this.datasource.queryProcessor.filterHosts(groupFilter).then(function(hosts) { + this.datasource.queryProcessor.getHosts(groupFilter).then(function(hosts) { self.metric.filteredHosts = hosts; }); } @@ -102,7 +102,7 @@ class TriggerPanelEditorCtrl{ var self = this; var groupFilter = this.templateSrv.replace(this.panel.triggers.group.filter); var hostFilter = this.templateSrv.replace(this.panel.triggers.host.filter); - this.datasource.queryProcessor.filterApplications(groupFilter, hostFilter) + this.datasource.queryProcessor.getApps(groupFilter, hostFilter) .then(function(apps) { self.metric.filteredApplications = apps; });