Merge branch 'master' into backend
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
export const DEFAULT_QUERY_LIMIT = 10000;
|
||||
export const HISTORY_TO_TABLE_MAP = {
|
||||
@@ -34,31 +35,30 @@ export const consolidateByTrendColumns = {
|
||||
* `testDataSource()` methods, which describe how to fetch data from source other than Zabbix API.
|
||||
*/
|
||||
export class DBConnector {
|
||||
constructor(options, datasourceSrv) {
|
||||
this.datasourceSrv = datasourceSrv;
|
||||
constructor(options) {
|
||||
this.datasourceId = options.datasourceId;
|
||||
this.datasourceName = options.datasourceName;
|
||||
this.datasourceTypeId = null;
|
||||
this.datasourceTypeName = null;
|
||||
}
|
||||
|
||||
static loadDatasource(dsId, dsName, datasourceSrv) {
|
||||
static loadDatasource(dsId, dsName) {
|
||||
if (!dsName && dsId !== undefined) {
|
||||
let ds = _.find(datasourceSrv.getAll(), {'id': dsId});
|
||||
let ds = _.find(getDataSourceSrv().getAll(), {'id': dsId});
|
||||
if (!ds) {
|
||||
return Promise.reject(`Data Source with ID ${dsId} not found`);
|
||||
}
|
||||
dsName = ds.name;
|
||||
}
|
||||
if (dsName) {
|
||||
return datasourceSrv.loadDatasource(dsName);
|
||||
return getDataSourceSrv().loadDatasource(dsName);
|
||||
} else {
|
||||
return Promise.reject(`Data Source name should be specified`);
|
||||
}
|
||||
}
|
||||
|
||||
loadDBDataSource() {
|
||||
return DBConnector.loadDatasource(this.datasourceId, this.datasourceName, this.datasourceSrv)
|
||||
return DBConnector.loadDatasource(this.datasourceId, this.datasourceName)
|
||||
.then(ds => {
|
||||
this.datasourceTypeId = ds.meta.id;
|
||||
this.datasourceTypeName = ds.meta.name;
|
||||
@@ -123,22 +123,36 @@ export class ZabbixNotImplemented {
|
||||
*/
|
||||
function convertGrafanaTSResponse(time_series, items, addHostName) {
|
||||
//uniqBy is needed to deduplicate
|
||||
var hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid');
|
||||
const hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid');
|
||||
let grafanaSeries = _.map(_.compact(time_series), series => {
|
||||
let itemid = series.name;
|
||||
var item = _.find(items, {'itemid': itemid});
|
||||
var alias = item.name;
|
||||
//only when actual multi hosts selected
|
||||
if (_.keys(hosts).length > 1 && addHostName) {
|
||||
var host = _.find(hosts, {'hostid': item.hostid});
|
||||
alias = host.name + ": " + alias;
|
||||
const itemid = series.name;
|
||||
const item = _.find(items, {'itemid': itemid});
|
||||
let alias = item.name;
|
||||
|
||||
// Add scopedVars for using in alias functions
|
||||
const scopedVars = {
|
||||
'__zbx_item': { value: item.name },
|
||||
'__zbx_item_name': { value: item.name },
|
||||
'__zbx_item_key': { value: item.key_ },
|
||||
};
|
||||
|
||||
if (_.keys(hosts).length > 0) {
|
||||
const host = _.find(hosts, {'hostid': item.hostid});
|
||||
scopedVars['__zbx_host'] = { value: host.host };
|
||||
scopedVars['__zbx_host_name'] = { value: host.name };
|
||||
|
||||
// Only add host when multiple hosts selected
|
||||
if (_.keys(hosts).length > 1 && addHostName) {
|
||||
alias = host.name + ": " + alias;
|
||||
}
|
||||
}
|
||||
// CachingProxy deduplicates requests and returns one time series for equal queries.
|
||||
// Clone is needed to prevent changing of series object shared between all targets.
|
||||
let datapoints = _.cloneDeep(series.points);
|
||||
const datapoints = _.cloneDeep(series.points);
|
||||
return {
|
||||
target: alias,
|
||||
datapoints: datapoints
|
||||
datapoints,
|
||||
scopedVars,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ const consolidateByFunc = {
|
||||
};
|
||||
|
||||
export class InfluxDBConnector extends DBConnector {
|
||||
constructor(options, datasourceSrv) {
|
||||
super(options, datasourceSrv);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.retentionPolicy = options.retentionPolicy;
|
||||
super.loadDBDataSource().then(ds => {
|
||||
this.influxDS = ds;
|
||||
@@ -24,7 +24,14 @@ export class InfluxDBConnector extends DBConnector {
|
||||
* Try to invoke test query for one of Zabbix database tables.
|
||||
*/
|
||||
testDataSource() {
|
||||
return this.influxDS.testDatasource();
|
||||
return this.influxDS.testDatasource().then(result => {
|
||||
if (result.status && result.status === 'error') {
|
||||
return Promise.reject({ data: {
|
||||
message: `InfluxDB connection error: ${result.message}`
|
||||
}});
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
getHistory(items, timeFrom, timeTill, options) {
|
||||
|
||||
@@ -3,26 +3,24 @@
|
||||
*/
|
||||
|
||||
function historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction) {
|
||||
let time_expression = `clock DIV ${intervalSec} * ${intervalSec}`;
|
||||
let query = `
|
||||
SELECT CAST(itemid AS CHAR) AS metric, ${time_expression} AS time_sec, ${aggFunction}(value) AS value
|
||||
SELECT CAST(itemid AS CHAR) AS metric, MIN(clock) AS time_sec, ${aggFunction}(value) AS value
|
||||
FROM ${table}
|
||||
WHERE itemid IN (${itemids})
|
||||
AND clock > ${timeFrom} AND clock < ${timeTill}
|
||||
GROUP BY ${time_expression}, metric
|
||||
GROUP BY (clock-${timeFrom}) DIV ${intervalSec}, metric
|
||||
ORDER BY time_sec ASC
|
||||
`;
|
||||
return query;
|
||||
}
|
||||
|
||||
function trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn) {
|
||||
let time_expression = `clock DIV ${intervalSec} * ${intervalSec}`;
|
||||
let query = `
|
||||
SELECT CAST(itemid AS CHAR) AS metric, ${time_expression} AS time_sec, ${aggFunction}(${valueColumn}) AS value
|
||||
SELECT CAST(itemid AS CHAR) AS metric, MIN(clock) AS time_sec, ${aggFunction}(${valueColumn}) AS value
|
||||
FROM ${table}
|
||||
WHERE itemid IN (${itemids})
|
||||
AND clock > ${timeFrom} AND clock < ${timeTill}
|
||||
GROUP BY ${time_expression}, metric
|
||||
GROUP BY (clock-${timeFrom}) DIV ${intervalSec}, metric
|
||||
ORDER BY time_sec ASC
|
||||
`;
|
||||
return query;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { compactQuery } from '../../../utils';
|
||||
import mysql from './mysql';
|
||||
import postgres from './postgres';
|
||||
@@ -10,15 +11,14 @@ const supportedDatabases = {
|
||||
};
|
||||
|
||||
export class SQLConnector extends DBConnector {
|
||||
constructor(options, datasourceSrv) {
|
||||
super(options, datasourceSrv);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.limit = options.limit || DEFAULT_QUERY_LIMIT;
|
||||
this.sqlDialect = null;
|
||||
|
||||
super.loadDBDataSource()
|
||||
.then(ds => {
|
||||
this.backendSrv = ds.backendSrv;
|
||||
.then(() => {
|
||||
this.loadSQLDialect();
|
||||
});
|
||||
}
|
||||
@@ -43,6 +43,12 @@ export class SQLConnector extends DBConnector {
|
||||
let {intervalMs, consolidateBy} = options;
|
||||
let intervalSec = Math.ceil(intervalMs / 1000);
|
||||
|
||||
// The interval must match the time range exactly n times, otherwise
|
||||
// the resulting first and last data points will yield invalid values in the
|
||||
// calculated average value in downsampleSeries - when using consolidateBy(avg)
|
||||
let numOfIntervals = Math.ceil((timeTill - timeFrom) / intervalSec);
|
||||
intervalSec = (timeTill - timeFrom) / numOfIntervals;
|
||||
|
||||
consolidateBy = consolidateBy || 'avg';
|
||||
let aggFunction = dbConnector.consolidateByFunc[consolidateBy];
|
||||
|
||||
@@ -66,6 +72,12 @@ export class SQLConnector extends DBConnector {
|
||||
let { intervalMs, consolidateBy } = options;
|
||||
let intervalSec = Math.ceil(intervalMs / 1000);
|
||||
|
||||
// The interval must match the time range exactly n times, otherwise
|
||||
// the resulting first and last data points will yield invalid values in the
|
||||
// calculated average value in downsampleSeries - when using consolidateBy(avg)
|
||||
let numOfIntervals = Math.ceil((timeTill - timeFrom) / intervalSec);
|
||||
intervalSec = (timeTill - timeFrom) / numOfIntervals;
|
||||
|
||||
consolidateBy = consolidateBy || 'avg';
|
||||
let aggFunction = dbConnector.consolidateByFunc[consolidateBy];
|
||||
|
||||
@@ -96,7 +108,7 @@ export class SQLConnector extends DBConnector {
|
||||
maxDataPoints: this.limit
|
||||
};
|
||||
|
||||
return this.backendSrv.datasourceRequest({
|
||||
return getBackendSrv().datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: {
|
||||
|
||||
42
src/datasource-zabbix/zabbix/connectors/zabbix_api/types.ts
Normal file
42
src/datasource-zabbix/zabbix/connectors/zabbix_api/types.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export interface JSONRPCRequest {
|
||||
jsonrpc: '2.0' | string;
|
||||
method: string;
|
||||
id: number;
|
||||
auth?: string | null;
|
||||
params?: JSONRPCRequestParams;
|
||||
}
|
||||
|
||||
export interface JSONRPCResponse<T> {
|
||||
jsonrpc: '2.0' | string;
|
||||
id: number;
|
||||
result?: T;
|
||||
error?: JSONRPCError;
|
||||
}
|
||||
|
||||
export interface JSONRPCError {
|
||||
code?: number;
|
||||
message?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
export interface GFHTTPRequest {
|
||||
method: HTTPMethod;
|
||||
url: string;
|
||||
data?: any;
|
||||
headers?: {[key: string]: string};
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
|
||||
export type JSONRPCRequestParams = {[key: string]: any};
|
||||
|
||||
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE';
|
||||
|
||||
export type GFRequestOptions = {[key: string]: any};
|
||||
|
||||
export interface ZabbixRequestResponse {
|
||||
data?: JSONRPCResponse<any>;
|
||||
}
|
||||
|
||||
export type ZabbixAPIResponse<T> = T;
|
||||
|
||||
export type APILoginResponse = string;
|
||||
@@ -1,8 +1,14 @@
|
||||
import _ from 'lodash';
|
||||
import semver from 'semver';
|
||||
import kbn from 'grafana/app/core/utils/kbn';
|
||||
import * as utils from '../../../utils';
|
||||
import { ZabbixAPICore } from './zabbixAPICore';
|
||||
import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants';
|
||||
import { ShowProblemTypes, ZBXProblem } from '../../../types';
|
||||
import { JSONRPCRequestParams } from './types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
const DEFAULT_ZABBIX_VERSION = '3.0.0';
|
||||
|
||||
/**
|
||||
* Zabbix API Wrapper.
|
||||
@@ -10,12 +16,25 @@ import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE, MI
|
||||
* Wraps API calls and provides high-level methods.
|
||||
*/
|
||||
export class ZabbixAPIConnector {
|
||||
constructor(api_url, username, password, version, basicAuth, withCredentials, backendSrv, datasourceId) {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
auth: string;
|
||||
requestOptions: { basicAuth: any; withCredentials: boolean; };
|
||||
loginPromise: Promise<string>;
|
||||
loginErrorCount: number;
|
||||
maxLoginAttempts: number;
|
||||
zabbixAPICore: ZabbixAPICore;
|
||||
getTrend: (items: any, timeFrom: any, timeTill: any) => Promise<any[]>;
|
||||
version: string;
|
||||
getVersionPromise: Promise<string>;
|
||||
datasourceId: number;
|
||||
|
||||
constructor(api_url: string, username: string, password: string, basicAuth: any, withCredentials: boolean, datasourceId: number) {
|
||||
this.url = api_url;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.auth = '';
|
||||
this.version = version;
|
||||
|
||||
this.requestOptions = {
|
||||
basicAuth: basicAuth,
|
||||
@@ -23,16 +42,17 @@ export class ZabbixAPIConnector {
|
||||
};
|
||||
|
||||
this.datasourceId = datasourceId;
|
||||
this.backendSrv = backendSrv;
|
||||
|
||||
this.loginPromise = null;
|
||||
this.loginErrorCount = 0;
|
||||
this.maxLoginAttempts = 3;
|
||||
|
||||
this.zabbixAPICore = new ZabbixAPICore(backendSrv);
|
||||
this.zabbixAPICore = new ZabbixAPICore();
|
||||
|
||||
this.getTrend = this.getTrend_ZBXNEXT1193;
|
||||
//getTrend = getTrend_30;
|
||||
|
||||
this.initVersion();
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
@@ -59,13 +79,40 @@ export class ZabbixAPIConnector {
|
||||
}],
|
||||
};
|
||||
|
||||
return this.backendSrv.datasourceRequest({
|
||||
return getBackendSrv().datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: tsdbRequestData
|
||||
});
|
||||
}
|
||||
|
||||
_request(method: string, params: JSONRPCRequestParams): Promise<any> {
|
||||
if (!this.version) {
|
||||
return this.initVersion().then(() => this.request(method, params));
|
||||
}
|
||||
|
||||
return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth)
|
||||
.catch(error => {
|
||||
if (isNotInitialized(error.data)) {
|
||||
// If API not initialized yet (auth is empty), login first
|
||||
return this.loginOnce()
|
||||
.then(() => this.request(method, params));
|
||||
} else if (isNotAuthorized(error.data)) {
|
||||
// Handle auth errors
|
||||
this.loginErrorCount++;
|
||||
if (this.loginErrorCount > this.maxLoginAttempts) {
|
||||
this.loginErrorCount = 0;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.loginOnce()
|
||||
.then(() => this.request(method, params));
|
||||
}
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleTsdbResponse(response) {
|
||||
if (!response || !response.data || !response.data.results) {
|
||||
return [];
|
||||
@@ -78,9 +125,8 @@ export class ZabbixAPIConnector {
|
||||
* 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
|
||||
*/
|
||||
loginOnce() {
|
||||
loginOnce(): Promise<string> {
|
||||
if (!this.loginPromise) {
|
||||
this.loginPromise = Promise.resolve(
|
||||
this.login().then(auth => {
|
||||
@@ -96,7 +142,7 @@ export class ZabbixAPIConnector {
|
||||
/**
|
||||
* Get authentication token.
|
||||
*/
|
||||
login() {
|
||||
login(): Promise<string> {
|
||||
return this.zabbixAPICore.login(this.url, this.username, this.password, this.requestOptions);
|
||||
}
|
||||
|
||||
@@ -107,23 +153,49 @@ export class ZabbixAPIConnector {
|
||||
return this.zabbixAPICore.getVersion(this.url, this.requestOptions);
|
||||
}
|
||||
|
||||
initVersion(): Promise<string> {
|
||||
if (!this.getVersionPromise) {
|
||||
this.getVersionPromise = Promise.resolve(
|
||||
this.getVersion().then(version => {
|
||||
if (version) {
|
||||
console.log(`Zabbix version detected: ${version}`);
|
||||
} else {
|
||||
console.log(`Failed to detect Zabbix version, use default ${DEFAULT_ZABBIX_VERSION}`);
|
||||
}
|
||||
|
||||
this.version = version || DEFAULT_ZABBIX_VERSION;
|
||||
this.getVersionPromise = null;
|
||||
return version;
|
||||
})
|
||||
);
|
||||
}
|
||||
return this.getVersionPromise;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Zabbix API method wrappers //
|
||||
////////////////////////////////
|
||||
|
||||
acknowledgeEvent(eventid, message) {
|
||||
const action = this.version >= 4 ? ZBX_ACK_ACTION_ACK + ZBX_ACK_ACTION_ADD_MESSAGE : ZBX_ACK_ACTION_NONE;
|
||||
const params = {
|
||||
acknowledgeEvent(eventid: string, message: string, action?: number, severity?: number) {
|
||||
if (!action) {
|
||||
action = semver.gte(this.version, '4.0.0') ? ZBX_ACK_ACTION_ADD_MESSAGE : ZBX_ACK_ACTION_NONE;
|
||||
}
|
||||
|
||||
const params: any = {
|
||||
eventids: eventid,
|
||||
message: message,
|
||||
action: action
|
||||
};
|
||||
|
||||
if (severity) {
|
||||
params.severity = severity;
|
||||
}
|
||||
|
||||
return this.request('event.acknowledge', params);
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
var params = {
|
||||
const params = {
|
||||
output: ['name'],
|
||||
sortfield: 'name',
|
||||
real_hosts: true
|
||||
@@ -133,7 +205,7 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getHosts(groupids) {
|
||||
var params = {
|
||||
const params: any = {
|
||||
output: ['name', 'host'],
|
||||
sortfield: 'name'
|
||||
};
|
||||
@@ -144,8 +216,8 @@ export class ZabbixAPIConnector {
|
||||
return this.request('host.get', params);
|
||||
}
|
||||
|
||||
getApps(hostids) {
|
||||
var params = {
|
||||
getApps(hostids): Promise<any[]> {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
hostids: hostids
|
||||
};
|
||||
@@ -161,7 +233,7 @@ export class ZabbixAPIConnector {
|
||||
* @return {[type]} array of items
|
||||
*/
|
||||
getItems(hostids, appids, itemtype) {
|
||||
var params = {
|
||||
const params: any = {
|
||||
output: [
|
||||
'name', 'key_',
|
||||
'value_type',
|
||||
@@ -172,7 +244,7 @@ export class ZabbixAPIConnector {
|
||||
sortfield: 'name',
|
||||
webitems: true,
|
||||
filter: {},
|
||||
selectHosts: ['hostid', 'name']
|
||||
selectHosts: ['hostid', 'name', 'host']
|
||||
};
|
||||
if (hostids) {
|
||||
params.hostids = hostids;
|
||||
@@ -194,7 +266,7 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getItemsByIDs(itemids) {
|
||||
var params = {
|
||||
const params = {
|
||||
itemids: itemids,
|
||||
output: [
|
||||
'name', 'key_',
|
||||
@@ -208,11 +280,11 @@ export class ZabbixAPIConnector {
|
||||
};
|
||||
|
||||
return this.request('item.get', params)
|
||||
.then(utils.expandItems);
|
||||
.then(items => utils.expandItems(items));
|
||||
}
|
||||
|
||||
getMacros(hostids) {
|
||||
var params = {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
hostids: hostids
|
||||
};
|
||||
@@ -221,7 +293,7 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getGlobalMacros() {
|
||||
var params = {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
globalmacro: true
|
||||
};
|
||||
@@ -230,7 +302,7 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getLastValue(itemid) {
|
||||
var params = {
|
||||
const params = {
|
||||
output: ['lastvalue'],
|
||||
itemids: itemid
|
||||
};
|
||||
@@ -249,10 +321,10 @@ export class ZabbixAPIConnector {
|
||||
getHistory(items, timeFrom, timeTill) {
|
||||
|
||||
// Group items by value type and perform request for each value type
|
||||
let grouped_items = _.groupBy(items, 'value_type');
|
||||
let promises = _.map(grouped_items, (items, value_type) => {
|
||||
let itemids = _.map(items, 'itemid');
|
||||
let params = {
|
||||
const grouped_items = _.groupBy(items, 'value_type');
|
||||
const promises = _.map(grouped_items, (items, value_type) => {
|
||||
const itemids = _.map(items, 'itemid');
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
history: value_type,
|
||||
itemids: itemids,
|
||||
@@ -284,10 +356,10 @@ export class ZabbixAPIConnector {
|
||||
getTrend_ZBXNEXT1193(items, timeFrom, timeTill) {
|
||||
|
||||
// Group items by value type and perform request for each value type
|
||||
let grouped_items = _.groupBy(items, 'value_type');
|
||||
let promises = _.map(grouped_items, (items, value_type) => {
|
||||
let itemids = _.map(items, 'itemid');
|
||||
let params = {
|
||||
const grouped_items = _.groupBy(items, 'value_type');
|
||||
const promises = _.map(grouped_items, (items, value_type) => {
|
||||
const itemids = _.map(items, 'itemid');
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
trend: value_type,
|
||||
itemids: itemids,
|
||||
@@ -308,10 +380,10 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getTrend_30(items, time_from, time_till, value_type) {
|
||||
var self = this;
|
||||
var itemids = _.map(items, 'itemid');
|
||||
const self = this;
|
||||
const itemids = _.map(items, 'itemid');
|
||||
|
||||
var params = {
|
||||
const params: any = {
|
||||
output: ["itemid",
|
||||
"clock",
|
||||
value_type
|
||||
@@ -328,8 +400,8 @@ export class ZabbixAPIConnector {
|
||||
return self.request('trend.get', params);
|
||||
}
|
||||
|
||||
getITService(serviceids) {
|
||||
var params = {
|
||||
getITService(serviceids?) {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
serviceids: serviceids
|
||||
};
|
||||
@@ -337,18 +409,88 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getSLA(serviceids, timeRange, options) {
|
||||
const intervals = buildSLAIntervals(timeRange, options.intervalMs);
|
||||
const params = {
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
let intervals = [{ from: timeFrom, to: timeTo }];
|
||||
if (options.slaInterval === 'auto') {
|
||||
const interval = getSLAInterval(options.intervalMs);
|
||||
intervals = buildSLAIntervals(timeRange, interval);
|
||||
} else if (options.slaInterval !== 'none') {
|
||||
const interval = utils.parseInterval(options.slaInterval) / 1000;
|
||||
intervals = buildSLAIntervals(timeRange, interval);
|
||||
}
|
||||
|
||||
const params: any = {
|
||||
serviceids,
|
||||
intervals
|
||||
};
|
||||
|
||||
return this.request('service.getsla', params);
|
||||
}
|
||||
|
||||
getTriggers(groupids, hostids, applicationids, options) {
|
||||
let {showTriggers, maintenance, timeFrom, timeTo} = options;
|
||||
getProblems(groupids, hostids, applicationids, options): Promise<ZBXProblem[]> {
|
||||
const { timeFrom, timeTo, recent, severities, limit, acknowledged } = options;
|
||||
|
||||
let params = {
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
selectAcknowledges: 'extend',
|
||||
selectSuppressionData: 'extend',
|
||||
selectTags: 'extend',
|
||||
source: '0',
|
||||
object: '0',
|
||||
sortfield: ['eventid'],
|
||||
sortorder: 'ASC',
|
||||
evaltype: '0',
|
||||
// preservekeys: '1',
|
||||
groupids,
|
||||
hostids,
|
||||
applicationids,
|
||||
recent,
|
||||
};
|
||||
|
||||
if (severities) {
|
||||
params.severities = severities;
|
||||
}
|
||||
|
||||
if (acknowledged !== undefined) {
|
||||
params.acknowledged = acknowledged;
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
params.limit = limit;
|
||||
}
|
||||
|
||||
if (timeFrom || timeTo) {
|
||||
params.time_from = timeFrom;
|
||||
params.time_till = timeTo;
|
||||
}
|
||||
|
||||
return this.request('problem.get', params).then(utils.mustArray);
|
||||
}
|
||||
|
||||
getTriggersByIds(triggerids: string[]) {
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
triggerids: triggerids,
|
||||
expandDescription: true,
|
||||
expandData: true,
|
||||
expandComment: true,
|
||||
monitored: true,
|
||||
skipDependent: true,
|
||||
selectGroups: ['name'],
|
||||
selectHosts: ['name', 'host', 'maintenance_status', 'proxy_hostid'],
|
||||
selectItems: ['name', 'key_', 'lastvalue'],
|
||||
// selectLastEvent: 'extend',
|
||||
// selectTags: 'extend',
|
||||
preservekeys: '1',
|
||||
};
|
||||
|
||||
return this.request('trigger.get', params).then(utils.mustArray);
|
||||
}
|
||||
|
||||
getTriggers(groupids, hostids, applicationids, options) {
|
||||
const {showTriggers, maintenance, timeFrom, timeTo} = options;
|
||||
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
groupids: groupids,
|
||||
hostids: hostids,
|
||||
@@ -369,8 +511,10 @@ export class ZabbixAPIConnector {
|
||||
selectTags: 'extend'
|
||||
};
|
||||
|
||||
if (showTriggers) {
|
||||
params.filter.value = showTriggers;
|
||||
if (showTriggers === ShowProblemTypes.Problems) {
|
||||
params.filter.value = 1;
|
||||
} else if (showTriggers === ShowProblemTypes.Recent || showTriggers === ShowProblemTypes.History) {
|
||||
params.filter.value = [0, 1];
|
||||
}
|
||||
|
||||
if (maintenance) {
|
||||
@@ -386,7 +530,7 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getEvents(objectids, timeFrom, timeTo, showEvents, limit) {
|
||||
var params = {
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
time_from: timeFrom,
|
||||
time_till: timeTo,
|
||||
@@ -402,27 +546,47 @@ export class ZabbixAPIConnector {
|
||||
params.sortorder = 'DESC';
|
||||
}
|
||||
|
||||
return this.request('event.get', params);
|
||||
return this.request('event.get', params).then(utils.mustArray);
|
||||
}
|
||||
|
||||
getAcknowledges(eventids) {
|
||||
var params = {
|
||||
getEventsHistory(groupids, hostids, applicationids, options) {
|
||||
const { timeFrom, timeTo, severities, limit, value } = options;
|
||||
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
eventids: eventids,
|
||||
preservekeys: true,
|
||||
time_from: timeFrom,
|
||||
time_till: timeTo,
|
||||
value: '1',
|
||||
source: '0',
|
||||
object: '0',
|
||||
evaltype: '0',
|
||||
sortfield: ['eventid'],
|
||||
sortorder: 'ASC',
|
||||
select_acknowledges: 'extend',
|
||||
sortfield: 'clock',
|
||||
sortorder: 'DESC'
|
||||
selectTags: 'extend',
|
||||
selectSuppressionData: ['maintenanceid', 'suppress_until'],
|
||||
groupids,
|
||||
hostids,
|
||||
applicationids,
|
||||
};
|
||||
|
||||
return this.request('event.get', params)
|
||||
.then(events => {
|
||||
return _.filter(events, (event) => event.acknowledges.length);
|
||||
});
|
||||
if (limit) {
|
||||
params.limit = limit;
|
||||
}
|
||||
|
||||
if (severities) {
|
||||
params.severities = severities;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
params.value = value;
|
||||
}
|
||||
|
||||
return this.request('event.get', params).then(utils.mustArray);
|
||||
}
|
||||
|
||||
getExtendedEventData(eventids) {
|
||||
var params = {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
eventids: eventids,
|
||||
preservekeys: true,
|
||||
@@ -450,8 +614,24 @@ export class ZabbixAPIConnector {
|
||||
return this.request('alert.get', params);
|
||||
}
|
||||
|
||||
getAcknowledges(eventids) {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
eventids: eventids,
|
||||
preservekeys: true,
|
||||
select_acknowledges: 'extend',
|
||||
sortfield: 'clock',
|
||||
sortorder: 'DESC'
|
||||
};
|
||||
|
||||
return this.request('event.get', params)
|
||||
.then(events => {
|
||||
return _.filter(events, (event) => event.acknowledges.length);
|
||||
});
|
||||
}
|
||||
|
||||
getAlerts(itemids, timeFrom, timeTo) {
|
||||
var params = {
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
itemids: itemids,
|
||||
expandDescription: true,
|
||||
@@ -475,8 +655,8 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getHostAlerts(hostids, applicationids, options) {
|
||||
let {minSeverity, acknowledged, count, timeFrom, timeTo} = options;
|
||||
let params = {
|
||||
const {minSeverity, acknowledged, count, timeFrom, timeTo} = options;
|
||||
const params: any = {
|
||||
output: 'extend',
|
||||
hostids: hostids,
|
||||
min_severity: minSeverity,
|
||||
@@ -517,7 +697,7 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
|
||||
getProxies() {
|
||||
var params = {
|
||||
const params = {
|
||||
output: ['proxyid', 'host'],
|
||||
};
|
||||
|
||||
@@ -535,13 +715,17 @@ function filterTriggersByAcknowledge(triggers, acknowledged) {
|
||||
}
|
||||
}
|
||||
|
||||
// function isNotAuthorized(message) {
|
||||
// return (
|
||||
// message === "Session terminated, re-login, please." ||
|
||||
// message === "Not authorised." ||
|
||||
// message === "Not authorized."
|
||||
// );
|
||||
// }
|
||||
function isNotAuthorized(message) {
|
||||
return (
|
||||
message === "Session terminated, re-login, please." ||
|
||||
message === "Not authorised." ||
|
||||
message === "Not authorized."
|
||||
);
|
||||
}
|
||||
|
||||
function isNotInitialized(message) {
|
||||
return message === "Not initialized";
|
||||
}
|
||||
|
||||
function getSLAInterval(intervalMs) {
|
||||
// Too many intervals may cause significant load on the database, so decrease number of resulting points
|
||||
@@ -550,19 +734,18 @@ function getSLAInterval(intervalMs) {
|
||||
return Math.max(interval, MIN_SLA_INTERVAL);
|
||||
}
|
||||
|
||||
function buildSLAIntervals(timeRange, intervalMs) {
|
||||
function buildSLAIntervals(timeRange, interval) {
|
||||
let [timeFrom, timeTo] = timeRange;
|
||||
const slaInterval = getSLAInterval(intervalMs);
|
||||
const intervals = [];
|
||||
|
||||
// Align time range with calculated interval
|
||||
timeFrom = Math.floor(timeFrom / slaInterval) * slaInterval;
|
||||
timeTo = Math.ceil(timeTo / slaInterval) * slaInterval;
|
||||
timeFrom = Math.floor(timeFrom / interval) * interval;
|
||||
timeTo = Math.ceil(timeTo / interval) * interval;
|
||||
|
||||
for (let i = timeFrom; i <= timeTo - slaInterval; i += slaInterval) {
|
||||
for (let i = timeFrom; i <= timeTo - interval; i += interval) {
|
||||
intervals.push({
|
||||
from : i,
|
||||
to : (i + slaInterval)
|
||||
to : (i + interval)
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
/**
|
||||
* General Zabbix API methods
|
||||
*/
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { JSONRPCRequest, ZabbixRequestResponse, JSONRPCError, APILoginResponse, GFHTTPRequest, GFRequestOptions } from './types';
|
||||
|
||||
export class ZabbixAPICore {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(backendSrv) {
|
||||
this.backendSrv = backendSrv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request data from Zabbix API
|
||||
* @return {object} response.result
|
||||
*/
|
||||
request(api_url, method, params, options, auth) {
|
||||
let requestData = {
|
||||
request(api_url: string, method: string, params: any, options: GFRequestOptions, auth?: string) {
|
||||
const requestData: JSONRPCRequest = {
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
@@ -23,13 +19,13 @@ export class ZabbixAPICore {
|
||||
|
||||
if (auth === "") {
|
||||
// Reject immediately if not authenticated
|
||||
return Promise.reject(new ZabbixAPIError({data: "Not authorised."}));
|
||||
return Promise.reject(new ZabbixAPIError({data: "Not initialized"}));
|
||||
} else if (auth) {
|
||||
// Set auth parameter only if it needed
|
||||
requestData.auth = auth;
|
||||
}
|
||||
|
||||
let requestOptions = {
|
||||
const requestOptions: GFHTTPRequest = {
|
||||
method: 'POST',
|
||||
url: api_url,
|
||||
data: requestData,
|
||||
@@ -50,18 +46,18 @@ export class ZabbixAPICore {
|
||||
}
|
||||
|
||||
datasourceRequest(requestOptions) {
|
||||
return this.backendSrv.datasourceRequest(requestOptions)
|
||||
.then((response) => {
|
||||
if (!response.data) {
|
||||
return getBackendSrv().datasourceRequest(requestOptions)
|
||||
.then((response: ZabbixRequestResponse) => {
|
||||
if (!response?.data) {
|
||||
return Promise.reject(new ZabbixAPIError({data: "General Error, no data"}));
|
||||
} else if (response.data.error) {
|
||||
} else if (response?.data.error) {
|
||||
|
||||
// Handle Zabbix API errors
|
||||
return Promise.reject(new ZabbixAPIError(response.data.error));
|
||||
}
|
||||
|
||||
// Success
|
||||
return response.data.result;
|
||||
return response?.data.result;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,8 +65,8 @@ export class ZabbixAPICore {
|
||||
* Get authentication token.
|
||||
* @return {string} auth token
|
||||
*/
|
||||
login(api_url, username, password, options) {
|
||||
let params = {
|
||||
login(api_url: string, username: string, password: string, options: GFRequestOptions): Promise<APILoginResponse> {
|
||||
const params = {
|
||||
user: username,
|
||||
password: password
|
||||
};
|
||||
@@ -81,14 +77,22 @@ export class ZabbixAPICore {
|
||||
* 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);
|
||||
getVersion(api_url: string, options: GFRequestOptions): Promise<string> {
|
||||
return this.request(api_url, 'apiinfo.version', [], options).catch(err => {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Define zabbix API exception type
|
||||
export class ZabbixAPIError {
|
||||
constructor(error) {
|
||||
code: number;
|
||||
name: string;
|
||||
data: string;
|
||||
message: string;
|
||||
|
||||
constructor(error: JSONRPCError) {
|
||||
this.code = error.code || null;
|
||||
this.name = error.message || "";
|
||||
this.data = error.data || "";
|
||||
@@ -4,6 +4,10 @@
|
||||
*/
|
||||
|
||||
export class CachingProxy {
|
||||
cacheEnabled: boolean;
|
||||
ttl: number;
|
||||
cache: any;
|
||||
promises: any;
|
||||
|
||||
constructor(cacheOptions) {
|
||||
this.cacheEnabled = cacheOptions.enabled;
|
||||
@@ -33,13 +37,13 @@ export class CachingProxy {
|
||||
}
|
||||
|
||||
proxyfyWithCache(func, funcName, funcScope) {
|
||||
let proxyfied = this.proxyfy(func, funcName, funcScope);
|
||||
const proxyfied = this.proxyfy(func, funcName, funcScope);
|
||||
return this.cacheRequest(proxyfied, funcName, funcScope);
|
||||
}
|
||||
|
||||
_isExpired(cacheObject) {
|
||||
if (cacheObject) {
|
||||
let object_age = Date.now() - cacheObject.timestamp;
|
||||
const object_age = Date.now() - cacheObject.timestamp;
|
||||
return !(cacheObject.timestamp && object_age < this.ttl);
|
||||
} else {
|
||||
return true;
|
||||
@@ -52,8 +56,9 @@ export class CachingProxy {
|
||||
* with same params when waiting for result.
|
||||
*/
|
||||
function callOnce(func, promiseKeeper, funcScope) {
|
||||
// tslint:disable-next-line: only-arrow-functions
|
||||
return function() {
|
||||
var hash = getRequestHash(arguments);
|
||||
const hash = getRequestHash(arguments);
|
||||
if (!promiseKeeper[hash]) {
|
||||
promiseKeeper[hash] = Promise.resolve(
|
||||
func.apply(funcScope, arguments)
|
||||
@@ -68,22 +73,25 @@ function callOnce(func, promiseKeeper, funcScope) {
|
||||
}
|
||||
|
||||
function cacheRequest(func, funcName, funcScope, self) {
|
||||
// tslint:disable-next-line: only-arrow-functions
|
||||
return function() {
|
||||
if (!self.cache[funcName]) {
|
||||
self.cache[funcName] = {};
|
||||
}
|
||||
|
||||
let cacheObject = self.cache[funcName];
|
||||
let hash = getRequestHash(arguments);
|
||||
const cacheObject = self.cache[funcName];
|
||||
const hash = getRequestHash(arguments);
|
||||
if (self.cacheEnabled && !self._isExpired(cacheObject[hash])) {
|
||||
return Promise.resolve(cacheObject[hash].value);
|
||||
} else {
|
||||
return func.apply(funcScope, arguments)
|
||||
.then(result => {
|
||||
cacheObject[hash] = {
|
||||
value: result,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
if (result !== undefined) {
|
||||
cacheObject[hash] = {
|
||||
value: result,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
@@ -92,17 +100,17 @@ function cacheRequest(func, funcName, funcScope, self) {
|
||||
|
||||
function getRequestHash(args) {
|
||||
const argsJson = JSON.stringify(args);
|
||||
return argsJson.getHash();
|
||||
return getHash(argsJson);
|
||||
}
|
||||
|
||||
String.prototype.getHash = function() {
|
||||
var hash = 0, i, chr, len;
|
||||
if (this.length !== 0) {
|
||||
for (i = 0, len = this.length; i < len; i++) {
|
||||
chr = this.charCodeAt(i);
|
||||
function getHash(str: string): number {
|
||||
let hash = 0, i, chr, len;
|
||||
if (str.length !== 0) {
|
||||
for (i = 0, len = str.length; i < len; i++) {
|
||||
chr = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
}
|
||||
23
src/datasource-zabbix/zabbix/types.ts
Normal file
23
src/datasource-zabbix/zabbix/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface ZabbixConnector {
|
||||
getHistory: (items, timeFrom, timeTill) => Promise<any>;
|
||||
getTrend: (items, timeFrom, timeTill) => Promise<any>;
|
||||
getItemsByIDs: (itemids) => Promise<any>;
|
||||
getEvents: (objectids, timeFrom, timeTo, showEvents, limit?) => Promise<any>;
|
||||
getAlerts: (itemids, timeFrom?, timeTo?) => Promise<any>;
|
||||
getHostAlerts: (hostids, applicationids, options?) => Promise<any>;
|
||||
getAcknowledges: (eventids) => Promise<any>;
|
||||
getITService: (serviceids?) => Promise<any>;
|
||||
acknowledgeEvent: (eventid, message) => Promise<any>;
|
||||
getProxies: () => Promise<any>;
|
||||
getEventAlerts: (eventids) => Promise<any>;
|
||||
getExtendedEventData: (eventids) => Promise<any>;
|
||||
getMacros: (hostids: any[]) => Promise<any>;
|
||||
getVersion: () => Promise<string>;
|
||||
login: () => Promise<any>;
|
||||
|
||||
getGroups: (groupFilter?) => any;
|
||||
getHosts: (groupFilter?, hostFilter?) => any;
|
||||
getApps: (groupFilter?, hostFilter?, appFilter?) => any;
|
||||
getItems: (groupFilter?, hostFilter?, appFilter?, itemFilter?, options?) => any;
|
||||
getSLA: (itservices, timeRange, target, options?) => any;
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import mocks from '../../test-setup/mocks';
|
||||
import { Zabbix } from './zabbix';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: () => ({
|
||||
datasourceRequest: jest.fn().mockResolvedValue({data: {result: ''}}),
|
||||
}),
|
||||
}), {virtual: true});
|
||||
|
||||
describe('Zabbix', () => {
|
||||
let ctx = {};
|
||||
let zabbix;
|
||||
@@ -8,14 +13,13 @@ describe('Zabbix', () => {
|
||||
url: 'http://localhost',
|
||||
username: 'zabbix',
|
||||
password: 'zabbix',
|
||||
zabbixVersion: 4,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.options = options;
|
||||
ctx.backendSrv = mocks.backendSrvMock;
|
||||
ctx.datasourceSrv = mocks.datasourceSrvMock;
|
||||
zabbix = new Zabbix(ctx.options, ctx.backendSrvMock, ctx.datasourceSrvMock);
|
||||
// ctx.backendSrv = mocks.backendSrvMock;
|
||||
// ctx.datasourceSrv = mocks.datasourceSrvMock;
|
||||
zabbix = new Zabbix(ctx.options);
|
||||
});
|
||||
|
||||
describe('When querying proxies', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as utils from '../utils';
|
||||
import responseHandler from '../responseHandler';
|
||||
import { CachingProxy } from './proxy/cachingProxy';
|
||||
@@ -7,11 +8,19 @@ import { DBConnector } from './connectors/dbConnector';
|
||||
import { ZabbixAPIConnector } from './connectors/zabbix_api/zabbixAPIConnector';
|
||||
import { SQLConnector } from './connectors/sql/sqlConnector';
|
||||
import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector';
|
||||
import { ZabbixConnector } from './types';
|
||||
import { joinTriggersWithProblems, joinTriggersWithEvents } from '../problemsHandler';
|
||||
import { ProblemDTO } from '../types';
|
||||
|
||||
interface AppsResponse extends Array<any> {
|
||||
appFilterEmpty?: boolean;
|
||||
hostids?: any[];
|
||||
}
|
||||
|
||||
const REQUESTS_TO_PROXYFY = [
|
||||
'getHistory', 'getTrend', 'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs',
|
||||
'getEvents', 'getAlerts', 'getHostAlerts', 'getAcknowledges', 'getITService', 'getSLA', 'getVersion', 'getProxies',
|
||||
'getEventAlerts', 'getExtendedEventData'
|
||||
'getEventAlerts', 'getExtendedEventData', 'getProblems', 'getEventsHistory', 'getTriggersByIds'
|
||||
];
|
||||
|
||||
const REQUESTS_TO_CACHE = [
|
||||
@@ -24,40 +33,63 @@ const REQUESTS_TO_BIND = [
|
||||
'getExtendedEventData'
|
||||
];
|
||||
|
||||
export class Zabbix {
|
||||
constructor(options, datasourceSrv, backendSrv, datasourceId) {
|
||||
let {
|
||||
export class Zabbix implements ZabbixConnector {
|
||||
enableDirectDBConnection: boolean;
|
||||
cachingProxy: CachingProxy;
|
||||
zabbixAPI: ZabbixAPIConnector;
|
||||
getHistoryDB: any;
|
||||
dbConnector: any;
|
||||
getTrendsDB: any;
|
||||
|
||||
getHistory: (items, timeFrom, timeTill) => Promise<any>;
|
||||
getTrend: (items, timeFrom, timeTill) => Promise<any>;
|
||||
getItemsByIDs: (itemids) => Promise<any>;
|
||||
getEvents: (objectids, timeFrom, timeTo, showEvents, limit?) => Promise<any>;
|
||||
getAlerts: (itemids, timeFrom?, timeTo?) => Promise<any>;
|
||||
getHostAlerts: (hostids, applicationids, options?) => Promise<any>;
|
||||
getAcknowledges: (eventids) => Promise<any>;
|
||||
getITService: (serviceids?) => Promise<any>;
|
||||
acknowledgeEvent: (eventid, message) => Promise<any>;
|
||||
getProxies: () => Promise<any>;
|
||||
getEventAlerts: (eventids) => Promise<any>;
|
||||
getExtendedEventData: (eventids) => Promise<any>;
|
||||
getMacros: (hostids: any[]) => Promise<any>;
|
||||
getVersion: () => Promise<string>;
|
||||
login: () => Promise<any>;
|
||||
|
||||
constructor(options) {
|
||||
const {
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
basicAuth,
|
||||
withCredentials,
|
||||
zabbixVersion,
|
||||
cacheTTL,
|
||||
enableDirectDBConnection,
|
||||
dbConnectionDatasourceId,
|
||||
dbConnectionDatasourceName,
|
||||
dbConnectionRetentionPolicy,
|
||||
datasourceId,
|
||||
} = options;
|
||||
|
||||
this.enableDirectDBConnection = enableDirectDBConnection;
|
||||
|
||||
// Initialize caching proxy for requests
|
||||
let cacheOptions = {
|
||||
const cacheOptions = {
|
||||
enabled: true,
|
||||
ttl: cacheTTL
|
||||
};
|
||||
this.cachingProxy = new CachingProxy(cacheOptions);
|
||||
|
||||
this.zabbixAPI = new ZabbixAPIConnector(url, username, password, zabbixVersion, basicAuth, withCredentials, backendSrv, datasourceId);
|
||||
this.zabbixAPI = new ZabbixAPIConnector(url, username, password, basicAuth, withCredentials, datasourceId);
|
||||
|
||||
this.proxyfyRequests();
|
||||
this.cacheRequests();
|
||||
this.bindRequests();
|
||||
|
||||
if (enableDirectDBConnection) {
|
||||
const connectorOptions = { dbConnectionRetentionPolicy };
|
||||
this.initDBConnector(dbConnectionDatasourceId, dbConnectionDatasourceName, datasourceSrv, connectorOptions)
|
||||
const connectorOptions: any = { dbConnectionRetentionPolicy };
|
||||
this.initDBConnector(dbConnectionDatasourceId, dbConnectionDatasourceName, connectorOptions)
|
||||
.then(() => {
|
||||
this.getHistoryDB = this.cachingProxy.proxyfyWithCache(this.dbConnector.getHistory, 'getHistory', this.dbConnector);
|
||||
this.getTrendsDB = this.cachingProxy.proxyfyWithCache(this.dbConnector.getTrends, 'getTrends', this.dbConnector);
|
||||
@@ -65,34 +97,34 @@ export class Zabbix {
|
||||
}
|
||||
}
|
||||
|
||||
initDBConnector(datasourceId, datasourceName, datasourceSrv, options) {
|
||||
return DBConnector.loadDatasource(datasourceId, datasourceName, datasourceSrv)
|
||||
initDBConnector(datasourceId, datasourceName, options) {
|
||||
return DBConnector.loadDatasource(datasourceId, datasourceName)
|
||||
.then(ds => {
|
||||
let connectorOptions = { datasourceId, datasourceName };
|
||||
const connectorOptions: any = { datasourceId, datasourceName };
|
||||
if (ds.type === 'influxdb') {
|
||||
connectorOptions.retentionPolicy = options.dbConnectionRetentionPolicy;
|
||||
this.dbConnector = new InfluxDBConnector(connectorOptions, datasourceSrv);
|
||||
this.dbConnector = new InfluxDBConnector(connectorOptions);
|
||||
} else {
|
||||
this.dbConnector = new SQLConnector(connectorOptions, datasourceSrv);
|
||||
this.dbConnector = new SQLConnector(connectorOptions);
|
||||
}
|
||||
return this.dbConnector;
|
||||
});
|
||||
}
|
||||
|
||||
proxyfyRequests() {
|
||||
for (let request of REQUESTS_TO_PROXYFY) {
|
||||
for (const request of REQUESTS_TO_PROXYFY) {
|
||||
this.zabbixAPI[request] = this.cachingProxy.proxyfy(this.zabbixAPI[request], request, this.zabbixAPI);
|
||||
}
|
||||
}
|
||||
|
||||
cacheRequests() {
|
||||
for (let request of REQUESTS_TO_CACHE) {
|
||||
for (const request of REQUESTS_TO_CACHE) {
|
||||
this.zabbixAPI[request] = this.cachingProxy.cacheRequest(this.zabbixAPI[request], request, this.zabbixAPI);
|
||||
}
|
||||
}
|
||||
|
||||
bindRequests() {
|
||||
for (let request of REQUESTS_TO_BIND) {
|
||||
for (const request of REQUESTS_TO_BIND) {
|
||||
this[request] = this.zabbixAPI[request].bind(this.zabbixAPI);
|
||||
}
|
||||
}
|
||||
@@ -101,14 +133,14 @@ export class Zabbix {
|
||||
* Perform test query for Zabbix API and external history DB.
|
||||
* @return {object} test result object:
|
||||
* ```
|
||||
{
|
||||
zabbixVersion,
|
||||
dbConnectorStatus: {
|
||||
dsType,
|
||||
dsName
|
||||
}
|
||||
}
|
||||
```
|
||||
* {
|
||||
* zabbixVersion,
|
||||
* dbConnectorStatus: {
|
||||
* dsType,
|
||||
* dsName
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
// testDataSource() {
|
||||
// let zabbixVersion;
|
||||
@@ -143,19 +175,20 @@ export class Zabbix {
|
||||
// }
|
||||
|
||||
getItemsFromTarget(target, options) {
|
||||
let parts = ['group', 'host', 'application', 'item'];
|
||||
let filters = _.map(parts, p => target[p].filter);
|
||||
const parts = ['group', 'host', 'application', 'item'];
|
||||
const filters = _.map(parts, p => target[p].filter);
|
||||
return this.getItems(...filters, options);
|
||||
}
|
||||
|
||||
getHostsFromTarget(target) {
|
||||
let parts = ['group', 'host', 'application'];
|
||||
let filters = _.map(parts, p => target[p].filter);
|
||||
const parts = ['group', 'host', 'application'];
|
||||
const filters = _.map(parts, p => target[p].filter);
|
||||
return Promise.all([
|
||||
this.getHosts(...filters),
|
||||
this.getApps(...filters),
|
||||
]).then((results) => {
|
||||
let [hosts, apps] = results;
|
||||
]).then(results => {
|
||||
const hosts = results[0];
|
||||
let apps: AppsResponse = results[1];
|
||||
if (apps.appFilterEmpty) {
|
||||
apps = [];
|
||||
}
|
||||
@@ -178,12 +211,12 @@ export class Zabbix {
|
||||
getAllHosts(groupFilter) {
|
||||
return this.getGroups(groupFilter)
|
||||
.then(groups => {
|
||||
let groupids = _.map(groups, 'groupid');
|
||||
const groupids = _.map(groups, 'groupid');
|
||||
return this.zabbixAPI.getHosts(groupids);
|
||||
});
|
||||
}
|
||||
|
||||
getHosts(groupFilter, hostFilter) {
|
||||
getHosts(groupFilter?, hostFilter?) {
|
||||
return this.getAllHosts(groupFilter)
|
||||
.then(hosts => findByFilter(hosts, hostFilter));
|
||||
}
|
||||
@@ -194,34 +227,34 @@ export class Zabbix {
|
||||
getAllApps(groupFilter, hostFilter) {
|
||||
return this.getHosts(groupFilter, hostFilter)
|
||||
.then(hosts => {
|
||||
let hostids = _.map(hosts, 'hostid');
|
||||
const hostids = _.map(hosts, 'hostid');
|
||||
return this.zabbixAPI.getApps(hostids);
|
||||
});
|
||||
}
|
||||
|
||||
getApps(groupFilter, hostFilter, appFilter) {
|
||||
getApps(groupFilter?, hostFilter?, appFilter?): Promise<AppsResponse> {
|
||||
return this.getHosts(groupFilter, hostFilter)
|
||||
.then(hosts => {
|
||||
let hostids = _.map(hosts, 'hostid');
|
||||
const hostids = _.map(hosts, 'hostid');
|
||||
if (appFilter) {
|
||||
return this.zabbixAPI.getApps(hostids)
|
||||
.then(apps => filterByQuery(apps, appFilter));
|
||||
} else {
|
||||
return {
|
||||
appFilterEmpty: true,
|
||||
hostids: hostids
|
||||
};
|
||||
const appsResponse: AppsResponse = hostids;
|
||||
appsResponse.hostids = hostids;
|
||||
appsResponse.appFilterEmpty = true;
|
||||
return Promise.resolve(appsResponse);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAllItems(groupFilter, hostFilter, appFilter, options = {}) {
|
||||
getAllItems(groupFilter, hostFilter, appFilter, options: any = {}) {
|
||||
return this.getApps(groupFilter, hostFilter, appFilter)
|
||||
.then(apps => {
|
||||
if (apps.appFilterEmpty) {
|
||||
return this.zabbixAPI.getItems(apps.hostids, undefined, options.itemtype);
|
||||
} else {
|
||||
let appids = _.map(apps, 'applicationid');
|
||||
const appids = _.map(apps, 'applicationid');
|
||||
return this.zabbixAPI.getItems(undefined, appids, options.itemtype);
|
||||
}
|
||||
})
|
||||
@@ -235,34 +268,54 @@ export class Zabbix {
|
||||
.then(this.expandUserMacro.bind(this));
|
||||
}
|
||||
|
||||
expandUserMacro(items) {
|
||||
let hostids = getHostIds(items);
|
||||
expandUserMacro(items, isTriggerItem) {
|
||||
const hostids = getHostIds(items);
|
||||
return this.getMacros(hostids)
|
||||
.then(macros => {
|
||||
_.forEach(items, item => {
|
||||
if (utils.containsMacro(item.name)) {
|
||||
item.name = utils.replaceMacro(item, macros);
|
||||
if (utils.containsMacro(isTriggerItem ? item.url : item.name)) {
|
||||
if (isTriggerItem) {
|
||||
item.url = utils.replaceMacro(item, macros, isTriggerItem);
|
||||
} else {
|
||||
item.name = utils.replaceMacro(item, macros);
|
||||
}
|
||||
}
|
||||
});
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
getItems(groupFilter, hostFilter, appFilter, itemFilter, options = {}) {
|
||||
getItems(groupFilter?, hostFilter?, appFilter?, itemFilter?, options = {}) {
|
||||
return this.getAllItems(groupFilter, hostFilter, appFilter, options)
|
||||
.then(items => filterByQuery(items, itemFilter));
|
||||
}
|
||||
|
||||
getItemValues(groupFilter?, hostFilter?, appFilter?, itemFilter?, options: any = {}) {
|
||||
return this.getItems(groupFilter, hostFilter, appFilter, itemFilter, options).then(items => {
|
||||
let timeRange = [moment().subtract(2, 'h').unix(), moment().unix()];
|
||||
if (options.range) {
|
||||
timeRange = [options.range.from.unix(), options.range.to.unix()];
|
||||
}
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
|
||||
return this.zabbixAPI.getHistory(items, timeFrom, timeTo).then(history => {
|
||||
if (history) {
|
||||
const values = _.uniq(history.map(v => v.value));
|
||||
return values.map(value => ({ name: value }));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getITServices(itServiceFilter) {
|
||||
return this.zabbixAPI.getITService()
|
||||
.then(itServices => findByFilter(itServices, itServiceFilter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query - convert target filters to array of Zabbix items
|
||||
*/
|
||||
getTriggers(groupFilter, hostFilter, appFilter, options, proxyFilter) {
|
||||
let promises = [
|
||||
getProblems(groupFilter, hostFilter, appFilter, proxyFilter?, options?) {
|
||||
const promises = [
|
||||
this.getGroups(groupFilter),
|
||||
this.getHosts(groupFilter, hostFilter),
|
||||
this.getApps(groupFilter, hostFilter, appFilter)
|
||||
@@ -270,8 +323,8 @@ export class Zabbix {
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(results => {
|
||||
let [filteredGroups, filteredHosts, filteredApps] = results;
|
||||
let query = {};
|
||||
const [filteredGroups, filteredHosts, filteredApps] = results;
|
||||
const query: any = {};
|
||||
|
||||
if (appFilter) {
|
||||
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
|
||||
@@ -285,8 +338,53 @@ export class Zabbix {
|
||||
|
||||
return query;
|
||||
})
|
||||
.then(query => this.zabbixAPI.getTriggers(query.groupids, query.hostids, query.applicationids, options))
|
||||
.then(triggers => this.filterTriggersByProxy(triggers, proxyFilter));
|
||||
.then(query => this.zabbixAPI.getProblems(query.groupids, query.hostids, query.applicationids, options))
|
||||
.then(problems => {
|
||||
const triggerids = problems?.map(problem => problem.objectid);
|
||||
return Promise.all([
|
||||
Promise.resolve(problems),
|
||||
this.zabbixAPI.getTriggersByIds(triggerids)
|
||||
]);
|
||||
})
|
||||
.then(([problems, triggers]) => joinTriggersWithProblems(problems, triggers))
|
||||
.then(triggers => this.filterTriggersByProxy(triggers, proxyFilter))
|
||||
.then(triggers => this.expandUserMacro.bind(this)(triggers, true));
|
||||
}
|
||||
|
||||
getProblemsHistory(groupFilter, hostFilter, appFilter, proxyFilter?, options?): Promise<ProblemDTO[]> {
|
||||
const { valueFromEvent } = options;
|
||||
|
||||
const promises = [
|
||||
this.getGroups(groupFilter),
|
||||
this.getHosts(groupFilter, hostFilter),
|
||||
this.getApps(groupFilter, hostFilter, appFilter)
|
||||
];
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(results => {
|
||||
const [filteredGroups, filteredHosts, filteredApps] = results;
|
||||
const query: any = {};
|
||||
|
||||
if (appFilter) {
|
||||
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
|
||||
}
|
||||
if (hostFilter) {
|
||||
query.hostids = _.map(filteredHosts, 'hostid');
|
||||
}
|
||||
if (groupFilter) {
|
||||
query.groupids = _.map(filteredGroups, 'groupid');
|
||||
}
|
||||
|
||||
return query;
|
||||
})
|
||||
.then(query => this.zabbixAPI.getEventsHistory(query.groupids, query.hostids, query.applicationids, options))
|
||||
.then(problems => {
|
||||
const triggerids = problems?.map(problem => problem.objectid);
|
||||
return Promise.all([Promise.resolve(problems), this.zabbixAPI.getTriggersByIds(triggerids)]);
|
||||
})
|
||||
.then(([problems, triggers]) => joinTriggersWithEvents(problems, triggers, { valueFromEvent }))
|
||||
.then(triggers => this.filterTriggersByProxy(triggers, proxyFilter))
|
||||
.then(triggers => this.expandUserMacro.bind(this)(triggers, true));
|
||||
}
|
||||
|
||||
filterTriggersByProxy(triggers, proxyFilter) {
|
||||
@@ -295,14 +393,13 @@ export class Zabbix {
|
||||
if (proxyFilter && proxyFilter !== '/.*/' && triggers) {
|
||||
const proxy_ids = proxies.map(proxy => proxy.proxyid);
|
||||
triggers = triggers.filter(trigger => {
|
||||
let filtered = false;
|
||||
for(let i = 0; i < trigger.hosts.length; i++) {
|
||||
for (let i = 0; i < trigger.hosts.length; i++) {
|
||||
const host = trigger.hosts[i];
|
||||
if (proxy_ids.includes(host.proxy_hostid)) {
|
||||
filtered = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return triggers;
|
||||
@@ -318,7 +415,7 @@ export class Zabbix {
|
||||
}
|
||||
|
||||
getHistoryTS(items, timeRange, options) {
|
||||
let [timeFrom, timeTo] = timeRange;
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
if (this.enableDirectDBConnection) {
|
||||
return this.getHistoryDB(items, timeFrom, timeTo, options)
|
||||
.then(history => this.dbConnector.handleGrafanaTSResponse(history, items));
|
||||
@@ -329,12 +426,12 @@ export class Zabbix {
|
||||
}
|
||||
|
||||
getTrends(items, timeRange, options) {
|
||||
let [timeFrom, timeTo] = timeRange;
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
if (this.enableDirectDBConnection) {
|
||||
return this.getTrendsDB(items, timeFrom, timeTo, options)
|
||||
.then(history => this.dbConnector.handleGrafanaTSResponse(history, items));
|
||||
} else {
|
||||
let valueType = options.consolidateBy || options.valueType;
|
||||
const valueType = options.consolidateBy || options.valueType;
|
||||
return this.zabbixAPI.getTrend(items, timeFrom, timeTo)
|
||||
.then(history => responseHandler.handleTrends(history, items, valueType))
|
||||
.then(responseHandler.sortTimeseries); // Sort trend data, issue #202
|
||||
@@ -342,7 +439,7 @@ export class Zabbix {
|
||||
}
|
||||
|
||||
getHistoryText(items, timeRange, target) {
|
||||
let [timeFrom, timeTo] = timeRange;
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
if (items.length) {
|
||||
return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
|
||||
.then(history => {
|
||||
@@ -358,15 +455,11 @@ export class Zabbix {
|
||||
}
|
||||
|
||||
getSLA(itservices, timeRange, target, options) {
|
||||
let itServices = itservices;
|
||||
if (options.isOldVersion) {
|
||||
itServices = _.filter(itServices, {'serviceid': target.itservice.serviceid});
|
||||
}
|
||||
let itServiceIds = _.map(itServices, 'serviceid');
|
||||
const itServiceIds = _.map(itservices, 'serviceid');
|
||||
return this.zabbixAPI.getSLA(itServiceIds, timeRange, options)
|
||||
.then(slaResponse => {
|
||||
return _.map(itServiceIds, serviceid => {
|
||||
let itservice = _.find(itServices, {'serviceid': serviceid});
|
||||
const itservice = _.find(itservices, {'serviceid': serviceid});
|
||||
return responseHandler.handleSLAResponse(itservice, target.slaProperty, slaResponse);
|
||||
});
|
||||
});
|
||||
@@ -382,7 +475,7 @@ export class Zabbix {
|
||||
* @return array with finded element or empty array
|
||||
*/
|
||||
function findByName(list, name) {
|
||||
var finded = _.find(list, {'name': name});
|
||||
const finded = _.find(list, {'name': name});
|
||||
if (finded) {
|
||||
return [finded];
|
||||
} else {
|
||||
@@ -399,7 +492,7 @@ function findByName(list, name) {
|
||||
* @return {[type]} array with finded element or empty array
|
||||
*/
|
||||
function filterByName(list, name) {
|
||||
var finded = _.filter(list, {'name': name});
|
||||
const finded = _.filter(list, {'name': name});
|
||||
if (finded) {
|
||||
return finded;
|
||||
} else {
|
||||
@@ -408,8 +501,8 @@ function filterByName(list, name) {
|
||||
}
|
||||
|
||||
function filterByRegex(list, regex) {
|
||||
var filterPattern = utils.buildRegex(regex);
|
||||
return _.filter(list, function (zbx_obj) {
|
||||
const filterPattern = utils.buildRegex(regex);
|
||||
return _.filter(list, (zbx_obj) => {
|
||||
return filterPattern.test(zbx_obj.name);
|
||||
});
|
||||
}
|
||||
@@ -431,7 +524,7 @@ function filterByQuery(list, filter) {
|
||||
}
|
||||
|
||||
function getHostIds(items) {
|
||||
let hostIds = _.map(items, item => {
|
||||
const hostIds = _.map(items, item => {
|
||||
return _.map(item.hosts, 'hostid');
|
||||
});
|
||||
return _.uniq(_.flatten(hostIds));
|
||||
Reference in New Issue
Block a user