From e95c61608ba20fc5bf99747635e50a01615233fd Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 26 Dec 2015 23:04:53 +0300 Subject: [PATCH] Issue #72 - merge triggers panel from https://github.com/alexanderzobnin/grafana/tree/zbx-trigger_panel. Can used as plugin since Grafana 3.0. --- .../datasource/zabbix}/datasource.js | 0 .../datasource/zabbix}/directives.js | 0 .../datasource/zabbix}/helperFunctions.js | 28 ++ .../zabbix}/partials/annotations.editor.html | 0 .../datasource/zabbix}/partials/config.html | 0 .../zabbix}/partials/query.editor.html | 0 .../zabbix}/partials/query.options.html | 0 .../datasource/zabbix}/plugin.json | 0 .../datasource/zabbix}/queryCtrl.js | 0 .../datasource/zabbix}/zabbixAPIWrapper.js | 46 +++ plugins/panels/triggers/editor.html | 221 +++++++++++++ plugins/panels/triggers/module.html | 118 +++++++ plugins/panels/triggers/module.js | 300 ++++++++++++++++++ plugins/panels/triggers/plugin.json | 8 + .../panels/triggers/trigger.colorpicker.html | 13 + 15 files changed, 734 insertions(+) rename {zabbix => plugins/datasource/zabbix}/datasource.js (100%) rename {zabbix => plugins/datasource/zabbix}/directives.js (100%) rename {zabbix => plugins/datasource/zabbix}/helperFunctions.js (91%) rename {zabbix => plugins/datasource/zabbix}/partials/annotations.editor.html (100%) rename {zabbix => plugins/datasource/zabbix}/partials/config.html (100%) rename {zabbix => plugins/datasource/zabbix}/partials/query.editor.html (100%) rename {zabbix => plugins/datasource/zabbix}/partials/query.options.html (100%) rename {zabbix => plugins/datasource/zabbix}/plugin.json (100%) rename {zabbix => plugins/datasource/zabbix}/queryCtrl.js (100%) rename {zabbix => plugins/datasource/zabbix}/zabbixAPIWrapper.js (92%) create mode 100644 plugins/panels/triggers/editor.html create mode 100644 plugins/panels/triggers/module.html create mode 100644 plugins/panels/triggers/module.js create mode 100644 plugins/panels/triggers/plugin.json create mode 100644 plugins/panels/triggers/trigger.colorpicker.html diff --git a/zabbix/datasource.js b/plugins/datasource/zabbix/datasource.js similarity index 100% rename from zabbix/datasource.js rename to plugins/datasource/zabbix/datasource.js diff --git a/zabbix/directives.js b/plugins/datasource/zabbix/directives.js similarity index 100% rename from zabbix/directives.js rename to plugins/datasource/zabbix/directives.js diff --git a/zabbix/helperFunctions.js b/plugins/datasource/zabbix/helperFunctions.js similarity index 91% rename from zabbix/helperFunctions.js rename to plugins/datasource/zabbix/helperFunctions.js index f59a470..69129c2 100644 --- a/zabbix/helperFunctions.js +++ b/plugins/datasource/zabbix/helperFunctions.js @@ -279,5 +279,33 @@ function (angular, _) { } 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 diff --git a/zabbix/partials/annotations.editor.html b/plugins/datasource/zabbix/partials/annotations.editor.html similarity index 100% rename from zabbix/partials/annotations.editor.html rename to plugins/datasource/zabbix/partials/annotations.editor.html diff --git a/zabbix/partials/config.html b/plugins/datasource/zabbix/partials/config.html similarity index 100% rename from zabbix/partials/config.html rename to plugins/datasource/zabbix/partials/config.html diff --git a/zabbix/partials/query.editor.html b/plugins/datasource/zabbix/partials/query.editor.html similarity index 100% rename from zabbix/partials/query.editor.html rename to plugins/datasource/zabbix/partials/query.editor.html diff --git a/zabbix/partials/query.options.html b/plugins/datasource/zabbix/partials/query.options.html similarity index 100% rename from zabbix/partials/query.options.html rename to plugins/datasource/zabbix/partials/query.options.html diff --git a/zabbix/plugin.json b/plugins/datasource/zabbix/plugin.json similarity index 100% rename from zabbix/plugin.json rename to plugins/datasource/zabbix/plugin.json diff --git a/zabbix/queryCtrl.js b/plugins/datasource/zabbix/queryCtrl.js similarity index 100% rename from zabbix/queryCtrl.js rename to plugins/datasource/zabbix/queryCtrl.js diff --git a/zabbix/zabbixAPIWrapper.js b/plugins/datasource/zabbix/zabbixAPIWrapper.js similarity index 92% rename from zabbix/zabbixAPIWrapper.js rename to plugins/datasource/zabbix/zabbixAPIWrapper.js index 8a89ccc..48220ea 100644 --- a/zabbix/zabbixAPIWrapper.js +++ b/plugins/datasource/zabbix/zabbixAPIWrapper.js @@ -531,6 +531,52 @@ function (angular, _) { return this.performZabbixAPIRequest('service.getsla', params); }; + p.getTriggers = function(limit, sortfield, groupids, hostids, applicationids, name) { + var params = { + output: 'extend', + expandDescription: true, + expandData: true, + monitored: true, + //only_true: true, + filter: { + value: 1 + }, + search : { + description: name + }, + searchWildcardsEnabled: false, + groupids: groupids, + hostids: hostids, + applicationids: applicationids, + limit: limit, + sortfield: 'lastchange', + sortorder: 'DESC' + }; + + if (sortfield) { + params.sortfield = sortfield; + } + + return this.performZabbixAPIRequest('trigger.get', params); + }; + + p.getAcknowledges = function(triggerids, from) { + var params = { + output: 'extend', + objectids: triggerids, + acknowledged: true, + select_acknowledges: 'extend', + sortfield: 'clock', + sortorder: 'DESC', + time_from: from + }; + + return this.performZabbixAPIRequest('event.get', params) + .then(function (events) { + return _.flatten(_.map(events, 'acknowledges')); + }); + }; + return ZabbixAPI; }); diff --git a/plugins/panels/triggers/editor.html b/plugins/panels/triggers/editor.html new file mode 100644 index 0000000..ac49831 --- /dev/null +++ b/plugins/panels/triggers/editor.html @@ -0,0 +1,221 @@ +
+
+
Select triggers
+
+
    +
  • + Group +
  • +
  • + +
  • +
  • + Host +
  • +
  • + +
  • +
+
+
+
+
    +
  • + Application +
  • +
  • + +
  • +
  • + Trigger +
  • +
  • + +
  • +
+
+
+
+
+
Data source
+
+
+
    +
  • + +
  • +
+
+
+
+
+ +
+
+
Options
+
+
+
    +
  • + Acknowledged +
  • +
  • + +
  • +
  • + Limit triggers number to +
  • +
  • + +
  • +
+
+
+
+
    +
  • + Sort by +
  • +
  • + +
  • +
+
+
+
+
    +
  • + Show fields +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
+
+
+
+
+
+
Customize triggers severity and colors
+
+
    +
  • + {{ trigger.priority }} +
  • +
  • + +
  • +
  • +   +   + +
  • +
  • + + + +
  • +
+
+
+
+
diff --git a/plugins/panels/triggers/module.html b/plugins/panels/triggers/module.html new file mode 100644 index 0000000..5f8ea0e --- /dev/null +++ b/plugins/panels/triggers/module.html @@ -0,0 +1,118 @@ + +
+ + + + + + + + + + + + + + + + + + + + + +
HostSeverityIssueLast changeAgeInfo
+
+ {{trigger.host}} +
+
+
+ {{trigger.severity}} +
+
+
+ {{trigger.description}} + + + +
+ + +
+
+ {{trigger.comments}} +
+
+ + +
+
+ + + + + + + + + + + + + + + +
TimeUserComments
+ {{ack.time}} + + {{ack.user}} + + {{ack.message}} +
+
+
+
+ {{trigger.lastchange}} + + {{trigger.age}} + + + + + + + + + + + + + + + + +
+
+
diff --git a/plugins/panels/triggers/module.js b/plugins/panels/triggers/module.js new file mode 100644 index 0000000..34af1b7 --- /dev/null +++ b/plugins/panels/triggers/module.js @@ -0,0 +1,300 @@ +/** + * Grafana-Zabbix + * Zabbix plugin for Grafana. + * http://github.com/alexanderzobnin/grafana-zabbix + * + * Trigger panel. + * This feature sponsored by CORE IT + * http://www.coreit.fr + * + * Copyright 2015 Alexander Zobnin alexanderzobnin@gmail.com + * Licensed under the Apache License, Version 2.0 + */ + +define([ + 'angular', + 'app/app', + 'lodash', + 'jquery', + 'app/core/config', + 'app/features/panel/panel_meta', + 'app/plugins/datasource/zabbix/helperFunctions', +], +function (angular, app, _, $, config, PanelMeta) { + 'use strict'; + + var module = angular.module('grafana.panels.triggers', []); + app.useModule(module); + + module.directive('grafanaPanelTriggers', function() { + return { + controller: 'TriggersPanelCtrl', + templateUrl: 'app/plugins/panels/triggers/module.html', + }; + }); + + module.controller('TriggersPanelCtrl', function($q, $scope, $element, datasourceSrv, panelSrv, + templateSrv, zabbixHelperSrv, popoverSrv) { + + $scope.panelMeta = new PanelMeta({ + panelName: 'Zabbix triggers', + editIcon: "fa fa-lightbulb-o", + fullscreen: true, + }); + + $scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/triggers/editor.html'); + + $scope.ackFilters = [ + 'all triggers', + 'unacknowledged', + 'acknowledged' + ]; + + $scope.sortByFields = [ + { text: 'last change', value: 'lastchange' }, + { text: 'severity', value: 'priority' } + ]; + + var grafanaDefaultSeverity = [ + { 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: {name: 'All', groupid: null}, + host: {name: 'All', hostid: null}, + application: {name: 'All', value: null} + }, + hostField: true, + severityField: false, + lastChangeField: true, + ageField: true, + infoField: true, + limit: 10, + showTriggers: 'all triggers', + sortTriggersBy: { text: 'last change', value: 'lastchange' }, + triggerSeverity: grafanaDefaultSeverity + }; + + _.defaults($scope.panel, panelDefaults); + $scope.triggerList = []; + + $scope.init = function() { + panelSrv.init($scope); + if ($scope.isNewPanel()) { + $scope.panel.title = "Zabbix Triggers"; + } + + if (!$scope.metric) { + $scope.metric = { + groupList: [{name: 'All', groupid: null}], + hostList: [{name: 'All', hostid: null}], + applicationList: [{name: 'All', applicationid: null}] + }; + } + + // Get zabbix data sources + var datasources = _.filter(datasourceSrv.getMetricSources(), function(datasource) { + return datasource.meta.type === 'zabbix'; + }); + $scope.datasources = _.map(datasources, 'name'); + + // Set default datasource + if (!$scope.panel.datasource) { + $scope.panel.datasource = $scope.datasources[0]; + } + + // Update lists of groups, hosts and applications + $scope.updateGroups() + .then($scope.updateHosts) + .then($scope.updateApplications); + }; + + $scope.refreshData = function() { + + // Load datasource + return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { + var zabbix = datasource.zabbixAPI; + + var groupid = $scope.panel.triggers.group.groupid; + var hostid = $scope.panel.triggers.host.hostid; + var applicationids = $scope.panel.triggers.application.value; + + // Get triggers + return zabbix.getTriggers($scope.panel.limit, + $scope.panel.sortTriggersBy.value, + groupid, + hostid, + applicationids, + $scope.panel.triggers.name) + .then(function(triggers) { + var promises = _.map(triggers, function (trigger) { + var lastchange = new Date(trigger.lastchange * 1000); + var lastchangeUnix = trigger.lastchange; + var now = new Date(); + + // Consider local time offset + var ageUnix = now - lastchange + now.getTimezoneOffset() * 60000; + var age = zabbixHelperSrv.toZabbixAgeFormat(ageUnix); + var triggerObj = trigger; + triggerObj.lastchangeUnix = lastchangeUnix; + triggerObj.lastchange = lastchange.toLocaleString(); + triggerObj.age = age.toLocaleString(); + triggerObj.color = $scope.panel.triggerSeverity[trigger.priority].color; + triggerObj.severity = $scope.panel.triggerSeverity[trigger.priority].severity; + + // Request acknowledges for trigger + return zabbix.getAcknowledges(trigger.triggerid, lastchangeUnix) + .then(function (acknowledges) { + if (acknowledges.length) { + triggerObj.acknowledges = _.map(acknowledges, function (ack) { + var time = new Date(+ack.clock * 1000); + ack.time = time.toLocaleString(); + ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; + return ack; + }); + } + return triggerObj; + }); + }); + return $q.all(promises).then(function (triggerList) { + + // Filter acknowledged triggers + if ($scope.panel.showTriggers === 'unacknowledged') { + $scope.triggerList = _.filter(triggerList, function (trigger) { + return !trigger.acknowledges; + }); + } else if ($scope.panel.showTriggers === 'acknowledged') { + $scope.triggerList = _.filter(triggerList, 'acknowledges'); + } else { + $scope.triggerList = triggerList; + } + + // Filter triggers by severity + $scope.triggerList = _.filter($scope.triggerList, function (trigger) { + return $scope.panel.triggerSeverity[trigger.priority].show; + }); + + $scope.panelRenderingComplete(); + }); + }); + }); + }; + + $scope.groupChanged = function() { + return $scope.updateHosts() + .then($scope.updateApplications) + .then($scope.refreshData); + }; + + $scope.hostChanged = function() { + return $scope.updateApplications() + .then($scope.refreshData); + }; + + $scope.appChanged = function() { + var app = $scope.panel.triggers.application.name; + + return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { + return datasource.zabbixAPI.getAppByName(app).then(function (applications) { + var appids = _.map(applications, 'applicationid'); + $scope.panel.triggers.application.value = appids.length ? appids : null; + }); + }).then($scope.refreshData); + }; + + $scope.updateGroups = function() { + return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { + return $scope.updateGroupList(datasource); + }); + }; + + $scope.updateHosts = function() { + return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { + return $scope.updateHostList(datasource); + }); + }; + + $scope.updateApplications = function() { + return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { + return $scope.updateAppList(datasource); + }); + }; + + $scope.refreshTriggerSeverity = function() { + _.each($scope.triggerList, function(trigger) { + trigger.color = $scope.panel.triggerSeverity[trigger.priority].color; + trigger.severity = $scope.panel.triggerSeverity[trigger.priority].severity; + }); + }; + + $scope.datasourceChanged = function() { + $scope.refreshData(); + }; + + $scope.changeTriggerSeverityColor = function(trigger, color) { + $scope.panel.triggerSeverity[trigger.priority].color = color; + $scope.refreshTriggerSeverity(); + }; + + function getTriggerIndexForElement(el) { + return el.parents('[data-trigger-index]').data('trigger-index'); + } + + $scope.openTriggerColorSelector = function(event) { + var el = $(event.currentTarget); + var index = getTriggerIndexForElement(el); + var popoverScope = $scope.$new(); + popoverScope.trigger = $scope.panel.triggerSeverity[index]; + popoverScope.changeTriggerSeverityColor = $scope.changeTriggerSeverityColor; + + popoverSrv.show({ + element: el, + placement: 'top', + templateUrl: 'app/plugins/panels/triggers/trigger.colorpicker.html', + scope: popoverScope + }); + }; + + $scope.updateGroupList = function (datasource) { + datasource.zabbixAPI.performHostGroupSuggestQuery().then(function (groups) { + $scope.metric.groupList = $scope.metric.groupList.concat(groups); + }); + }; + + $scope.updateHostList = function (datasource) { + var groups = $scope.panel.triggers.group.groupid ? $scope.panel.triggers.group.name : '*'; + if (groups) { + datasource.zabbixAPI.hostFindQuery(groups).then(function (hosts) { + $scope.metric.hostList = [{name: 'All', hostid: null}]; + $scope.metric.hostList = $scope.metric.hostList.concat(hosts); + }); + } + }; + + $scope.updateAppList = function (datasource) { + var groups = $scope.panel.triggers.group.groupid ? $scope.panel.triggers.group.name : '*'; + var hosts = $scope.panel.triggers.host.hostid ? $scope.panel.triggers.host.name : '*'; + if (groups && hosts) { + datasource.zabbixAPI.appFindQuery(hosts, groups).then(function (apps) { + apps = _.map(_.uniq(_.map(apps, 'name')), function (appname) { + return { + name: appname, + value: appname + }; + }); + $scope.metric.applicationList = [{name: 'All', value: null}]; + $scope.metric.applicationList = $scope.metric.applicationList.concat(apps); + }); + } + }; + + $scope.init(); + }); +}); diff --git a/plugins/panels/triggers/plugin.json b/plugins/panels/triggers/plugin.json new file mode 100644 index 0000000..3f50686 --- /dev/null +++ b/plugins/panels/triggers/plugin.json @@ -0,0 +1,8 @@ +{ + "pluginType": "panel", + + "name": "Zabbix triggers", + "type": "triggers", + + "module": "app/plugins/panels/triggers/module" +} diff --git a/plugins/panels/triggers/trigger.colorpicker.html b/plugins/panels/triggers/trigger.colorpicker.html new file mode 100644 index 0000000..4f878d0 --- /dev/null +++ b/plugins/panels/triggers/trigger.colorpicker.html @@ -0,0 +1,13 @@ +
+ × + +
+   +
+
+