From 915973829d913201f57c0fae9266e8999e4d032c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 9 Aug 2021 14:57:49 +0300 Subject: [PATCH] Support item tags, fixes #1258 --- pkg/datasource/models.go | 1 + pkg/datasource/zabbix.go | 3 +- pkg/zabbix/methods.go | 69 +- pkg/zabbix/models.go | 6 + pkg/zabbix/utils.go | 8 + pkg/zabbix/zabbix.go | 19 +- src/datasource-zabbix/datasource.ts | 2 +- src/datasource-zabbix/migrations.ts | 13 +- .../partials/query.editor.html | 669 +++++++++--------- src/datasource-zabbix/query.controller.ts | 38 +- src/datasource-zabbix/types.ts | 11 +- src/datasource-zabbix/utils.ts | 17 +- .../zabbix_api/zabbixAPIConnector.ts | 28 +- src/datasource-zabbix/zabbix/types.ts | 2 +- src/datasource-zabbix/zabbix/zabbix.ts | 52 +- 15 files changed, 574 insertions(+), 364 deletions(-) diff --git a/pkg/datasource/models.go b/pkg/datasource/models.go index 36ac57a..01af4c0 100644 --- a/pkg/datasource/models.go +++ b/pkg/datasource/models.go @@ -75,6 +75,7 @@ type QueryModel struct { Group QueryFilter `json:"group"` Host QueryFilter `json:"host"` Application QueryFilter `json:"application"` + ItemTag QueryFilter `json:"itemTag"` Item QueryFilter `json:"item"` // Item ID mode diff --git a/pkg/datasource/zabbix.go b/pkg/datasource/zabbix.go index b77b54c..ee55391 100644 --- a/pkg/datasource/zabbix.go +++ b/pkg/datasource/zabbix.go @@ -47,9 +47,10 @@ func (ds *ZabbixDatasourceInstance) queryNumericItems(ctx context.Context, query groupFilter := query.Group.Filter hostFilter := query.Host.Filter appFilter := query.Application.Filter + itemTagFilter := query.ItemTag.Filter itemFilter := query.Item.Filter - items, err := ds.zabbix.GetItems(ctx, groupFilter, hostFilter, appFilter, itemFilter, "num") + items, err := ds.zabbix.GetItems(ctx, groupFilter, hostFilter, appFilter, itemTagFilter, itemFilter, "num") if err != nil { return nil, err } diff --git a/pkg/zabbix/methods.go b/pkg/zabbix/methods.go index b002278..fe339b4 100644 --- a/pkg/zabbix/methods.go +++ b/pkg/zabbix/methods.go @@ -2,6 +2,8 @@ package zabbix import ( "context" + "strconv" + "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" ) @@ -78,7 +80,7 @@ func (ds *Zabbix) getTrend(ctx context.Context, itemids []string, timeRange back return trend, err } -func (ds *Zabbix) GetItems(ctx context.Context, groupFilter string, hostFilter string, appFilter string, itemFilter string, itemType string) ([]*Item, error) { +func (ds *Zabbix) GetItems(ctx context.Context, groupFilter string, hostFilter string, appFilter string, itemTagFilter string, itemFilter string, itemType string) ([]*Item, error) { hosts, err := ds.GetHosts(ctx, groupFilter, hostFilter) if err != nil { return nil, err @@ -90,7 +92,8 @@ func (ds *Zabbix) GetItems(ctx context.Context, groupFilter string, hostFilter s apps, err := ds.GetApps(ctx, groupFilter, hostFilter, appFilter) // Apps not supported in Zabbix 5.4 and higher - if isAppMethodNotFoundError(err) { + isZabbix54orHigher := isAppMethodNotFoundError(err) + if isZabbix54orHigher { apps = []Application{} } else if err != nil { return nil, err @@ -107,9 +110,50 @@ func (ds *Zabbix) GetItems(ctx context.Context, groupFilter string, hostFilter s allItems, err = ds.GetAllItems(ctx, nil, appids, itemType) } + if isZabbix54orHigher && itemTagFilter != "" { + allItems, err = filterItemsByTag(allItems, itemTagFilter) + if err != nil { + return nil, err + } + } + return filterItemsByQuery(allItems, itemFilter) } +func filterItemsByTag(items []*Item, filter string) ([]*Item, error) { + re, err := parseFilter(filter) + if err != nil { + return nil, err + } + + var filteredItems []*Item + for _, i := range items { + if len(i.Tags) == 0 && filter == "/.*/" { + filteredItems = append(filteredItems, i) + } + + if len(i.Tags) > 0 { + var tags []string + for _, t := range i.Tags { + tags = append(tags, itemTagToString(t)) + } + for _, t := range tags { + if re != nil { + if re.MatchString(t) { + filteredItems = append(filteredItems, i) + break + } + } else if t == filter { + filteredItems = append(filteredItems, i) + break + } + } + } + } + + return filteredItems, nil +} + func filterItemsByQuery(items []*Item, filter string) ([]*Item, error) { re, err := parseFilter(filter) if err != nil { @@ -259,6 +303,10 @@ func (ds *Zabbix) GetAllItems(ctx context.Context, hostids []string, appids []st filter["value_type"] = []int{1, 2, 4} } + if ds.version >= 54 { + params["selectTags"] = "extend" + } + result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "item.get", Params: params}) if err != nil { return nil, err @@ -347,6 +395,23 @@ func (ds *Zabbix) GetAllGroups(ctx context.Context) ([]Group, error) { return groups, err } +func (ds *Zabbix) GetVersion(ctx context.Context) (int, error) { + result, err := ds.request(ctx, "apiinfo.version", ZabbixAPIParams{}) + if err != nil { + return 0, err + } + + var version string + err = convertTo(result, &version) + if err != nil { + return 0, err + } + + version = strings.Replace(version[0:3], ".", "", 1) + versionNum, err := strconv.Atoi(version) + return versionNum, err +} + func isAppMethodNotFoundError(err error) bool { if err == nil { return false diff --git a/pkg/zabbix/models.go b/pkg/zabbix/models.go index c914816..deaf9dc 100644 --- a/pkg/zabbix/models.go +++ b/pkg/zabbix/models.go @@ -51,6 +51,7 @@ type Item struct { Delay string `json:"delay,omitempty"` Units string `json:"units,omitempty"` ValueMapID string `json:"valuemapid,omitempty"` + Tags []ItemTag `json:"tags,omitempty"` } type ItemHost struct { @@ -58,6 +59,11 @@ type ItemHost struct { Name string `json:"name,omitempty"` } +type ItemTag struct { + Tag string `json:"tag,omitempty"` + Value string `json:"value,omitempty"` +} + type Trend []TrendPoint type TrendPoint struct { diff --git a/pkg/zabbix/utils.go b/pkg/zabbix/utils.go index db6c975..562331b 100644 --- a/pkg/zabbix/utils.go +++ b/pkg/zabbix/utils.go @@ -85,3 +85,11 @@ func parseFilter(filter string) (*regexp.Regexp, error) { return regexp.Compile(pattern) } + +func itemTagToString(tag ItemTag) string { + if tag.Value != "" { + return fmt.Sprintf("%s: %s", tag.Tag, tag.Value) + } else { + return tag.Tag + } +} diff --git a/pkg/zabbix/zabbix.go b/pkg/zabbix/zabbix.go index 8621672..a61ceba 100644 --- a/pkg/zabbix/zabbix.go +++ b/pkg/zabbix/zabbix.go @@ -14,10 +14,11 @@ import ( // Zabbix is a wrapper for Zabbix API. It wraps Zabbix API queries and performs authentication, adds caching, // deduplication and other performance optimizations. type Zabbix struct { - api *zabbixapi.ZabbixAPI - dsInfo *backend.DataSourceInstanceSettings - cache *ZabbixCache - logger log.Logger + api *zabbixapi.ZabbixAPI + dsInfo *backend.DataSourceInstanceSettings + cache *ZabbixCache + version int + logger log.Logger } // New returns new instance of Zabbix client. @@ -49,6 +50,16 @@ func (ds *Zabbix) Request(ctx context.Context, apiReq *ZabbixAPIRequest) (*simpl var resultJson *simplejson.Json var err error + if ds.version == 0 { + version, err := ds.GetVersion(ctx) + if err != nil { + ds.logger.Error("Error querying Zabbix version", "error", err) + } else { + ds.logger.Debug("Got Zabbix version", "version", version) + ds.version = version + } + } + cachedResult, queryExistInCache := ds.cache.GetAPIRequest(apiReq) if !queryExistInCache { resultJson, err = ds.request(ctx, apiReq.Method, apiReq.Params) diff --git a/src/datasource-zabbix/datasource.ts b/src/datasource-zabbix/datasource.ts index dd3cadd..df9d655 100644 --- a/src/datasource-zabbix/datasource.ts +++ b/src/datasource-zabbix/datasource.ts @@ -680,7 +680,7 @@ export class ZabbixDatasource extends DataSourceApi -
-
- -
- -
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
-
- -
- -
-
-
-
-
-
- -
-
- - +
+ + - + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
- -
- - + +
+ + -
- -
- - + + -
+
-
- - + + +
+ +
+
+
-
-
-
-
- -
- -
- - + +
+ + -
+
- -
- - + + +
+ + +
+ + -
+
-
- - + + +
+ +
+ + +
+ +
+
+
-
- - +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+
-
-
-
-
+ +
+ +
+ + +
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
+ + +
+
+
-
-
-
-
- - -
- -
- - -
- - - -
-
-
-
- - -
-
- - +
+ + - -
-
-
-
-
- - -
-
- -
-
- -
- -
-
-
-
- - - - -
-
- - - - - - -
- -
-
- - -
-
- -
- - -
- -
- +
-
+
+
+
+
-
+ +
- -
- -
+
- - - - - - -
- - +
+ +
+ +
+
+
+
+ + + + +
+
+ + + + + + +
+ +
+
+ + +
+
+ +
+ + +
+ +
+ +
+
+ +
+
+ +
+ +
+
+ + + + + + +
+ + +
+
-
-
diff --git a/src/datasource-zabbix/query.controller.ts b/src/datasource-zabbix/query.controller.ts index 4d84a99..bd02610 100644 --- a/src/datasource-zabbix/query.controller.ts +++ b/src/datasource-zabbix/query.controller.ts @@ -2,9 +2,10 @@ import { QueryCtrl } from 'grafana/app/plugins/sdk'; import _ from 'lodash'; import * as c from './constants'; import * as utils from './utils'; +import { itemTagToString } from './utils'; import * as metricFunctions from './metricFunctions'; import * as migrations from './migrations'; -import { ShowProblemTypes } from './types'; +import { ShowProblemTypes, ZBXItem, ZBXItemTag } from './types'; import { CURRENT_SCHEMA_VERSION } from '../panel-triggers/migrations'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; @@ -15,6 +16,7 @@ function getTargetDefaults() { group: { 'filter': "" }, host: { 'filter': "" }, application: { 'filter': "" }, + itemTag: { 'filter': "" }, item: { 'filter': "" }, functions: [], triggers: { @@ -274,7 +276,11 @@ export class ZabbixQueryController extends QueryCtrl { promises.push(this.suggestProxies()); } - return Promise.all(promises); + return Promise.all(promises).then(() => { + if (this.zabbix.isZabbix54OrHigher()) { + this.suggestItemTags(); + } + }); } initDefaultQueryMode(target) { @@ -304,12 +310,23 @@ export class ZabbixQueryController extends QueryCtrl { return metrics; } + getItemTags = () => { + if (!this.metric?.tagList) { + return []; + } + return this.metric.tagList.map(t => itemTagToString(t)); + }; + getTemplateVariables() { return _.map(this.templateSrv.getVariables(), variable => { return '$' + variable.name; }); } + isZabbix54OrHigher() { + return this.zabbix.isZabbix54OrHigher(); + } + suggestGroups() { return this.zabbix.getAllGroups() .then(groups => { @@ -341,19 +358,34 @@ export class ZabbixQueryController extends QueryCtrl { const groupFilter = this.replaceTemplateVars(this.target.group.filter); const hostFilter = this.replaceTemplateVars(this.target.host.filter); const appFilter = this.replaceTemplateVars(this.target.application.filter); + const itemTagFilter = this.replaceTemplateVars(this.target.itemTag.filter); const options = { itemtype: itemtype, showDisabledItems: this.target.options.showDisabledItems }; return this.zabbix - .getAllItems(groupFilter, hostFilter, appFilter, options) + .getAllItems(groupFilter, hostFilter, appFilter, itemTagFilter, options) .then(items => { this.metric.itemList = items; return items; }); } + async suggestItemTags() { + const groupFilter = this.replaceTemplateVars(this.target.group.filter); + const hostFilter = this.replaceTemplateVars(this.target.host.filter); + const items = await this.zabbix.getAllItems(groupFilter, hostFilter, null, null, {}); + const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => { + if (item.tags) { + return item.tags; + } else { + return []; + } + })); + this.metric.tagList = _.uniqBy(tags, t => t.tag + t.value || ''); + } + suggestITServices() { return this.zabbix.getITService() .then(itservices => { diff --git a/src/datasource-zabbix/types.ts b/src/datasource-zabbix/types.ts index 299aa9d..651b48a 100644 --- a/src/datasource-zabbix/types.ts +++ b/src/datasource-zabbix/types.ts @@ -1,4 +1,4 @@ -import { SelectableValue, DataQuery, DataSourceJsonData } from "@grafana/data"; +import { DataQuery, DataSourceJsonData, SelectableValue } from "@grafana/data"; export interface ZabbixDSOptions extends DataSourceJsonData { username: string; @@ -40,6 +40,7 @@ export interface ZabbixMetricsQuery extends DataQuery { group: { filter: string; name?: string; }; host: { filter: string; name?: string; }; application: { filter: string; name?: string; }; + itemTag: { filter: string; name?: string; }; item: { filter: string; name?: string; }; textFilter: string; mode: number; @@ -87,7 +88,9 @@ export interface TemplateSrv { variables: { name: string; }; + highlightVariablesAsHtml(str: any): any; + replace(target: any, scopedVars?: any, format?: any): any; } @@ -293,6 +296,12 @@ export interface ZBXItem { name: string; key_: string; lastvalue?: string; + tags?: ZBXItemTag[]; +} + +export interface ZBXItemTag { + tag: string; + value?: string; } export interface ZBXEvent { diff --git a/src/datasource-zabbix/utils.ts b/src/datasource-zabbix/utils.ts index 1f34a22..9a18cce 100644 --- a/src/datasource-zabbix/utils.ts +++ b/src/datasource-zabbix/utils.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import moment from 'moment'; import * as c from './constants'; -import { VariableQuery, VariableQueryTypes } from './types'; +import { VariableQuery, VariableQueryTypes, ZBXItemTag } from './types'; import { DataFrame, FieldType, getValueFormats, MappingType, rangeUtil, ValueMapping } from '@grafana/data'; /* @@ -405,6 +405,21 @@ export function parseTags(tagStr: string): any[] { return tags; } +// Parses string representation of tag into the object +export function parseItemTag(tagStr: string): ZBXItemTag { + const itemTag: ZBXItemTag = { tag: '', value: '' }; + const tagParts = tagStr.split(': '); + itemTag.tag = tagParts[0]; + if (tagParts[1]) { + itemTag.value = tagParts[1]; + } + return itemTag; +} + +export function itemTagToString(t: ZBXItemTag): string { + return t.value ? `${t.tag}: ${t.value}` : t.tag; +} + export function mustArray(result: any): any[] { return result || []; } diff --git a/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts b/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts index 0843f6e..7e63b23 100644 --- a/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts +++ b/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts @@ -2,9 +2,9 @@ import _ from 'lodash'; import semver from 'semver'; import kbn from 'grafana/app/core/utils/kbn'; import * as utils from '../../../utils'; -import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants'; +import { MIN_SLA_INTERVAL, ZBX_ACK_ACTION_ADD_MESSAGE, ZBX_ACK_ACTION_NONE } from '../../../constants'; import { ShowProblemTypes, ZBXProblem } from '../../../types'; -import { JSONRPCError, ZBXScript, APIExecuteScriptResponse } from './types'; +import { APIExecuteScriptResponse, JSONRPCError, ZBXScript } from './types'; import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime'; import { rangeUtil } from '@grafana/data'; @@ -106,6 +106,10 @@ export class ZabbixAPIConnector { return this.getVersionPromise; } + isZabbix54OrHigher() { + return semver.gte(this.version, '5.4.0'); + } + //////////////////////////////// // Zabbix API method wrappers // //////////////////////////////// @@ -151,7 +155,7 @@ export class ZabbixAPIConnector { } async getApps(hostids): Promise { - if (semver.gte(this.version, '5.4.0')) { + if (this.isZabbix54OrHigher()) { return []; } @@ -203,12 +207,16 @@ export class ZabbixAPIConnector { params.filter.value_type = [1, 2, 4]; } + if (this.isZabbix54OrHigher()) { + params.selectTags = 'extend'; + } + return this.request('item.get', params) .then(utils.expandItems); } getItemsByIDs(itemids) { - const params = { + const params: any = { itemids: itemids, output: [ 'name', @@ -225,6 +233,10 @@ export class ZabbixAPIConnector { selectHosts: ['hostid', 'name'] }; + if (this.isZabbix54OrHigher()) { + params.selectTags = 'extend'; + } + return this.request('item.get', params) .then(items => utils.expandItems(items)); } @@ -438,7 +450,7 @@ export class ZabbixAPIConnector { } getTriggers(groupids, hostids, applicationids, options) { - const {showTriggers, maintenance, timeFrom, timeTo} = options; + const { showTriggers, maintenance, timeFrom, timeTo } = options; const params: any = { output: 'extend', @@ -605,7 +617,7 @@ export class ZabbixAPIConnector { } getHostAlerts(hostids, applicationids, options) { - const {minSeverity, acknowledged, count, timeFrom, timeTo} = options; + const { minSeverity, acknowledged, count, timeFrom, timeTo } = options; const params: any = { output: 'extend', hostids: hostids, @@ -709,8 +721,8 @@ function buildSLAIntervals(timeRange, interval) { for (let i = timeFrom; i <= timeTo - interval; i += interval) { intervals.push({ - from : i, - to : (i + interval) + from: i, + to: (i + interval) }); } diff --git a/src/datasource-zabbix/zabbix/types.ts b/src/datasource-zabbix/zabbix/types.ts index 7cd65d3..69230a7 100644 --- a/src/datasource-zabbix/zabbix/types.ts +++ b/src/datasource-zabbix/zabbix/types.ts @@ -17,7 +17,7 @@ export interface ZabbixConnector { getGroups: (groupFilter?) => any; getHosts: (groupFilter?, hostFilter?) => any; getApps: (groupFilter?, hostFilter?, appFilter?) => any; - getItems: (groupFilter?, hostFilter?, appFilter?, itemFilter?, options?) => any; + getItems: (groupFilter?, hostFilter?, appFilter?, itemTagFilter?, itemFilter?, options?) => any; getSLA: (itservices, timeRange, target, options?) => any; supportsApplications: () => boolean; diff --git a/src/datasource-zabbix/zabbix/zabbix.ts b/src/datasource-zabbix/zabbix/zabbix.ts index 4cf201c..0434b19 100644 --- a/src/datasource-zabbix/zabbix/zabbix.ts +++ b/src/datasource-zabbix/zabbix/zabbix.ts @@ -2,6 +2,7 @@ import _ from 'lodash'; import moment from 'moment'; import semver from 'semver'; import * as utils from '../utils'; +import { itemTagToString } from '../utils'; import responseHandler from '../responseHandler'; import { CachingProxy } from './proxy/cachingProxy'; import { DBConnector } from './connectors/dbConnector'; @@ -10,7 +11,7 @@ import { SQLConnector } from './connectors/sql/sqlConnector'; import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector'; import { ZabbixConnector } from './types'; import { joinTriggersWithEvents, joinTriggersWithProblems } from '../problemsHandler'; -import { ProblemDTO } from '../types'; +import { ProblemDTO, ZBXItemTag } from '../types'; interface AppsResponse extends Array { appFilterEmpty?: boolean; @@ -30,7 +31,7 @@ const REQUESTS_TO_CACHE = [ const REQUESTS_TO_BIND = [ 'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts', 'getAcknowledges', 'getITService', 'acknowledgeEvent', 'getProxies', 'getEventAlerts', - 'getExtendedEventData', 'getScripts', 'executeScript', 'getValueMappings' + 'getExtendedEventData', 'getScripts', 'executeScript', 'getValueMappings', 'isZabbix54OrHigher' ]; export class Zabbix implements ZabbixConnector { @@ -56,6 +57,7 @@ export class Zabbix implements ZabbixConnector { getExtendedEventData: (eventids) => Promise; getMacros: (hostids: any[]) => Promise; getValueMappings: () => Promise; + isZabbix54OrHigher: () => boolean; constructor(options) { const { @@ -180,7 +182,7 @@ export class Zabbix implements ZabbixConnector { } getItemsFromTarget(target, options) { - const parts = ['group', 'host', 'application', 'item']; + const parts = ['group', 'host', 'application', 'itemTag', 'item']; const filters = _.map(parts, p => target[p].filter); return this.getItems(...filters, options); } @@ -261,24 +263,36 @@ export class Zabbix implements ZabbixConnector { }); } - getAllItems(groupFilter, hostFilter, appFilter, options: any = {}) { - return this.getApps(groupFilter, hostFilter, appFilter) - .then(apps => { + async getAllItems(groupFilter, hostFilter, appFilter, itemTagFilter, options: any = {}) { + const apps = await this.getApps(groupFilter, hostFilter, appFilter); + let items: any[]; + + if (this.isZabbix54OrHigher()) { + items = await this.zabbixAPI.getItems(apps.hostids, undefined, options.itemtype); + if (itemTagFilter) { + items = items.filter(item => { + if (item.tags) { + const tags: ZBXItemTag[] = item.tags.map(t => itemTagToString(t)); + return tags.includes(itemTagFilter); + } else { + return false; + } + }); + } + } else { if (apps.appFilterEmpty) { - return this.zabbixAPI.getItems(apps.hostids, undefined, options.itemtype); + items = await 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' }); + items = await this.zabbixAPI.getItems(undefined, appids, options.itemtype); } + } - return items; - }) - .then(this.expandUserMacro.bind(this)); + if (!options.showDisabledItems) { + items = _.filter(items, { 'status': '0' }); + } + + return await this.expandUserMacro(items, false); } expandUserMacro(items, isTriggerItem) { @@ -298,13 +312,13 @@ export class Zabbix implements ZabbixConnector { }); } - getItems(groupFilter?, hostFilter?, appFilter?, itemFilter?, options = {}) { - return this.getAllItems(groupFilter, hostFilter, appFilter, options) + getItems(groupFilter?, hostFilter?, appFilter?, itemTagFilter?, itemFilter?, options = {}) { + return this.getAllItems(groupFilter, hostFilter, appFilter, itemTagFilter, options) .then(items => filterByQuery(items, itemFilter)); } getItemValues(groupFilter?, hostFilter?, appFilter?, itemFilter?, options: any = {}) { - return this.getItems(groupFilter, hostFilter, appFilter, itemFilter, options).then(items => { + return this.getItems(groupFilter, hostFilter, appFilter, null, 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()];