Merge branch 'fix-event-ack'

This commit is contained in:
Alexander Zobnin
2018-10-21 12:13:43 +03:00
12 changed files with 1515 additions and 926 deletions

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ awsconfig
/public_gen /public_gen
/tmp /tmp
vendor/phantomjs/phantomjs vendor/phantomjs/phantomjs
yarn-error.log
# Built plugin # Built plugin
dist/ dist/

View File

@@ -1,5 +1,6 @@
module.exports = function(grunt) { module.exports = function(grunt) {
const sass = require('node-sass');
require('load-grunt-tasks')(grunt); require('load-grunt-tasks')(grunt);
grunt.loadNpmTasks('grunt-execute'); grunt.loadNpmTasks('grunt-execute');
@@ -114,6 +115,7 @@ module.exports = function(grunt) {
sass: { sass: {
options: { options: {
implementation: sass,
sourceMap: true sourceMap: true
}, },
dist: { dist: {

View File

@@ -22,36 +22,35 @@
"url": "https://github.com/alexanderzobnin/grafana-zabbix/issues" "url": "https://github.com/alexanderzobnin/grafana-zabbix/issues"
}, },
"devDependencies": { "devDependencies": {
"babel-jest": "^21.2.0", "babel": "^6.23.0",
"babel-jest": "^23.6.0",
"babel-plugin-transform-es2015-for-of": "^6.6.0", "babel-plugin-transform-es2015-for-of": "^6.6.0",
"babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0",
"babel-preset-es2015": "^6.5.0", "babel-preset-es2015": "^6.5.0",
"babel": "~6.5.1",
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"codecov": "^3.0.0", "codecov": "^3.1.0",
"grunt-babel": "~6.0.0", "grunt": "^1.0.3",
"grunt-babel": "^7.0.0",
"grunt-benchmark": "^1.0.0", "grunt-benchmark": "^1.0.0",
"grunt-cli": "~1.2.0", "grunt-cli": "^1.3.1",
"grunt-contrib-clean": "~0.6.0", "grunt-contrib-clean": "^2.0.0",
"grunt-contrib-copy": "~0.8.2", "grunt-contrib-copy": "^1.0.0",
"grunt-contrib-jshint": "^1.0.0", "grunt-contrib-jshint": "^2.0.0",
"grunt-contrib-uglify": "~0.11.0", "grunt-contrib-uglify": "^4.0.0",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^1.1.0",
"grunt-execute": "~0.2.2", "grunt-execute": "^0.2.2",
"grunt-jscs": "^2.8.0", "grunt-jscs": "^3.0.1",
"grunt-sass": "^1.1.0", "grunt-sass": "^3.0.2",
"grunt-systemjs-builder": "^0.2.5", "grunt-systemjs-builder": "^1.0.0",
"grunt": "~0.4.5",
"jest": "^23.5.0", "jest": "^23.5.0",
"jsdom": "~11.3.0", "jsdom": "~11.3.0",
"jshint-stylish": "^2.1.0", "jshint-stylish": "^2.1.0",
"load-grunt-tasks": "~3.2.0", "load-grunt-tasks": "~3.2.0",
"lodash": "~4.17.5", "lodash": "~4.17.5",
"moment": "~2.21.0", "moment": "~2.21.0",
"node-sass": "^4.9.4",
"systemjs": "^0.20.19", "systemjs": "^0.20.19",
"tether-drop": "^1.4.2" "tether-drop": "^1.4.2"
}, },
"dependencies": {
},
"homepage": "http://grafana-zabbix.org" "homepage": "http://grafana-zabbix.org"
} }

View File

@@ -2,6 +2,11 @@ import _ from 'lodash';
import { migrateDSConfig } from './migrations'; import { migrateDSConfig } from './migrations';
const SUPPORTED_SQL_DS = ['mysql', 'postgres']; const SUPPORTED_SQL_DS = ['mysql', 'postgres'];
const zabbixVersions = [
{ name: '2.x', value: 2 },
{ name: '3.x', value: 3 },
{ name: '4.x', value: 4 },
];
const defaultConfig = { const defaultConfig = {
trends: false, trends: false,
@@ -10,7 +15,8 @@ const defaultConfig = {
alerting: false, alerting: false,
addThresholds: false, addThresholds: false,
alertingMinSeverity: 3, alertingMinSeverity: 3,
disableReadOnlyUsersAck: false disableReadOnlyUsersAck: false,
zabbixVersion: 3,
}; };
export class ZabbixDSConfigController { export class ZabbixDSConfigController {
@@ -22,6 +28,8 @@ export class ZabbixDSConfigController {
this.current.jsonData = migrateDSConfig(this.current.jsonData); this.current.jsonData = migrateDSConfig(this.current.jsonData);
_.defaults(this.current.jsonData, defaultConfig); _.defaults(this.current.jsonData, defaultConfig);
this.sqlDataSources = this.getSupportedSQLDataSources(); this.sqlDataSources = this.getSupportedSQLDataSources();
this.zabbixVersions = _.cloneDeep(zabbixVersions);
this.autoDetectZabbixVersion();
} }
getSupportedSQLDataSources() { getSupportedSQLDataSources() {
@@ -30,4 +38,23 @@ export class ZabbixDSConfigController {
return _.includes(SUPPORTED_SQL_DS, ds.type); return _.includes(SUPPORTED_SQL_DS, ds.type);
}); });
} }
autoDetectZabbixVersion() {
if (!this.current.id) {
return;
}
this.datasourceSrv.loadDatasource(this.current.name)
.then(ds => {
return ds.getVersion();
})
.then(version => {
if (version) {
if (!_.find(zabbixVersions, ['value', version])) {
this.zabbixVersions.push({ name: version + '.x', value: version });
}
this.current.jsonData.zabbixVersion = version;
}
});
}
} }

View File

@@ -1,3 +1,7 @@
// Data point
export const DATAPOINT_VALUE = 0;
export const DATAPOINT_TS = 1;
// Editor modes // Editor modes
export const MODE_METRICS = 0; export const MODE_METRICS = 0;
export const MODE_ITSERVICE = 1; export const MODE_ITSERVICE = 1;
@@ -17,9 +21,10 @@ export const SHOW_ALL_TRIGGERS = [0, 1];
export const SHOW_ALL_EVENTS = [0, 1]; export const SHOW_ALL_EVENTS = [0, 1];
export const SHOW_OK_EVENTS = 1; export const SHOW_OK_EVENTS = 1;
// Data point // Acknowledge
export const DATAPOINT_VALUE = 0; export const ZBX_ACK_ACTION_NONE = 0;
export const DATAPOINT_TS = 1; export const ZBX_ACK_ACTION_ACK = 2;
export const ZBX_ACK_ACTION_ADD_MESSAGE = 4;
export const TRIGGER_SEVERITY = [ export const TRIGGER_SEVERITY = [
{val: 0, text: 'Not classified'}, {val: 0, text: 'Not classified'},

View File

@@ -9,6 +9,8 @@ import responseHandler from './responseHandler';
import { Zabbix } from './zabbix/zabbix'; import { Zabbix } from './zabbix/zabbix';
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore';
const DEFAULT_ZABBIX_VERSION = 3;
export class ZabbixDatasource { export class ZabbixDatasource {
/** @ngInject */ /** @ngInject */
@@ -47,6 +49,7 @@ export class ZabbixDatasource {
// Other options // Other options
this.disableReadOnlyUsersAck = jsonData.disableReadOnlyUsersAck; this.disableReadOnlyUsersAck = jsonData.disableReadOnlyUsersAck;
this.zabbixVersion = jsonData.zabbixVersion || DEFAULT_ZABBIX_VERSION;
// Direct DB Connection options // Direct DB Connection options
this.enableDirectDBConnection = jsonData.dbConnectionEnable || false; this.enableDirectDBConnection = jsonData.dbConnectionEnable || false;
@@ -59,6 +62,7 @@ export class ZabbixDatasource {
password: this.password, password: this.password,
basicAuth: this.basicAuth, basicAuth: this.basicAuth,
withCredentials: this.withCredentials, withCredentials: this.withCredentials,
zabbixVersion: this.zabbixVersion,
cacheTTL: this.cacheTTL, cacheTTL: this.cacheTTL,
enableDirectDBConnection: this.enableDirectDBConnection, enableDirectDBConnection: this.enableDirectDBConnection,
dbConnectionDatasourceId: this.dbConnectionDatasourceId, dbConnectionDatasourceId: this.dbConnectionDatasourceId,
@@ -380,6 +384,20 @@ export class ZabbixDatasource {
}); });
} }
/**
* Get Zabbix version
*/
getVersion() {
return this.zabbix.getVersion()
.then(version => {
const zabbixVersion = utils.parseVersion(version);
if (!zabbixVersion) {
return null;
}
return zabbixVersion.major;
});
}
//////////////// ////////////////
// Templating // // Templating //
//////////////// ////////////////

View File

@@ -67,12 +67,21 @@
Zabbix data source caches metric names in memory. Specify how often data will be updated. Zabbix data source caches metric names in memory. Specify how often data will be updated.
</info-popover> </info-popover>
</span> </span>
<input class="gf-form-input max-width-5" <input class="gf-form-input max-width-7"
type="text" type="text"
ng-model='ctrl.current.jsonData.cacheTTL' ng-model='ctrl.current.jsonData.cacheTTL'
placeholder="1h"> placeholder="1h">
</input> </input>
</div> </div>
<div class="gf-form max-width-20">
<span class="gf-form-label width-12">Zabbix version</span>
<div class="gf-form-select-wrapper max-width-7">
<select class="gf-form-input" ng-model="ctrl.current.jsonData.zabbixVersion"
ng-options="s.value as s.name for s in ctrl.zabbixVersions">
</select>
</div>
</div>
</div> </div>
<div class="gf-form-group"> <div class="gf-form-group">

View File

@@ -240,6 +240,24 @@ export function sequence(funcsArray) {
}; };
} }
const versionPattern = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z\.]+))?/;
export function isValidVersion(version) {
return versionPattern.exec(version);
}
export function parseVersion(version) {
const match = versionPattern.exec(version);
if (!match) {
return null;
}
const major = Number(match[1]);
const minor = Number(match[2] || 0);
const patch = Number(match[3] || 0);
const meta = match[4];
return { major, minor, patch, meta };
}
// Fix for backward compatibility with lodash 2.4 // Fix for backward compatibility with lodash 2.4
if (!_.includes) { if (!_.includes) {
_.includes = _.contains; _.includes = _.contains;

View File

@@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import * as utils from '../../../utils'; import * as utils from '../../../utils';
import { ZabbixAPICore } from './zabbixAPICore'; import { ZabbixAPICore } from './zabbixAPICore';
import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE } from '../../../constants';
/** /**
* Zabbix API Wrapper. * Zabbix API Wrapper.
@@ -8,11 +9,12 @@ import { ZabbixAPICore } from './zabbixAPICore';
* Wraps API calls and provides high-level methods. * Wraps API calls and provides high-level methods.
*/ */
export class ZabbixAPIConnector { export class ZabbixAPIConnector {
constructor(api_url, username, password, basicAuth, withCredentials, backendSrv) { constructor(api_url, username, password, version, basicAuth, withCredentials, backendSrv) {
this.url = api_url; this.url = api_url;
this.username = username; this.username = username;
this.password = password; this.password = password;
this.auth = ""; this.auth = '';
this.version = version;
this.requestOptions = { this.requestOptions = {
basicAuth: basicAuth, basicAuth: basicAuth,
@@ -47,9 +49,7 @@ export class ZabbixAPIConnector {
.then(() => this.request(method, params)); .then(() => this.request(method, params));
} }
} else { } else {
// Handle API errors return Promise.reject(error);
let message = error.data ? error.data : error.statusText;
return Promise.reject(message);
} }
}); });
} }
@@ -92,9 +92,11 @@ export class ZabbixAPIConnector {
//////////////////////////////// ////////////////////////////////
acknowledgeEvent(eventid, message) { acknowledgeEvent(eventid, message) {
var params = { const action = this.version >= 4 ? ZBX_ACK_ACTION_ACK + ZBX_ACK_ACTION_ADD_MESSAGE : ZBX_ACK_ACTION_NONE;
const params = {
eventids: eventid, eventids: eventid,
message: message message: message,
action: action
}; };
return this.request('event.acknowledge', params); return this.request('event.acknowledge', params);

View File

@@ -17,7 +17,7 @@ const REQUESTS_TO_CACHE = [
const REQUESTS_TO_BIND = [ const REQUESTS_TO_BIND = [
'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts', 'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts',
'getAcknowledges', 'getITService', 'getVersion', 'login' 'getAcknowledges', 'getITService', 'getVersion', 'login', 'acknowledgeEvent'
]; ];
export class Zabbix { export class Zabbix {
@@ -28,6 +28,7 @@ export class Zabbix {
password, password,
basicAuth, basicAuth,
withCredentials, withCredentials,
zabbixVersion,
cacheTTL, cacheTTL,
enableDirectDBConnection, enableDirectDBConnection,
dbConnectionDatasourceId, dbConnectionDatasourceId,
@@ -43,7 +44,7 @@ export class Zabbix {
}; };
this.cachingProxy = new CachingProxy(cacheOptions); this.cachingProxy = new CachingProxy(cacheOptions);
this.zabbixAPI = new ZabbixAPIConnector(url, username, password, basicAuth, withCredentials, backendSrv); this.zabbixAPI = new ZabbixAPIConnector(url, username, password, zabbixVersion, basicAuth, withCredentials, backendSrv);
if (enableDirectDBConnection) { if (enableDirectDBConnection) {
let dbConnectorOptions = { let dbConnectorOptions = {

View File

@@ -102,6 +102,23 @@ export class TriggerPanelCtrl extends PanelCtrl {
this.events.on('refresh', this.onRefresh.bind(this)); this.events.on('refresh', this.onRefresh.bind(this));
} }
setPanelError(err, defaultError) {
const defaultErrorMessage = defaultError || "Request Error";
this.inspector = { error: err };
this.error = err.message || defaultErrorMessage;
if (err.data) {
if (err.data.message) {
this.error = err.data.message;
}
if (err.data.error) {
this.error = err.data.error;
}
}
this.events.emit('data-error', err);
console.log('Panel data error:', err);
}
initDatasources() { initDatasources() {
let promises = _.map(this.panel.datasources, (ds) => { let promises = _.map(this.panel.datasources, (ds) => {
// Load datasource // Load datasource
@@ -161,18 +178,7 @@ export class TriggerPanelCtrl extends PanelCtrl {
return; return;
} }
this.error = err.message || "Request Error"; this.setPanelError(err);
if (err.data) {
if (err.data.message) {
this.error = err.data.message;
}
if (err.data.error) {
this.error = err.data.error;
}
}
this.events.emit('data-error', err);
console.log('Panel data error:', err);
}); });
} }
@@ -459,16 +465,14 @@ export class TriggerPanelCtrl extends PanelCtrl {
return Promise.reject({message: 'You have no permissions to acknowledge events.'}); return Promise.reject({message: 'You have no permissions to acknowledge events.'});
} }
if (eventid) { if (eventid) {
return datasource.zabbix.zabbixAPI.acknowledgeEvent(eventid, ack_message); return datasource.zabbix.acknowledgeEvent(eventid, ack_message);
} else { } else {
return Promise.reject({message: 'Trigger has no events. Nothing to acknowledge.'}); return Promise.reject({message: 'Trigger has no events. Nothing to acknowledge.'});
} }
}) })
.then(this.onRefresh.bind(this)) .then(this.onRefresh.bind(this))
.catch((err) => { .catch((err) => {
this.error = err.message || "Acknowledge Error"; this.setPanelError(err);
this.events.emit('data-error', err);
console.log('Panel data error:', err);
}); });
} }

2261
yarn.lock

File diff suppressed because it is too large Load Diff