diff --git a/debug-backend.sh b/debug-backend.sh old mode 100755 new mode 100644 index 469b037ba..d419dab01 --- a/debug-backend.sh +++ b/debug-backend.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash if [ "$1" == "-h" ]; then echo "Usage: ${BASH_SOURCE[0]} [plugin process name] [port]" exit diff --git a/devenv/zas-agent/run_zas_agent.sh b/devenv/zas-agent/run_zas_agent.sh old mode 100755 new mode 100644 index 3256f6678..cd2d9fca9 --- a/devenv/zas-agent/run_zas_agent.sh +++ b/devenv/zas-agent/run_zas_agent.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash # Run redis server first # cd /zas/redis-3.2.9/src/ diff --git a/docs/make-docs b/docs/make-docs old mode 100755 new mode 100644 index 34d470156..c88acd7ed --- a/docs/make-docs +++ b/docs/make-docs @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh # The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs. # # `make-docs` procedure changelog # diff --git a/src/datasource/components/VariableQueryEditor.tsx b/src/datasource/components/VariableQueryEditor.tsx index c6086f32d..7295b7d3b 100644 --- a/src/datasource/components/VariableQueryEditor.tsx +++ b/src/datasource/components/VariableQueryEditor.tsx @@ -13,6 +13,8 @@ export class ZabbixVariableQueryEditor extends PureComponent { - const { queryType, group, host, application, itemTag, item } = this.state; - const queryModel = { queryType, group, host, application, itemTag, item }; + const { queryType, group, host, application, itemTag, item, userMacroName , userMacroValue } = this.state; + const queryModel = { queryType, group, host, application, itemTag, item, userMacroName, userMacroValue }; this.props.onChange(queryModel, `Zabbix - ${queryType}`); }; @@ -81,14 +85,14 @@ export class ZabbixVariableQueryEditor extends PureComponent - {selectedQueryType.value !== VariableQueryTypes.Group && ( + {( selectedQueryType.value === VariableQueryTypes.Application || + selectedQueryType.value === VariableQueryTypes.ItemTag || + selectedQueryType.value === VariableQueryTypes.Item || + selectedQueryType.value === VariableQueryTypes.ItemValues || + selectedQueryType.value === VariableQueryTypes.Host ) && ( )} + + {(selectedQueryType.value === VariableQueryTypes.Application || selectedQueryType.value === VariableQueryTypes.ItemTag || selectedQueryType.value === VariableQueryTypes.Item || - selectedQueryType.value === VariableQueryTypes.ItemValues) && ( + selectedQueryType.value === VariableQueryTypes.ItemValues ) && ( <> {supportsItemTags && ( @@ -173,6 +183,75 @@ export class ZabbixVariableQueryEditor extends PureComponent )} + + + )} + + {selectedQueryType.value === VariableQueryTypes.UserMacroName && ( + + + this.handleQueryUpdate(evt, 'userMacroName')} + onBlur={this.handleQueryChange} + /> + + + )} + + {selectedQueryType.value === VariableQueryTypes.UserMacroValue && ( + <> + + + + this.handleQueryUpdate(evt, 'userMacroName')} + onBlur={this.handleQueryChange} + /> + + + + + + this.handleQueryUpdate(evt, 'userMacroValue')} + onBlur={this.handleQueryChange} + /> + + + + + + )} + + {selectedQueryType.value === VariableQueryTypes.Host && ( + <> + + + this.handleQueryUpdate(evt, 'userMacroName')} + onBlur={this.handleQueryChange} + /> + + + + + + this.handleQueryUpdate(evt, 'userMacroValue')} + onBlur={this.handleQueryChange} + /> + + )} @@ -188,3 +267,4 @@ export class ZabbixVariableQueryEditor extends PureComponent { it('should return hosts', (done) => { const tests = [ - { query: '*.*', expect: ['/.*/', '/.*/'] }, - { query: '.', expect: ['', ''] }, - { query: 'Backend.*', expect: ['Backend', '/.*/'] }, - { query: 'Back*.', expect: ['Back*', ''] }, + { query: '*.*', expect: ['/.*/', '/.*/',null, null] }, + { query: '.', expect: ['', '', null,null] }, + { query: 'Backend.*', expect: ['Backend', '/.*/', null,null] }, + { query: 'Back*.', expect: ['Back*', '',null,null] }, ]; for (const test of tests) { ctx.ds.metricFindQuery(test.query); - expect(ctx.ds.zabbix.getHosts).toBeCalledWith(test.expect[0], test.expect[1]); + expect(ctx.ds.zabbix.getHosts).toBeCalledWith(test.expect[0], test.expect[1],test.expect[2],test.expect[3]); ctx.ds.zabbix.getHosts.mockClear(); } done(); @@ -315,7 +315,7 @@ describe('ZabbixDatasource', () => { let query = '*.*'; ctx.ds.metricFindQuery(query); - expect(ctx.ds.zabbix.getHosts).toBeCalledWith('/.*/', '/.*/'); + expect(ctx.ds.zabbix.getHosts).toBeCalledWith('/.*/','/.*/',null,null); done(); }); }); diff --git a/src/datasource/types.ts b/src/datasource/types.ts index c1f50e9b9..df55e0e13 100644 --- a/src/datasource/types.ts +++ b/src/datasource/types.ts @@ -72,6 +72,9 @@ export interface VariableQuery { itemTag?: string; item?: string; macro?: string; + userMacro?: String; + userMacroName?: string; + userMacroValue?: string; } export type LegacyVariableQuery = VariableQuery | string; @@ -84,6 +87,10 @@ export enum VariableQueryTypes { ItemTag = 'itemTag', Item = 'item', ItemValues = 'itemValues', + UserMacro = 'userMacro', + UserMacroName = 'userMacroName', + UserMacroValue = 'userMacroValue', + } export interface ProblemDTO { diff --git a/src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts b/src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts index 845e08a44..49d6cd476 100644 --- a/src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts +++ b/src/datasource/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts @@ -148,6 +148,15 @@ export class ZabbixAPIConnector { return this.request('hostgroup.get', params); } + getUserMacrosByGroup(groupids) { + const params = { + output: ['hostmacroid', 'macro', 'value', 'hostid'], + groupids: groupids, + }; + + return this.request('usermacro.get', params); + } + getHosts(groupids): Promise { const params: any = { output: ['hostid', 'name', 'host'], @@ -245,6 +254,15 @@ export class ZabbixAPIConnector { return this.request('usermacro.get', params); } + getMacrosByGroup(groupids) { + const params = { + output: 'extend', + groupids: groupids, + }; + + return this.request('usermacro.get', params); + } + getUserMacros(hostmacroids) { const params = { output: 'extend', diff --git a/src/datasource/zabbix/types.ts b/src/datasource/zabbix/types.ts index 32c216603..926081db0 100644 --- a/src/datasource/zabbix/types.ts +++ b/src/datasource/zabbix/types.ts @@ -18,7 +18,7 @@ export interface ZabbixConnector { getVersion: () => Promise; getGroups: (groupFilter?) => any; - getHosts: (groupFilter?, hostFilter?) => any; + getHosts: (groupFilter?, hostFilter?, userMacroName?, userMacroValue?) => any; getApps: (groupFilter?, hostFilter?, appFilter?) => any; getUMacros: (groupFilter?, hostFilter?, macroFilter?) => any; getItems: (groupFilter?, hostFilter?, appFilter?, itemTagFilter?, itemFilter?, options?) => any; diff --git a/src/datasource/zabbix/zabbix.ts b/src/datasource/zabbix/zabbix.ts index 805256bbf..fb6cba67b 100644 --- a/src/datasource/zabbix/zabbix.ts +++ b/src/datasource/zabbix/zabbix.ts @@ -33,6 +33,9 @@ const REQUESTS_TO_PROXYFY = [ 'getAlerts', 'getHostAlerts', 'getUserMacros', + 'getUMacrosVariable', + 'getUserMacrosNames', + 'getUserMacrosValues', 'getHostICAlerts', 'getHostPCAlerts', 'getAcknowledges', @@ -58,6 +61,7 @@ const REQUESTS_TO_CACHE = [ 'getItems', 'getMacros', 'getUMacros', + 'getUserMacros', 'getItemsByIDs', 'getITService', 'getProxies', @@ -206,7 +210,7 @@ export class Zabbix implements ZabbixConnector { * } * } * ``` - */ + n*/ testDataSource() { let zabbixVersion; let dbConnectorStatus; @@ -314,9 +318,7 @@ export class Zabbix implements ZabbixConnector { return this.getAllGroups().then((groups) => findByFilter(groups, groupFilter)); } - /** - * Get list of host belonging to given groups. - */ + getAllHosts(groupFilter): Promise { return this.getGroups(groupFilter).then((groups) => { const groupids = _.map(groups, 'groupid'); @@ -324,10 +326,76 @@ export class Zabbix implements ZabbixConnector { }); } - getHosts(groupFilter?, hostFilter?): Promise { - return this.getAllHosts(groupFilter).then((hosts) => findByFilter(hosts, hostFilter)); + async getAllMacros(groupFilter, hostFilter?) { + const hosts = await this.getHosts(groupFilter, hostFilter); + const hostids = hosts?.map((h) => h.hostid); + return this.zabbixAPI.getMacros(hostids); + } + + getAllMacrosByGroup(groupFilter): Promise { + return this.getGroups(groupFilter).then((groups) => { + const groupids = _.map(groups, 'groupid'); + return this.zabbixAPI.getMacrosByGroup(groupids); + }); + } + + + + async getUMacrosVariable(groupFilter, macroFilter? + ) { + const allMacros = await this.getAllMacrosByGroup(groupFilter); + const filteredMacros = filterByMQuery(allMacros, macroFilter); + return filteredMacros; + } + + getUserMacrosNames(groupFilter, userMacroName?) { + return this.getUMacrosVariable(groupFilter, userMacroName).then((macros) => { + const names = _.map(macros, 'macro'); + return _.uniq(names).map((name) => ({ name })); + }); + + } + + getUserMacrosValues(groupFilter, userMacroName?, userMacroValue?) { + return this.getUMacrosVariable(groupFilter, { name: userMacroName, value: userMacroValue }).then((macros) => { + const values = _.map(macros, 'value'); + return _.uniq(values).map((value) => ({ name: value })); + }); + } + + + async getUMacros(groupFilter?, hostFilter?, macroFilter?) { + const allMacros = await this.getAllMacros(groupFilter, hostFilter); + return filterByMQuery(allMacros, macroFilter); + } + + // getHosts now supports filtering on user macros (name,value) + + async getHosts(groupFilter?: string, hostFilter?: string, userMacroName?: string, userMacroValue?: string): Promise { + // Ensure userMacroName and userMacroValue are not empty or undefined + const macroFilter = (userMacroName && userMacroName.trim() !== '') || (userMacroValue && userMacroValue.trim() !== '') + ? { name: userMacroName || '', value: userMacroValue || '' } + : null; + + + const hosts = await this.getAllHosts(groupFilter); + + if (macroFilter) { + const macros = await this.getUMacrosVariable(groupFilter, macroFilter); + const filteredHosts = hosts.filter((host) => { + const hostMacros = macros.filter((macro) => macro.hostid === host.hostid); + return filterByMQuery(hostMacros, macroFilter).length > 0; + }); + return findByFilter(filteredHosts, hostFilter); + } + + return findByFilter(hosts, hostFilter); + } + + + /** * Get list of applications belonging to given groups and hosts. */ @@ -360,16 +428,7 @@ export class Zabbix implements ZabbixConnector { }); } - async getAllMacros(groupFilter, hostFilter) { - const hosts = await this.getHosts(groupFilter, hostFilter); - const hostids = hosts?.map((h) => h.hostid); - return this.zabbixAPI.getMacros(hostids); - } - async getUMacros(groupFilter?, hostFilter?, macroFilter?) { - const allMacros = await this.getAllMacros(groupFilter, hostFilter); - return filterByMQuery(allMacros, macroFilter); - } async getItemTags(groupFilter?, hostFilter?, itemTagFilter?) { const items = await this.getAllItems(groupFilter, hostFilter, null, null, {}); @@ -732,12 +791,22 @@ function filterByName(list, name) { } } -function filterByMacro(list, name) { - const finded = _.filter(list, { macro: name }); - if (finded) { - return finded; + + +function filterByMacro(list, filter) { + if (typeof filter === 'string') { + // Filter is a macro name (string) + return _.filter(list, (zbx_obj) => zbx_obj.macro === filter); + } else if (typeof filter === 'object' && filter !== null) { + // Filter is an object { name, value } + return _.filter(list, (zbx_obj) => { + const nameMatch = filter.name !== undefined && filter.name !== '' ? zbx_obj.macro === filter.name : true; + const valueMatch = filter.value !== undefined && filter.value !== '' ? zbx_obj.value === filter.value : true; + return nameMatch && valueMatch; + }); } else { - return []; + // Invalid or empty filter, return the entire list + return list; } } @@ -748,12 +817,6 @@ function filterByRegex(list, regex) { }); } -function filterByMRegex(list, regex) { - const filterPattern = utils.buildRegex(regex); - return _.filter(list, (zbx_obj) => { - return filterPattern.test(zbx_obj?.macro); - }); -} function findByFilter(list, filter) { if (utils.isRegex(filter)) { @@ -771,11 +834,53 @@ function filterByQuery(list, filter) { } } + +function filterByMRegex(list, filter) { + if (typeof filter === 'string') { + // Filter is a macro name regex + const namePattern = utils.buildRegex(filter); + return _.filter(list, (zbx_obj) => namePattern.test(zbx_obj.macro)); + } else if (typeof filter === 'object' && filter !== null) { + // Filter is an object { name, value } + return _.filter(list, (zbx_obj) => { + const nameMatch = filter.name !== undefined && filter.name !== '' + ? utils.isRegex(filter.name) + ? utils.buildRegex(filter.name).test(zbx_obj.macro) + : zbx_obj.macro === filter.name + : true; + + const valueMatch = filter.value !== undefined && filter.value !== '' + ? utils.isRegex(filter.value) + ? utils.buildRegex(filter.value).test(zbx_obj.value) + : zbx_obj.value === filter.value + : true; + + return nameMatch && valueMatch; + }); + } else { + // Invalid or empty filter, return the entire list + return list; + } +} + function filterByMQuery(list, filter) { - if (utils.isRegex(filter)) { - return filterByMRegex(list, filter); + if (typeof filter === 'string') { + // Filter is a string (macro name or regex) + return utils.isRegex(filter) ? filterByMRegex(list, filter) : filterByMacro(list, filter); + } else if (typeof filter === 'object' && filter !== null) { + // Filter is an object { name, value } + const isNameRegex = filter.name && utils.isRegex(filter.name); + const isValueRegex = filter.value && utils.isRegex(filter.value); + + if ((filter.name && filter.name !== '') || (filter.value && filter.value !== '')) { + return isNameRegex || isValueRegex ? filterByMRegex(list, filter) : filterByMacro(list, filter); + } else { + // Both name and value are empty strings, return the entire list + return list; + } } else { - return filterByMacro(list, filter); + // Invalid or empty filter, return the entire list + return list; } }