import _ from 'lodash'; import { Observable } from 'rxjs'; 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 utils from './utils'; import * as migrations from './migrations'; import * as metricFunctions from './metricFunctions'; import * as c from './constants'; import dataProcessor from './dataProcessor'; import responseHandler from './responseHandler'; import problemsHandler from './problemsHandler'; import { Zabbix } from './zabbix/zabbix'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector'; import { ProblemDTO, ShowProblemTypes, VariableQueryTypes, ZabbixDSOptions, ZabbixMetricsQuery } from './types'; import { BackendSrvRequest, getBackendSrv, getTemplateSrv, toDataQueryResponse } from '@grafana/runtime'; import { DataFrame, dataFrameFromJSON, DataQueryRequest, DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, FieldType, isDataFrame, LoadingState } from '@grafana/data'; export class ZabbixDatasource extends DataSourceApi { name: string; basicAuth: any; withCredentials: any; trends: boolean; trendsFrom: string; trendsRange: string; cacheTTL: any; disableReadOnlyUsersAck: boolean; disableDataAlignment: boolean; enableDirectDBConnection: boolean; dbConnectionDatasourceId: number; dbConnectionDatasourceName: string; dbConnectionRetentionPolicy: string; enableDebugLog: boolean; datasourceId: number; zabbix: Zabbix; replaceTemplateVars: (target: any, scopedVars?: any) => any; /** @ngInject */ constructor(instanceSettings: DataSourceInstanceSettings, private templateSrv) { super(instanceSettings); this.templateSrv = templateSrv; this.enableDebugLog = config.buildInfo.env === 'development'; // Use custom format for template variables this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv); // General data source settings this.datasourceId = instanceSettings.id; this.name = instanceSettings.name; this.basicAuth = instanceSettings.basicAuth; this.withCredentials = instanceSettings.withCredentials; const jsonData = migrations.migrateDSConfig(instanceSettings.jsonData); // Use trends instead history since specified time this.trends = jsonData.trends; this.trendsFrom = jsonData.trendsFrom || '7d'; this.trendsRange = jsonData.trendsRange || '4d'; // Set cache update interval const ttl = jsonData.cacheTTL || '1h'; this.cacheTTL = utils.parseInterval(ttl); // Other options this.disableReadOnlyUsersAck = jsonData.disableReadOnlyUsersAck; this.disableDataAlignment = jsonData.disableDataAlignment; // Direct DB Connection options this.enableDirectDBConnection = jsonData.dbConnectionEnable || false; this.dbConnectionDatasourceId = jsonData.dbConnectionDatasourceId; this.dbConnectionDatasourceName = jsonData.dbConnectionDatasourceName; this.dbConnectionRetentionPolicy = jsonData.dbConnectionRetentionPolicy; const zabbixOptions = { basicAuth: this.basicAuth, withCredentials: this.withCredentials, cacheTTL: this.cacheTTL, enableDirectDBConnection: this.enableDirectDBConnection, dbConnectionDatasourceId: this.dbConnectionDatasourceId, dbConnectionDatasourceName: this.dbConnectionDatasourceName, dbConnectionRetentionPolicy: this.dbConnectionRetentionPolicy, datasourceId: this.datasourceId, }; this.zabbix = new Zabbix(zabbixOptions); } //////////////////////// // Datasource methods // //////////////////////// /** * Query panel data. Calls for each panel in dashboard. * @param {Object} request Contains time range, targets and other info. * @return {Object} Grafana metrics object with timeseries data for each target. */ query(request: DataQueryRequest): Promise | Observable { // Migrate old targets const requestTargets = request.targets.map(t => { // Prevent changes of original object const target = _.cloneDeep(t); return migrations.migrate(target); }); const backendResponsePromise = this.backendQuery({ ...request, targets: requestTargets }); const dbConnectionResponsePromise = this.dbConnectionQuery({ ...request, targets: requestTargets }); const frontendResponsePromise = this.frontendQuery({ ...request, targets: requestTargets }); return Promise.all([backendResponsePromise, dbConnectionResponsePromise, frontendResponsePromise]) .then(rsp => { // Merge backend and frontend queries results const [backendRes, dbConnectionRes, frontendRes] = rsp; if (dbConnectionRes.data) { backendRes.data = backendRes.data.concat(dbConnectionRes.data); } if (frontendRes.data) { backendRes.data = backendRes.data.concat(frontendRes.data); } return { data: backendRes.data, state: LoadingState.Done, key: request.requestId, }; }); } async backendQuery(request: DataQueryRequest): Promise { const { intervalMs, maxDataPoints, range, requestId } = request; const targets = request.targets.filter(this.isBackendTarget); // Add range variables request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range)); const queries = _.compact(targets.map((query) => { // Don't request for hidden targets if (query.hide) { return null; } this.replaceTargetVariables(query, request); return { ...query, datasourceId: this.datasourceId, intervalMs, maxDataPoints, }; })); // Return early if no queries exist if (!queries.length) { return Promise.resolve({ data: [] }); } const body: any = { queries }; if (range) { body.range = range; body.from = range.from.valueOf().toString(); body.to = range.to.valueOf().toString(); } let rsp: any; try { rsp = await getBackendSrv().fetch({ url: '/api/ds/query', method: 'POST', data: body, requestId, }).toPromise(); } catch (err) { return toDataQueryResponse(err); } const resp = toDataQueryResponse(rsp); this.sortByRefId(resp); this.applyFrontendFunctions(resp, request); if (responseHandler.isConvertibleToWide(resp.data)) { console.log('Converting response to the wide format'); resp.data = responseHandler.convertToWide(resp.data); } return resp; } async frontendQuery(request: DataQueryRequest): Promise { const frontendTargets = request.targets.filter(t => !(this.isBackendTarget(t) || this.isDBConnectionTarget(t))); const promises = _.map(frontendTargets, target => { // Don't request for hidden targets if (target.hide) { return []; } // Add range variables request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range)); this.replaceTargetVariables(target, request); const timeRange = this.buildTimeRange(request, target); if (target.queryType === c.MODE_TEXT) { // Text query // Don't request undefined targets if (!target.group || !target.host || !target.item) { return []; } return this.queryTextData(target, timeRange); } else if (target.queryType === c.MODE_ITSERVICE) { // IT services query return this.queryITServiceData(target, timeRange, request); } else if (target.queryType === c.MODE_TRIGGERS) { // Triggers query return this.queryTriggersData(target, timeRange); } else if (target.queryType === c.MODE_PROBLEMS) { // Problems query return this.queryProblems(target, timeRange, request); } else { return []; } }); // Data for panel (all targets) return Promise.all(_.flatten(promises)) .then(_.flatten) .then(data => { if (data && data.length > 0 && isDataFrame(data[0]) && !utils.isProblemsDataFrame(data[0])) { data = responseHandler.alignFrames(data); if (responseHandler.isConvertibleToWide(data)) { console.log('Converting response to the wide format'); data = responseHandler.convertToWide(data); } } return { data }; }); } async dbConnectionQuery(request: DataQueryRequest): Promise { const targets = request.targets.filter(this.isDBConnectionTarget); const queries = _.compact(targets.map((target) => { // Don't request for hidden targets if (target.hide) { return []; } // Add range variables request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range)); this.replaceTargetVariables(target, request); const timeRange = this.buildTimeRange(request, target); const useTrends = this.isUseTrends(timeRange); if (!target.queryType || target.queryType === c.MODE_METRICS) { return this.queryNumericData(target, timeRange, useTrends, request); } else if (target.queryType === c.MODE_ITEMID) { // Item ID query if (!target.itemids) { return []; } return this.queryItemIdData(target, timeRange, useTrends, request); } else { return []; } })); const promises: Promise = Promise.all(queries) .then(_.flatten) .then(data => ({ data })); return promises; } buildTimeRange(request, target) { let timeFrom = Math.ceil(dateMath.parse(request.range.from) / 1000); let timeTo = Math.ceil(dateMath.parse(request.range.to) / 1000); // Apply Time-related functions (timeShift(), etc) const timeFunctions = bindFunctionDefs(target.functions, 'Time'); if (timeFunctions.length) { const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]); timeFrom = time_from; timeTo = time_to; } return [timeFrom, timeTo]; } /** * Query target data for Metrics */ async queryNumericData(target, timeRange, useTrends, request): Promise { const getItemOptions = { itemtype: 'num' }; const items = await this.zabbix.getItemsFromTarget(target, getItemOptions); const queryStart = new Date().getTime(); const result = await this.queryNumericDataForItems(items, target, timeRange, useTrends, request); const queryEnd = new Date().getTime(); if (this.enableDebugLog) { console.log(`Datasource::Performance Query Time (${this.name}): ${queryEnd - queryStart}`); } return this.handleBackendPostProcessingResponse(result, request, target); } /** * Query history for numeric items */ async queryNumericDataForItems(items, target: ZabbixMetricsQuery, timeRange, useTrends, request) { let history; request.valueType = this.getTrendValueType(target); request.consolidateBy = getConsolidateBy(target) || request.valueType; if (useTrends) { history = await this.zabbix.getTrends(items, timeRange, request); } else { history = await this.zabbix.getHistoryTS(items, timeRange, request); } const range = { from: timeRange[0], to: timeRange[1], }; return await this.invokeDataProcessingQuery(history, target, range); } async invokeDataProcessingQuery(timeSeriesData, query, timeRange) { // Request backend for data processing const requestOptions: BackendSrvRequest = { url: `/api/datasources/${this.datasourceId}/resources/db-connection-post`, method: 'POST', headers: { 'Content-Type': 'application/json' }, hideFromInspector: false, data: { series: timeSeriesData, query, timeRange, }, }; const response: any = await getBackendSrv().fetch(requestOptions).toPromise(); return response.data; } handleBackendPostProcessingResponse(response, request, target) { const frames = []; for (const frameJSON of response) { const frame = dataFrameFromJSON(frameJSON); frame.refId = target.refId; frames.push(frame); } const resp = { data: frames }; this.sortByRefId(resp); this.applyFrontendFunctions(resp, request); if (responseHandler.isConvertibleToWide(resp.data)) { console.log('Converting response to the wide format'); resp.data = responseHandler.convertToWide(resp.data); } return resp.data; } getTrendValueType(target) { // Find trendValue() function and get specified trend value const trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); const trendValueFunc = _.find(target.functions, func => { return _.includes(trendFunctions, func.def.name); }); return trendValueFunc ? trendValueFunc.params[0] : "avg"; } sortByRefId(response: DataQueryResponse) { response.data.sort((a, b) => { if (a.refId < b.refId) { return -1; } else if (a.refId > b.refId) { return 1; } return 0; }); } applyFrontendFunctions(response: DataQueryResponse, request: DataQueryRequest) { for (let i = 0; i < response.data.length; i++) { const frame: DataFrame = response.data[i]; const target = getRequestTarget(request, frame.refId); // Apply alias functions const aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); utils.sequence(aliasFunctions)(frame); } return response; } /** * Query target data for Text */ queryTextData(target, timeRange) { const options = { itemtype: 'text' }; return this.zabbix.getItemsFromTarget(target, options) .then(items => { return this.zabbix.getHistoryText(items, timeRange, target); }) .then(result => { if (target.resultFormat !== 'table') { return result.map(s => responseHandler.seriesToDataFrame(s, target, [], FieldType.string)); } return result; }); } /** * Query target data for Item ID */ queryItemIdData(target, timeRange, useTrends, options) { let itemids = target.itemids; itemids = this.templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat); itemids = _.map(itemids.split(','), itemid => itemid.trim()); if (!itemids) { return []; } return this.zabbix.getItemsByIDs(itemids) .then(items => { return this.queryNumericDataForItems(items, target, timeRange, useTrends, options); }); } /** * Query target data for IT Services */ async queryITServiceData(target, timeRange, request) { // Don't show undefined and hidden targets if (target.hide || (!target.itservice && !target.itServiceFilter) || !target.slaProperty) { return []; } let itServiceFilter; request.isOldVersion = target.itservice && !target.itServiceFilter; if (request.isOldVersion) { // Backward compatibility itServiceFilter = '/.*/'; } else { itServiceFilter = this.replaceTemplateVars(target.itServiceFilter, request.scopedVars); } request.slaInterval = target.slaInterval; let itservices = await this.zabbix.getITServices(itServiceFilter); if (request.isOldVersion) { itservices = _.filter(itservices, { 'serviceid': target.itservice?.serviceid }); } const itservicesdp = await this.zabbix.getSLA(itservices, timeRange, target, request); const backendRequest = responseHandler.itServiceResponseToTimeSeries(itservicesdp, target.slaInterval); const processedResponse = await this.invokeDataProcessingQuery(backendRequest, target, {}); return this.handleBackendPostProcessingResponse(processedResponse, request, target); } queryTriggersData(target, timeRange) { const [timeFrom, timeTo] = timeRange; return this.zabbix.getHostsFromTarget(target) .then(results => { const [hosts, apps] = results; if (hosts.length) { const hostids = _.map(hosts, 'hostid'); const appids = _.map(apps, 'applicationid'); const options = { minSeverity: target.triggers.minSeverity, acknowledged: target.triggers.acknowledged, count: target.triggers.count, timeFrom: timeFrom, timeTo: timeTo }; const groupFilter = target.group.filter; return Promise.all([ this.zabbix.getHostAlerts(hostids, appids, options), this.zabbix.getGroups(groupFilter) ]) .then(([triggers, groups]) => { return responseHandler.handleTriggersResponse(triggers, groups, timeRange); }); } else { return Promise.resolve([]); } }); } queryProblems(target: ZabbixMetricsQuery, timeRange, options) { const [timeFrom, timeTo] = timeRange; const userIsEditor = contextSrv.isEditor || contextSrv.isGrafanaAdmin; let proxies; let showAckButton = true; const showProblems = target.showProblems || ShowProblemTypes.Problems; const showProxy = target.options.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 }, }; // replaceTemplateVars() builds regex-like string, so we should trim it. const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', ''); const tags = utils.parseTags(tagsFilterStr); tags.forEach(tag => { // Zabbix uses {"tag": "", "value": "", "operator": ""} format, where 1 means Equal tag.operator = 1; }); const problemsOptions: any = { recent: showProblems === ShowProblemTypes.Recent, minSeverity: target.options?.minSeverity, limit: target.options?.limit, }; if (tags && tags.length) { problemsOptions.tags = tags; } if (target.options?.acknowledged === 0 || target.options?.acknowledged === 1) { problemsOptions.acknowledged = !!target.options?.acknowledged; } if (target.options?.minSeverity) { let severities = [0, 1, 2, 3, 4, 5].filter(v => v >= target.options?.minSeverity); if (target.options?.severities) { severities = severities.filter(v => target.options?.severities.includes(v)); } problemsOptions.severities = severities; } let getProblemsPromise: Promise; if (showProblems === ShowProblemTypes.History || target.options?.useTimeRange) { problemsOptions.timeFrom = timeFrom; problemsOptions.timeTo = timeTo; getProblemsPromise = this.zabbix.getProblemsHistory(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions); } else { getProblemsPromise = this.zabbix.getProblems(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions); } const problemsPromises = Promise.all([ getProblemsPromise, getProxiesPromise ]) .then(([problems, sourceProxies]) => { proxies = _.keyBy(sourceProxies, 'proxyid'); return problems; }) .then(problems => problemsHandler.setMaintenanceStatus(problems)) .then(problems => problemsHandler.setAckButtonStatus(problems, showAckButton)) .then(problems => problemsHandler.filterTriggersPre(problems, replacedTarget)) .then(problems => problemsHandler.sortProblems(problems, target)) .then(problems => problemsHandler.addTriggerDataSource(problems, target)) .then(problems => problemsHandler.addTriggerHostProxy(problems, proxies)); return problemsPromises.then(problems => { const problemsDataFrame = problemsHandler.toDataFrame(problems); return problemsDataFrame; }); } /** * Test connection to Zabbix API and external history DB. */ async testDatasource() { try { const { zabbixVersion, dbConnectorStatus } = await this.zabbix.testDataSource(); let message = `Zabbix API version: ${zabbixVersion}`; if (dbConnectorStatus) { message += `, DB connector type: ${dbConnectorStatus.dsType}`; } return { status: "success", title: "Success", message: message }; } catch (error) { if (error instanceof ZabbixAPIError) { return { status: "error", title: error.message, message: error.message }; } else if (error.data && error.data.message) { return { status: "error", title: "Zabbix Client Error", message: error.data.message }; } else if (typeof (error) === 'string') { return { status: "error", title: "Unknown Error", message: error }; } else { console.log(error); return { status: "error", title: "Connection failed", message: "Could not connect to given url" }; } } } //////////////// // Templating // //////////////// /** * Find metrics from templated request. * * @param {string} query Query from Templating * @param options * @return {string} Metric name - group, host, app or item or list * of metrics in "{metric1, metric2,..., metricN}" format. */ metricFindQuery(query, options) { let resultPromise; let queryModel = _.cloneDeep(query); if (!query) { return Promise.resolve([]); } if (typeof query === 'string') { // Backward compatibility queryModel = utils.parseLegacyVariableQuery(query); } for (const prop of ['group', 'host', 'application', 'itemTag', 'item']) { queryModel[prop] = this.replaceTemplateVars(queryModel[prop], {}); } const { group, host, application, item } = queryModel; switch (queryModel.queryType) { case VariableQueryTypes.Group: resultPromise = this.zabbix.getGroups(queryModel.group); break; case VariableQueryTypes.Host: resultPromise = this.zabbix.getHosts(queryModel.group, queryModel.host); break; case VariableQueryTypes.Application: resultPromise = this.zabbix.getApps(queryModel.group, queryModel.host, queryModel.application); break; case VariableQueryTypes.ItemTag: resultPromise = this.zabbix.getItemTags(queryModel.group, queryModel.host, queryModel.itemTag); break; case VariableQueryTypes.Item: resultPromise = this.zabbix.getItems(queryModel.group, queryModel.host, queryModel.application, null, queryModel.item); break; case VariableQueryTypes.ItemValues: const range = options?.range; resultPromise = this.zabbix.getItemValues(group, host, application, item, { range }); break; default: resultPromise = Promise.resolve([]); break; } return resultPromise.then(metrics => { return _.map(metrics, formatMetric); }); } targetContainsTemplate(target: ZabbixMetricsQuery): boolean { const templateSrv = getTemplateSrv() as any; return ( templateSrv.variableExists(target.group?.filter) || templateSrv.variableExists(target.host?.filter) || templateSrv.variableExists(target.application?.filter) || templateSrv.variableExists(target.itemTag?.filter) || templateSrv.variableExists(target.item?.filter) || templateSrv.variableExists(target.proxy?.filter) || templateSrv.variableExists(target.trigger?.filter) || templateSrv.variableExists(target.textFilter) || templateSrv.variableExists(target.itServiceFilter) ); } ///////////////// // Annotations // ///////////////// annotationQuery(options) { const timeRange = options.range || options.rangeRaw; const timeFrom = Math.ceil(dateMath.parse(timeRange.from) / 1000); const timeTo = Math.ceil(dateMath.parse(timeRange.to) / 1000); const annotation = options.annotation; // Show all triggers const problemsOptions: any = { value: annotation.showOkEvents ? ['0', '1'] : '1', valueFromEvent: true, timeFrom, timeTo, }; if (annotation.minseverity) { const severities = [0, 1, 2, 3, 4, 5].filter(v => v >= Number(annotation.minseverity)); problemsOptions.severities = severities; } const groupFilter = this.replaceTemplateVars(annotation.group, {}); const hostFilter = this.replaceTemplateVars(annotation.host, {}); const appFilter = this.replaceTemplateVars(annotation.application, {}); const proxyFilter = undefined; return this.zabbix.getProblemsHistory(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions) .then(problems => { // Filter triggers by description const problemName = this.replaceTemplateVars(annotation.trigger, {}); if (utils.isRegex(problemName)) { problems = _.filter(problems, p => { return utils.buildRegex(problemName).test(p.description); }); } else if (problemName) { problems = _.filter(problems, p => { return p.description === problemName; }); } // Hide acknowledged events if option enabled if (annotation.hideAcknowledged) { problems = _.filter(problems, p => { return !p.acknowledges?.length; }); } return _.map(problems, p => { const formattedAcknowledges = utils.formatAcknowledges(p.acknowledges); let annotationTags: string[] = []; if (annotation.showHostname) { annotationTags = _.map(p.hosts, 'name'); } return { title: p.value === '1' ? 'Problem' : 'OK', time: p.timestamp * 1000, annotation: annotation, text: p.name + formattedAcknowledges, tags: annotationTags, }; }); }); } // Replace template variables replaceTargetVariables(target, options) { const parts = ['group', 'host', 'application', 'itemTag', 'item']; _.forEach(parts, p => { if (target[p] && target[p].filter) { target[p].filter = this.replaceTemplateVars(target[p].filter, options.scopedVars); } }); if (target.textFilter) { target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars); } if (target.itemids) { target.itemids = this.templateSrv.replace(target.itemids, options.scopedVars, zabbixItemIdsTemplateFormat); } _.forEach(target.functions, func => { func.params = _.map(func.params, param => { if (typeof param === 'number') { return +this.templateSrv.replace(param.toString(), options.scopedVars); } else { return this.templateSrv.replace(param, options.scopedVars); } }); }); } isUseTrends(timeRange) { const [timeFrom, timeTo] = timeRange; const useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000); const useTrendsRange = Math.ceil(utils.parseInterval(this.trendsRange) / 1000); const useTrends = this.trends && ( (timeFrom < useTrendsFrom) || (timeTo - timeFrom > useTrendsRange) ); return useTrends; } isBackendTarget = (target: any): boolean => { if (this.enableDirectDBConnection) { return false; } return target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID; }; isDBConnectionTarget = (target: any): boolean => { return this.enableDirectDBConnection && (target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID); }; } function bindFunctionDefs(functionDefs, category) { const aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); const aggFuncDefs = _.filter(functionDefs, func => { return _.includes(aggregationFunctions, func.def.name); }); return _.map(aggFuncDefs, func => { const funcInstance = metricFunctions.createFuncInstance(func.def, func.params); return funcInstance.bindFunction(dataProcessor.metricFunctions); }); } function getConsolidateBy(target) { let consolidateBy; const funcDef = _.find(target.functions, func => { return func.def.name === 'consolidateBy'; }); if (funcDef && funcDef.params && funcDef.params.length) { consolidateBy = funcDef.params[0]; } return consolidateBy; } function formatMetric(metricObj) { return { text: metricObj.name, expandable: false }; } /** * Custom formatter for template variables. * Default Grafana "regex" formatter returns * value1|value2 * This formatter returns * (value1|value2) * This format needed for using in complex regex with * template variables, for example * /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait */ export function zabbixTemplateFormat(value) { if (typeof value === 'string') { return utils.escapeRegex(value); } const escapedValues = _.map(value, utils.escapeRegex); return '(' + escapedValues.join('|') + ')'; } function zabbixItemIdsTemplateFormat(value) { if (typeof value === 'string') { return value; } return value.join(','); } /** * If template variables are used in request, replace it using regex format * and wrap with '/' for proper multi-value work. Example: * $variable selected as a, b, c * We use filter $variable * $variable -> a|b|c -> /a|b|c/ * /$variable/ -> /a|b|c/ -> /a|b|c/ */ function replaceTemplateVars(templateSrv, target, scopedVars) { let replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat); if (target && target !== replacedTarget && !utils.isRegex(replacedTarget)) { replacedTarget = '/^' + replacedTarget + '$/'; } return replacedTarget; } export function base64StringToArrowTable(text: string) { const b64 = atob(text); const arr = Uint8Array.from(b64, (c) => { return c.charCodeAt(0); }); return arr; } function getRequestTarget(request: DataQueryRequest, refId: string): any { for (let i = 0; i < request.targets.length; i++) { const target = request.targets[i]; if (target.refId === refId) { return target; } } return null; }