import _ from 'lodash'; import moment from 'moment'; 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'; import { ZabbixConnector } from './types'; import { joinTriggersWithProblems, joinTriggersWithEvents } from '../problemsHandler'; import { ProblemDTO } from '../types'; interface AppsResponse extends Array { 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', 'getProblems', 'getEventsHistory', 'getTriggersByIds' ]; 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 implements ZabbixConnector { enableDirectDBConnection: boolean; cachingProxy: CachingProxy; zabbixAPI: ZabbixAPIConnector; getHistoryDB: any; dbConnector: any; getTrendsDB: any; getHistory: (items, timeFrom, timeTill) => Promise; getTrend: (items, timeFrom, timeTill) => Promise; getItemsByIDs: (itemids) => Promise; getEvents: (objectids, timeFrom, timeTo, showEvents, limit?) => Promise; getAlerts: (itemids, timeFrom?, timeTo?) => Promise; getHostAlerts: (hostids, applicationids, options?) => Promise; getAcknowledges: (eventids) => Promise; getITService: (serviceids?) => Promise; acknowledgeEvent: (eventid, message) => Promise; getProxies: () => Promise; getEventAlerts: (eventids) => Promise; getExtendedEventData: (eventids) => Promise; getMacros: (hostids: any[]) => Promise; getVersion: () => Promise; login: () => Promise; constructor(options) { const { url, username, password, basicAuth, withCredentials, cacheTTL, enableDirectDBConnection, dbConnectionDatasourceId, dbConnectionDatasourceName, dbConnectionRetentionPolicy, } = options; this.enableDirectDBConnection = enableDirectDBConnection; // Initialize caching proxy for requests const cacheOptions = { enabled: true, ttl: cacheTTL }; this.cachingProxy = new CachingProxy(cacheOptions); this.zabbixAPI = new ZabbixAPIConnector(url, username, password, basicAuth, withCredentials); this.proxyfyRequests(); this.cacheRequests(); this.bindRequests(); if (enableDirectDBConnection) { 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); }); } } initDBConnector(datasourceId, datasourceName, options) { return DBConnector.loadDatasource(datasourceId, datasourceName) .then(ds => { const connectorOptions: any = { datasourceId, datasourceName }; if (ds.type === 'influxdb') { connectorOptions.retentionPolicy = options.dbConnectionRetentionPolicy; this.dbConnector = new InfluxDBConnector(connectorOptions); } else { this.dbConnector = new SQLConnector(connectorOptions); } return this.dbConnector; }); } proxyfyRequests() { for (const request of REQUESTS_TO_PROXYFY) { this.zabbixAPI[request] = this.cachingProxy.proxyfy(this.zabbixAPI[request], request, this.zabbixAPI); } } cacheRequests() { for (const request of REQUESTS_TO_CACHE) { this.zabbixAPI[request] = this.cachingProxy.cacheRequest(this.zabbixAPI[request], request, this.zabbixAPI); } } bindRequests() { for (const 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) { const parts = ['group', 'host', 'application', 'item']; const filters = _.map(parts, p => target[p].filter); return this.getItems(...filters, options); } getHostsFromTarget(target) { const parts = ['group', 'host', 'application']; const filters = _.map(parts, p => target[p].filter); return Promise.all([ this.getHosts(...filters), this.getApps(...filters), ]).then(results => { const hosts = results[0]; let apps: AppsResponse = results[1]; 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 => { const 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 => { const hostids = _.map(hosts, 'hostid'); return this.zabbixAPI.getApps(hostids); }); } getApps(groupFilter?, hostFilter?, appFilter?): Promise { return this.getHosts(groupFilter, hostFilter) .then(hosts => { const hostids = _.map(hosts, 'hostid'); if (appFilter) { return this.zabbixAPI.getApps(hostids) .then(apps => filterByQuery(apps, appFilter)); } else { const appsResponse: AppsResponse = hostids; appsResponse.hostids = hostids; appsResponse.appFilterEmpty = true; return Promise.resolve(appsResponse); } }); } 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 { const 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) { const 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)); } 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)); } getProblems(groupFilter, hostFilter, appFilter, proxyFilter?, 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.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 { 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) { return this.getFilteredProxies(proxyFilter) .then(proxies => { if (proxyFilter && proxyFilter !== '/.*/' && triggers) { const proxy_ids = proxies.map(proxy => proxy.proxyid); triggers = triggers.filter(trigger => { for (let i = 0; i < trigger.hosts.length; i++) { const host = trigger.hosts[i]; if (proxy_ids.includes(host.proxy_hostid)) { return true; } } return false; }); } 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) { const [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) { const [timeFrom, timeTo] = timeRange; if (this.enableDirectDBConnection) { return this.getTrendsDB(items, timeFrom, timeTo, options) .then(history => this.dbConnector.handleGrafanaTSResponse(history, items)); } else { 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 } } getHistoryText(items, timeRange, target) { const [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) { const itServiceIds = _.map(itservices, 'serviceid'); return this.zabbixAPI.getSLA(itServiceIds, timeRange, options) .then(slaResponse => { return _.map(itServiceIds, serviceid => { const 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) { const 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) { const finded = _.filter(list, {'name': name}); if (finded) { return finded; } else { return []; } } function filterByRegex(list, regex) { const filterPattern = utils.buildRegex(regex); return _.filter(list, (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) { const hostIds = _.map(items, item => { return _.map(item.hosts, 'hostid'); }); return _.uniq(_.flatten(hostIds)); }