Move problems query to the data source
This commit is contained in:
@@ -8,6 +8,7 @@ export const MODE_ITSERVICE = 1;
|
|||||||
export const MODE_TEXT = 2;
|
export const MODE_TEXT = 2;
|
||||||
export const MODE_ITEMID = 3;
|
export const MODE_ITEMID = 3;
|
||||||
export const MODE_TRIGGERS = 4;
|
export const MODE_TRIGGERS = 4;
|
||||||
|
export const MODE_PROBLEMS = 5;
|
||||||
|
|
||||||
// Triggers severity
|
// Triggers severity
|
||||||
export const SEV_NOT_CLASSIFIED = 0;
|
export const SEV_NOT_CLASSIFIED = 0;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import config from 'grafana/app/core/config';
|
import config from 'grafana/app/core/config';
|
||||||
|
import { contextSrv } from 'grafana/app/core/core';
|
||||||
import * as dateMath from 'grafana/app/core/utils/datemath';
|
import * as dateMath from 'grafana/app/core/utils/datemath';
|
||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
import * as migrations from './migrations';
|
import * as migrations from './migrations';
|
||||||
@@ -7,9 +8,11 @@ import * as metricFunctions from './metricFunctions';
|
|||||||
import * as c from './constants';
|
import * as c from './constants';
|
||||||
import dataProcessor from './dataProcessor';
|
import dataProcessor from './dataProcessor';
|
||||||
import responseHandler from './responseHandler';
|
import responseHandler from './responseHandler';
|
||||||
|
import problemsHandler from './problemsHandler';
|
||||||
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';
|
||||||
import { VariableQueryTypes } from './types';
|
import { VariableQueryTypes } from './types';
|
||||||
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
const DEFAULT_ZABBIX_VERSION = 3;
|
const DEFAULT_ZABBIX_VERSION = 3;
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ export class ZabbixDatasource {
|
|||||||
enableDebugLog: boolean;
|
enableDebugLog: boolean;
|
||||||
zabbix: any;
|
zabbix: any;
|
||||||
|
|
||||||
replaceTemplateVars: (templateSrv: any, target: any, scopedVars?: any) => any;
|
replaceTemplateVars: (target: any, scopedVars?: any) => any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(instanceSettings, private templateSrv, private zabbixAlertingSrv) {
|
constructor(instanceSettings, private templateSrv, private zabbixAlertingSrv) {
|
||||||
@@ -181,6 +184,9 @@ export class ZabbixDatasource {
|
|||||||
} else if (target.queryType === c.MODE_TRIGGERS) {
|
} else if (target.queryType === c.MODE_TRIGGERS) {
|
||||||
// Triggers query
|
// Triggers query
|
||||||
return this.queryTriggersData(target, timeRange);
|
return this.queryTriggersData(target, timeRange);
|
||||||
|
} else if (target.queryType === c.MODE_PROBLEMS) {
|
||||||
|
// Problems query
|
||||||
|
return this.queryProblems(target, timeRange, options);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -384,6 +390,78 @@ export class ZabbixDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryProblems(target, timeRange, options) {
|
||||||
|
const [timeFrom, timeTo] = timeRange;
|
||||||
|
const userIsEditor = contextSrv.isEditor || contextSrv.isGrafanaAdmin;
|
||||||
|
|
||||||
|
let proxies;
|
||||||
|
let showAckButton = true;
|
||||||
|
|
||||||
|
// const showEvents = this.panel.showEvents.value;
|
||||||
|
const showEvents = target.showEvents?.value || 1;
|
||||||
|
// const showProxy = this.panel.hostProxy;
|
||||||
|
const showProxy = target.hostProxy;
|
||||||
|
|
||||||
|
const getProxiesPromise = showProxy ? this.zabbix.getProxies() : () => [];
|
||||||
|
showAckButton = !this.disableReadOnlyUsersAck || userIsEditor;
|
||||||
|
|
||||||
|
// Replace template variables
|
||||||
|
const groupFilter = this.replaceTemplateVars(target.group?.filter, options.scopedVars);
|
||||||
|
const hostFilter = this.replaceTemplateVars(target.host?.filter, options.scopedVars);
|
||||||
|
const appFilter = this.replaceTemplateVars(target.application?.filter, options.scopedVars);
|
||||||
|
const proxyFilter = this.replaceTemplateVars(target.proxy?.filter, options.scopedVars);
|
||||||
|
|
||||||
|
const triggerFilter = this.replaceTemplateVars(target.trigger?.filter, options.scopedVars);
|
||||||
|
const tagsFilter = this.replaceTemplateVars(target.tags?.filter, options.scopedVars);
|
||||||
|
|
||||||
|
const replacedTarget = {
|
||||||
|
...target,
|
||||||
|
trigger: { filter: triggerFilter },
|
||||||
|
tags: { filter: tagsFilter },
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggersOptions: any = {
|
||||||
|
showTriggers: showEvents
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showEvents !== 1) {
|
||||||
|
triggersOptions.timeFrom = timeFrom;
|
||||||
|
triggersOptions.timeTo = timeTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problemsPromises = Promise.all([
|
||||||
|
this.zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions, proxyFilter),
|
||||||
|
getProxiesPromise
|
||||||
|
])
|
||||||
|
.then(([triggers, sourceProxies]) => {
|
||||||
|
proxies = _.keyBy(sourceProxies, 'proxyid');
|
||||||
|
const eventids = _.compact(triggers.map(trigger => {
|
||||||
|
return trigger.lastEvent.eventid;
|
||||||
|
}));
|
||||||
|
return Promise.all([
|
||||||
|
this.zabbix.getExtendedEventData(eventids),
|
||||||
|
Promise.resolve(triggers)
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
.then(([events, triggers]) => {
|
||||||
|
problemsHandler.addEventTags(events, triggers);
|
||||||
|
problemsHandler.addAcknowledges(events, triggers);
|
||||||
|
return triggers;
|
||||||
|
})
|
||||||
|
.then(triggers => problemsHandler.setMaintenanceStatus(triggers))
|
||||||
|
.then(triggers => problemsHandler.setAckButtonStatus(triggers, showAckButton))
|
||||||
|
.then(triggers => problemsHandler.filterTriggersPre(triggers, replacedTarget))
|
||||||
|
.then(triggers => problemsHandler.addTriggerDataSource(triggers, target))
|
||||||
|
.then(triggers => problemsHandler.addTriggerHostProxy(triggers, proxies));
|
||||||
|
|
||||||
|
return problemsPromises.then(problems => {
|
||||||
|
const problemsDataFrame = problemsHandler.toDataFrame(problems);
|
||||||
|
console.log(problems);
|
||||||
|
console.log(problemsDataFrame);
|
||||||
|
return problemsDataFrame;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test connection to Zabbix API and external history DB.
|
* Test connection to Zabbix API and external history DB.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS">
|
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||||
<!-- Select Group -->
|
<!-- Select Group -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Group</label>
|
<label class="gf-form-label query-keyword width-7">Group</label>
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS">
|
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||||
<!-- Select Application -->
|
<!-- Select Application -->
|
||||||
<div class="gf-form max-width-20">
|
<div class="gf-form max-width-20">
|
||||||
<label class="gf-form-label query-keyword width-7">Application</label>
|
<label class="gf-form-label query-keyword width-7">Application</label>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
}">
|
}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form max-width-23" ng-show="ctrl.target.queryType == editorMode.TRIGGERS">
|
<div class="gf-form max-width-23" ng-show="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||||
<label class="gf-form-label query-keyword width-8">Min Severity</label>
|
<label class="gf-form-label query-keyword width-8">Min Severity</label>
|
||||||
<div class="gf-form-select-wrapper width-16">
|
<div class="gf-form-select-wrapper width-16">
|
||||||
<select class="gf-form-input"
|
<select class="gf-form-input"
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.TRIGGERS">
|
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||||
<label class="gf-form-label query-keyword width-8">Acknowledged</label>
|
<label class="gf-form-label query-keyword width-8">Acknowledged</label>
|
||||||
<div class="gf-form-select-wrapper width-12">
|
<div class="gf-form-select-wrapper width-12">
|
||||||
<select class="gf-form-input"
|
<select class="gf-form-input"
|
||||||
@@ -145,13 +145,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<gf-form-switch class="gf-form" label="Count" ng-show="ctrl.target.queryType == editorMode.TRIGGERS"
|
<gf-form-switch class="gf-form" label="Count" ng-show="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS"
|
||||||
checked="ctrl.target.triggers.count" on-change="ctrl.onTargetBlur()">
|
checked="ctrl.target.triggers.count" on-change="ctrl.onTargetBlur()">
|
||||||
</gf-form-switch>
|
</gf-form-switch>
|
||||||
|
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
<label class="gf-form-label gf-form-label--grow">
|
<label class="gf-form-label gf-form-label--grow">
|
||||||
<a ng-click="ctrl.toggleQueryOptions()" ng-hide="ctrl.target.queryType == editorMode.TRIGGERS">
|
<a ng-click="ctrl.toggleQueryOptions()" ng-hide="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||||
<i class="fa fa-caret-down" ng-show="ctrl.showQueryOptions"></i>
|
<i class="fa fa-caret-down" ng-show="ctrl.showQueryOptions"></i>
|
||||||
<i class="fa fa-caret-right" ng-hide="ctrl.showQueryOptions"></i>
|
<i class="fa fa-caret-right" ng-hide="ctrl.showQueryOptions"></i>
|
||||||
{{ctrl.queryOptionsText}}
|
{{ctrl.queryOptionsText}}
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
|
|
||||||
<!-- Query options -->
|
<!-- Query options -->
|
||||||
<div class="gf-form-group" ng-if="ctrl.showQueryOptions">
|
<div class="gf-form-group" ng-if="ctrl.showQueryOptions">
|
||||||
<div class="gf-form offset-width-7" ng-hide="ctrl.target.queryType == editorMode.TRIGGERS">
|
<div class="gf-form offset-width-7" ng-hide="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||||
<gf-form-switch class="gf-form" label-class="width-10"
|
<gf-form-switch class="gf-form" label-class="width-10"
|
||||||
label="Show disabled items"
|
label="Show disabled items"
|
||||||
checked="ctrl.target.options.showDisabledItems"
|
checked="ctrl.target.options.showDisabledItems"
|
||||||
|
|||||||
137
src/datasource-zabbix/problemsHandler.ts
Normal file
137
src/datasource-zabbix/problemsHandler.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import TableModel from 'grafana/app/core/table_model';
|
||||||
|
import * as utils from '../datasource-zabbix/utils';
|
||||||
|
import * as c from './constants';
|
||||||
|
import { DataFrame, Field, FieldType, ArrayVector } from '@grafana/data';
|
||||||
|
|
||||||
|
export function addEventTags(events, triggers) {
|
||||||
|
_.each(triggers, trigger => {
|
||||||
|
const event = _.find(events, event => {
|
||||||
|
return event.eventid === trigger.lastEvent.eventid;
|
||||||
|
});
|
||||||
|
if (event && event.tags && event.tags.length) {
|
||||||
|
trigger.tags = event.tags;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addAcknowledges(events, triggers) {
|
||||||
|
// Map events to triggers
|
||||||
|
_.each(triggers, trigger => {
|
||||||
|
const event = _.find(events, event => {
|
||||||
|
return event.eventid === trigger.lastEvent.eventid;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
trigger.acknowledges = event.acknowledges;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trigger.lastEvent.eventid) {
|
||||||
|
trigger.lastEvent = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMaintenanceStatus(triggers) {
|
||||||
|
_.each(triggers, (trigger) => {
|
||||||
|
const maintenance_status = _.some(trigger.hosts, (host) => host.maintenance_status === '1');
|
||||||
|
trigger.maintenance = maintenance_status;
|
||||||
|
});
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAckButtonStatus(triggers, showAckButton) {
|
||||||
|
_.each(triggers, (trigger) => {
|
||||||
|
trigger.showAckButton = showAckButton;
|
||||||
|
});
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addTriggerDataSource(triggers, target) {
|
||||||
|
_.each(triggers, (trigger) => {
|
||||||
|
trigger.datasource = target.datasource;
|
||||||
|
});
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addTriggerHostProxy(triggers, proxies) {
|
||||||
|
triggers.forEach(trigger => {
|
||||||
|
if (trigger.hosts && trigger.hosts.length) {
|
||||||
|
const host = trigger.hosts[0];
|
||||||
|
if (host.proxy_hostid !== '0') {
|
||||||
|
const hostProxy = proxies[host.proxy_hostid];
|
||||||
|
host.proxy = hostProxy ? hostProxy.host : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterTriggersPre(triggerList, replacedTarget) {
|
||||||
|
// Filter triggers by description
|
||||||
|
const triggerFilter = replacedTarget.trigger.filter;
|
||||||
|
if (triggerFilter) {
|
||||||
|
triggerList = filterTriggers(triggerList, triggerFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by tags
|
||||||
|
if (replacedTarget.tags.filter) {
|
||||||
|
let tagsFilter = replacedTarget.tags.filter;
|
||||||
|
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
||||||
|
tagsFilter = tagsFilter.replace('/^', '').replace('$/', '');
|
||||||
|
const tags = utils.parseTags(tagsFilter);
|
||||||
|
triggerList = _.filter(triggerList, trigger => {
|
||||||
|
return _.every(tags, tag => {
|
||||||
|
return _.includes(trigger.tags, {tag: tag.tag, value: tag.value});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTriggers(triggers, triggerFilter) {
|
||||||
|
if (utils.isRegex(triggerFilter)) {
|
||||||
|
return _.filter(triggers, trigger => {
|
||||||
|
return utils.buildRegex(triggerFilter).test(trigger.description);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return _.filter(triggers, trigger => {
|
||||||
|
return trigger.description === triggerFilter;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toDataFrame(problems: any[]): DataFrame {
|
||||||
|
const problemsField: Field<any> = {
|
||||||
|
name: 'Problems',
|
||||||
|
type: FieldType.other,
|
||||||
|
values: new ArrayVector(problems),
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response: DataFrame = {
|
||||||
|
name: 'problems',
|
||||||
|
fields: [problemsField],
|
||||||
|
length: problems.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problemsHandler = {
|
||||||
|
addEventTags,
|
||||||
|
addAcknowledges,
|
||||||
|
addTriggerDataSource,
|
||||||
|
addTriggerHostProxy,
|
||||||
|
setMaintenanceStatus,
|
||||||
|
setAckButtonStatus,
|
||||||
|
filterTriggersPre,
|
||||||
|
toDataFrame,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default problemsHandler;
|
||||||
@@ -21,7 +21,8 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
{value: 'text', text: 'Text', queryType: c.MODE_TEXT},
|
{value: 'text', text: 'Text', queryType: c.MODE_TEXT},
|
||||||
{value: 'itservice', text: 'IT Services', queryType: c.MODE_ITSERVICE},
|
{value: 'itservice', text: 'IT Services', queryType: c.MODE_ITSERVICE},
|
||||||
{value: 'itemid', text: 'Item ID', queryType: c.MODE_ITEMID},
|
{value: 'itemid', text: 'Item ID', queryType: c.MODE_ITEMID},
|
||||||
{value: 'triggers', text: 'Triggers', queryType: c.MODE_TRIGGERS}
|
{value: 'triggers', text: 'Triggers', queryType: c.MODE_TRIGGERS},
|
||||||
|
{value: 'problems', text: 'Problems', queryType: c.MODE_PROBLEMS},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.$scope.editorMode = {
|
this.$scope.editorMode = {
|
||||||
@@ -29,7 +30,8 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
TEXT: c.MODE_TEXT,
|
TEXT: c.MODE_TEXT,
|
||||||
ITSERVICE: c.MODE_ITSERVICE,
|
ITSERVICE: c.MODE_ITSERVICE,
|
||||||
ITEMID: c.MODE_ITEMID,
|
ITEMID: c.MODE_ITEMID,
|
||||||
TRIGGERS: c.MODE_TRIGGERS
|
TRIGGERS: c.MODE_TRIGGERS,
|
||||||
|
PROBLEMS: c.MODE_PROBLEMS,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.slaPropertyList = [
|
this.slaPropertyList = [
|
||||||
@@ -109,7 +111,8 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
|
|
||||||
if (target.queryType === c.MODE_METRICS ||
|
if (target.queryType === c.MODE_METRICS ||
|
||||||
target.queryType === c.MODE_TEXT ||
|
target.queryType === c.MODE_TEXT ||
|
||||||
target.queryType === c.MODE_TRIGGERS) {
|
target.queryType === c.MODE_TRIGGERS ||
|
||||||
|
target.queryType === c.MODE_PROBLEMS) {
|
||||||
this.initFilters();
|
this.initFilters();
|
||||||
}
|
}
|
||||||
else if (target.queryType === c.MODE_ITSERVICE) {
|
else if (target.queryType === c.MODE_ITSERVICE) {
|
||||||
|
|||||||
@@ -351,3 +351,19 @@ export function getArrayDepth(a, level = 0) {
|
|||||||
export function isNumeric(n: any): boolean {
|
export function isNumeric(n: any): boolean {
|
||||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses tags string into array of {tag: value} objects
|
||||||
|
*/
|
||||||
|
export function parseTags(tagStr: string): any[] {
|
||||||
|
if (!tagStr) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags: any[] = _.map(tagStr.split(','), (tag) => tag.trim());
|
||||||
|
tags = _.map(tags, (tag) => {
|
||||||
|
const tagParts = tag.split(':');
|
||||||
|
return {tag: tagParts[0].trim(), value: tagParts[1].trim()};
|
||||||
|
});
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user