import _ from 'lodash'; import * as utils from '../utils'; import responseHandler from '../responseHandler'; import { CachingProxy } from './proxy/cachingProxy'; import { ZabbixNotImplemented } from './connectors/dbConnector'; import { DBConnector } from './connectors/dbConnector'; import { ZabbixAPIConnector } from './connectors/zabbix_api/zabbixAPIConnector'; import { SQLConnector } from './connectors/sql/sqlConnector'; import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector'; const REQUESTS_TO_PROXYFY = [ 'getHistory', 'getTrend', 'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts', 'getAcknowledges', 'getITService', 'getSLA', 'getVersion', 'getProxies', 'getEventAlerts', 'getExtendedEventData' ]; const REQUESTS_TO_CACHE = [ 'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs', 'getITService', 'getProxies' ]; const REQUESTS_TO_BIND = [ 'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts', 'getAcknowledges', 'getITService', 'getVersion', 'login', 'acknowledgeEvent', 'getProxies', 'getEventAlerts', 'getExtendedEventData' ]; export class Zabbix { constructor(options, datasourceSrv, backendSrv) { let { url, username, password, basicAuth, withCredentials, zabbixVersion, cacheTTL, enableDirectDBConnection, dbConnectionDatasourceId, dbConnectionDatasourceName, dbConnectionRetentionPolicy, } = options; this.enableDirectDBConnection = enableDirectDBConnection; // Initialize caching proxy for requests let cacheOptions = { enabled: true, ttl: cacheTTL }; this.cachingProxy = new CachingProxy(cacheOptions); this.zabbixAPI = new ZabbixAPIConnector(url, username, password, zabbixVersion, basicAuth, withCredentials, backendSrv); this.proxyfyRequests(); this.cacheRequests(); this.bindRequests(); if (enableDirectDBConnection) { const connectorOptions = { dbConnectionRetentionPolicy }; this.initDBConnector(dbConnectionDatasourceId, dbConnectionDatasourceName, datasourceSrv, connectorOptions) .then(() => { this.getHistoryDB = this.cachingProxy.proxyfyWithCache(this.dbConnector.getHistory, 'getHistory', this.dbConnector); this.getTrendsDB = this.cachingProxy.proxyfyWithCache(this.dbConnector.getTrends, 'getTrends', this.dbConnector); }); } } initDBConnector(datasourceId, datasourceName, datasourceSrv, options) { return DBConnector.loadDatasource(datasourceId, datasourceName, datasourceSrv) .then(ds => { let connectorOptions = { datasourceId, datasourceName }; if (ds.type === 'influxdb') { connectorOptions.retentionPolicy = options.dbConnectionRetentionPolicy; this.dbConnector = new InfluxDBConnector(connectorOptions, datasourceSrv); } else { this.dbConnector = new SQLConnector(connectorOptions, datasourceSrv); } return this.dbConnector; }); } proxyfyRequests() { for (let 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) { this.zabbixAPI[request] = this.cachingProxy.cacheRequest(this.zabbixAPI[request], request, this.zabbixAPI); } } bindRequests() { for (let request of REQUESTS_TO_BIND) { this[request] = this.zabbixAPI[request].bind(this.zabbixAPI); } } /** * Perform test query for Zabbix API and external history DB. * @return {object} test result object: * ``` { zabbixVersion, dbConnectorStatus: { dsType, dsName } } ``` */ testDataSource() { let zabbixVersion; let dbConnectorStatus; return this.getVersion() .then(version => { zabbixVersion = version; return this.login(); }) .then(() => { if (this.enableDirectDBConnection) { return this.dbConnector.testDataSource(); } else { return Promise.resolve(); } }) .catch(error => { if (error instanceof ZabbixNotImplemented) { return Promise.resolve(); } return Promise.reject(error); }) .then(testResult => { if (testResult) { dbConnectorStatus = { dsType: this.dbConnector.datasourceTypeName, dsName: this.dbConnector.datasourceName }; } return { zabbixVersion, dbConnectorStatus }; }); } getItemsFromTarget(target, options) { let parts = ['group', 'host', 'application', 'item']; let 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); return Promise.all([ this.getHosts(...filters), this.getApps(...filters), ]).then((results) => { let [hosts, apps] = results; if (apps.appFilterEmpty) { apps = []; } return [hosts, apps]; }); } getAllGroups() { return this.zabbixAPI.getGroups(); } getGroups(groupFilter) { return this.getAllGroups() .then(groups => findByFilter(groups, groupFilter)); } /** * Get list of host belonging to given groups. */ getAllHosts(groupFilter) { return this.getGroups(groupFilter) .then(groups => { let groupids = _.map(groups, 'groupid'); return this.zabbixAPI.getHosts(groupids); }); } getHosts(groupFilter, hostFilter) { return this.getAllHosts(groupFilter) .then(hosts => findByFilter(hosts, hostFilter)); } /** * Get list of applications belonging to given groups and hosts. */ getAllApps(groupFilter, hostFilter) { return this.getHosts(groupFilter, hostFilter) .then(hosts => { let hostids = _.map(hosts, 'hostid'); return this.zabbixAPI.getApps(hostids); }); } getApps(groupFilter, hostFilter, appFilter) { return this.getHosts(groupFilter, hostFilter) .then(hosts => { let hostids = _.map(hosts, 'hostid'); if (appFilter) { return this.zabbixAPI.getApps(hostids) .then(apps => filterByQuery(apps, appFilter)); } else { return { appFilterEmpty: true, hostids: hostids }; } }); } getAllItems(groupFilter, hostFilter, appFilter, options = {}) { 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'); return this.zabbixAPI.getItems(undefined, appids, options.itemtype); } }) .then(items => { if (!options.showDisabledItems) { items = _.filter(items, {'status': '0'}); } return items; }) .then(this.expandUserMacro.bind(this)); } expandUserMacro(items, isTriggerItem) { let hostids = getHostIds(items); return this.getMacros(hostids) .then(macros => { _.forEach(items, item => { 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 = {}) { return this.getAllItems(groupFilter, hostFilter, appFilter, options) .then(items => filterByQuery(items, itemFilter)); } 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 = [ this.getGroups(groupFilter), this.getHosts(groupFilter, hostFilter), this.getApps(groupFilter, hostFilter, appFilter) ]; return Promise.all(promises) .then(results => { let [filteredGroups, filteredHosts, filteredApps] = results; let query = {}; 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.getTriggers(query.groupids, query.hostids, query.applicationids, options)) .then(triggers => this.filterTriggersByProxy(triggers, proxyFilter)) .then(triggers => this.expandUserMacro.bind(this)(triggers, true)); } filterTriggersByProxy(triggers, proxyFilter) { return this.getFilteredProxies(proxyFilter) .then(proxies => { 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++) { const host = trigger.hosts[i]; if (proxy_ids.includes(host.proxy_hostid)) { filtered = true; } } return filtered; }); } return triggers; }); } getFilteredProxies(proxyFilter) { return this.zabbixAPI.getProxies() .then(proxies => { proxies.forEach(proxy => proxy.name = proxy.host); return findByFilter(proxies, proxyFilter); }); } getHistoryTS(items, timeRange, options) { let [timeFrom, timeTo] = timeRange; if (this.enableDirectDBConnection) { return this.getHistoryDB(items, timeFrom, timeTo, options) .then(history => this.dbConnector.handleGrafanaTSResponse(history, items)); } else { return this.zabbixAPI.getHistory(items, timeFrom, timeTo) .then(history => responseHandler.handleHistory(history, items)); } } getTrends(items, timeRange, options) { let [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; return this.zabbixAPI.getTrend(items, timeFrom, timeTo) .then(history => responseHandler.handleTrends(history, items, valueType)) .then(responseHandler.sortTimeseries); // Sort trend data, issue #202 } } getHistoryText(items, timeRange, target) { let [timeFrom, timeTo] = timeRange; if (items.length) { return this.zabbixAPI.getHistory(items, timeFrom, timeTo) .then(history => { if (target.resultFormat === 'table') { return responseHandler.handleHistoryAsTable(history, items, target); } else { return responseHandler.handleText(history, items, target); } }); } else { return Promise.resolve([]); } } getSLA(itservices, timeRange, target, options) { let itServices = itservices; if (options.isOldVersion) { itServices = _.filter(itServices, {'serviceid': target.itservice.serviceid}); } let itServiceIds = _.map(itServices, 'serviceid'); return this.zabbixAPI.getSLA(itServiceIds, timeRange) .then(slaResponse => { return _.map(itServiceIds, serviceid => { let itservice = _.find(itServices, {'serviceid': serviceid}); return responseHandler.handleSLAResponse(itservice, target.slaProperty, slaResponse); }); }); } } /////////////////////////////////////////////////////////////////////////////// /** * Find group, host, app or item by given name. * @param list list of groups, apps or other * @param name visible name * @return array with finded element or empty array */ function findByName(list, name) { var finded = _.find(list, {'name': name}); if (finded) { return [finded]; } else { return []; } } /** * Different hosts can contains applications and items with same name. * For this reason use _.filter, which return all elements instead _.find, * which return only first finded. * @param {[type]} list list of elements * @param {[type]} name app name * @return {[type]} array with finded element or empty array */ function filterByName(list, name) { var finded = _.filter(list, {'name': name}); if (finded) { return finded; } else { return []; } } function filterByRegex(list, regex) { var filterPattern = utils.buildRegex(regex); return _.filter(list, function (zbx_obj) { return filterPattern.test(zbx_obj.name); }); } function findByFilter(list, filter) { if (utils.isRegex(filter)) { return filterByRegex(list, filter); } else { return findByName(list, filter); } } function filterByQuery(list, filter) { if (utils.isRegex(filter)) { return filterByRegex(list, filter); } else { return filterByName(list, filter); } } function getHostIds(items) { let hostIds = _.map(items, item => { return _.map(item.hosts, 'hostid'); }); return _.uniq(_.flatten(hostIds)); }