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 @@
+
+
+
+
+
+
+
+
Customize triggers severity and colors
+
+
+
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 @@
+
+
+
+
+
+ | Host |
+ Severity |
+ Issue |
+ Last change |
+ Age |
+ Info |
+
+
+
+
+ |
+
+ {{trigger.host}}
+
+ |
+
+
+ {{trigger.severity}}
+
+ |
+
+
+ {{trigger.description}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Time |
+ User |
+ Comments |
+
+
+
+
+ |
+ {{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 @@
+
+