Support item tags, fixes #1258

This commit is contained in:
Alexander Zobnin
2021-08-09 14:57:49 +03:00
parent c6441cccf0
commit 915973829d
15 changed files with 574 additions and 364 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -680,7 +680,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
resultPromise = this.zabbix.getApps(queryModel.group, queryModel.host, queryModel.application);
break;
case VariableQueryTypes.Item:
resultPromise = this.zabbix.getItems(queryModel.group, queryModel.host, queryModel.application, queryModel.item);
resultPromise = this.zabbix.getItems(queryModel.group, queryModel.host, queryModel.application, null, queryModel.item);
break;
case VariableQueryTypes.ItemValues:
const range = options?.range;

View File

@@ -11,7 +11,7 @@ export function isGrafana2target(target) {
if (!target.mode || target.mode === 0 || target.mode === 2) {
if ((target.hostFilter || target.itemFilter || target.downsampleFunction ||
(target.host && target.host.host)) &&
(target.item.filter === undefined && target.host.filter === undefined)) {
(target.item.filter === undefined && target.host.filter === undefined)) {
return true;
} else {
return false;
@@ -68,6 +68,15 @@ function migrateProblemSort(target) {
}
}
function migrateApplications(target) {
if (!target.itemTag) {
target.itemTag = { filter: '' };
if (target.application?.filter) {
target.itemTag.filter = `Application: ${target.application?.filter}`;
}
}
}
export function migrate(target) {
target.resultFormat = target.resultFormat || 'time_series';
target = fixTargetGroup(target);
@@ -78,6 +87,7 @@ export function migrate(target) {
migrateQueryType(target);
migrateSLA(target);
migrateProblemSort(target);
migrateApplications(target);
return target;
}
@@ -97,6 +107,7 @@ function convertToRegex(str) {
}
export const DS_CONFIG_SCHEMA = 2;
export function migrateDSConfig(jsonData) {
if (!jsonData) {
jsonData = {};

View File

@@ -1,371 +1,396 @@
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form max-width-20">
<label class="gf-form-label width-7">Query Mode</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input"
ng-change="ctrl.switchEditorMode(ctrl.target.queryType)"
ng-model="ctrl.target.queryType"
ng-options="m.queryType as m.text for m in ctrl.editorModes">
</select>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-20">
<label class="gf-form-label width-7">Query Mode</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input"
ng-change="ctrl.switchEditorMode(ctrl.target.queryType)"
ng-model="ctrl.target.queryType"
ng-options="m.queryType as m.text for m in ctrl.editorModes">
</select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.target.queryType == editorMode.TEXT">
<label class="gf-form-label query-keyword width-7">Format As</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat"
ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form" ng-show="ctrl.target.queryType == editorMode.TEXT">
<label class="gf-form-label query-keyword width-7">Format As</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- IT Service editor -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.ITSERVICE">
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">IT Service</label>
<input type="text"
ng-model="ctrl.target.itServiceFilter"
bs-typeahead="ctrl.getITServices"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
<!-- IT Service editor -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.ITSERVICE">
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">IT Service</label>
<input type="text"
ng-model="ctrl.target.itServiceFilter"
bs-typeahead="ctrl.getITServices"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.itServiceFilter),
'zbx-regex': ctrl.isRegex(ctrl.target.itServiceFilter)
}">
</input>
</input>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Property</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-change="ctrl.onTargetBlur()"
ng-model="ctrl.target.slaProperty"
ng-options="slaProperty.name for slaProperty in ctrl.slaPropertyList track by slaProperty.name">
</select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Interval</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-change="ctrl.onTargetBlur()"
ng-model="ctrl.target.slaInterval"
ng-options="i.value as i.text for i in ctrl.slaIntervals">
</select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Property</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-change="ctrl.onTargetBlur()"
ng-model="ctrl.target.slaProperty"
ng-options="slaProperty.name for slaProperty in ctrl.slaPropertyList track by slaProperty.name">
</select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Interval</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input"
ng-change="ctrl.onTargetBlur()"
ng-model="ctrl.target.slaInterval"
ng-options="i.value as i.text for i in ctrl.slaIntervals">
</select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<!-- Select Group -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Group</label>
<input type="text"
ng-model="ctrl.target.group.filter"
bs-typeahead="ctrl.getGroupNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
<div class="gf-form-inline"
ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<!-- Select Group -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Group</label>
<input type="text"
ng-model="ctrl.target.group.filter"
bs-typeahead="ctrl.getGroupNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.group.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.group.filter)
}"></input>
</div>
<!-- Select Host -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Host</label>
<input type="text"
ng-model="ctrl.target.host.filter"
bs-typeahead="ctrl.getHostNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
</div>
<!-- Select Host -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Host</label>
<input type="text"
ng-model="ctrl.target.host.filter"
bs-typeahead="ctrl.getHostNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.host.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.host.filter)
}">
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Proxy</label>
<input type="text"
ng-model="ctrl.target.proxy.filter"
bs-typeahead="ctrl.getProxyNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input width-14"
ng-class="{
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Proxy</label>
<input type="text"
ng-model="ctrl.target.proxy.filter"
bs-typeahead="ctrl.getProxyNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input width-14"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.proxy.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.proxy.filter)
}">
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<!-- Select Application -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Application</label>
<input type="text"
ng-model="ctrl.target.application.filter"
ng-disabled="ctrl.appFilterDisabled()"
bs-typeahead="ctrl.getApplicationNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
<div class="gf-form-inline"
ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT || ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<!-- Select Application -->
<div class="gf-form max-width-20" ng-show="!ctrl.appFilterDisabled()">
<label class="gf-form-label query-keyword width-7">Application</label>
<input type="text"
ng-model="ctrl.target.application.filter"
bs-typeahead="ctrl.getApplicationNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.application.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.application.filter)
}">
</div>
</div>
<!-- Select Item -->
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT">
<label class="gf-form-label query-keyword width-7">Item</label>
<input type="text"
ng-model="ctrl.target.item.filter"
bs-typeahead="ctrl.getItemNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
<!-- Select item tags -->
<div class="gf-form max-width-20" ng-show="ctrl.appFilterDisabled()">
<label class="gf-form-label query-keyword width-7">Item tag</label>
<input type="text"
ng-model="ctrl.target.itemTag.filter"
bs-typeahead="ctrl.getItemTags"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.itemTag.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.itemTag.filter)
}">
</div>
<!-- Select Item -->
<div class="gf-form max-width-20"
ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.TEXT">
<label class="gf-form-label query-keyword width-7">Item</label>
<input type="text"
ng-model="ctrl.target.item.filter"
bs-typeahead="ctrl.getItemNames"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.item.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.item.filter)
}">
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Problem</label>
<input type="text"
ng-model="ctrl.target.trigger.filter"
ng-blur="ctrl.onTargetBlur()"
placeholder="Problem name"
class="gf-form-input"
ng-style="ctrl.target.trigger.style"
ng-class="{
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Problem</label>
<input type="text"
ng-model="ctrl.target.trigger.filter"
ng-blur="ctrl.onTargetBlur()"
placeholder="Problem name"
class="gf-form-input"
ng-style="ctrl.target.trigger.style"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.trigger.filter),
'zbx-regex': ctrl.isRegex(ctrl.target.trigger.filter)
}">
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Tags</label>
<input type="text" class="gf-form-input width-14"
ng-model="ctrl.target.tags.filter"
ng-blur="ctrl.onTargetBlur()"
placeholder="tag1:value1, tag2:value2">
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Tags</label>
<input type="text" class="gf-form-input width-14"
ng-model="ctrl.target.tags.filter"
ng-blur="ctrl.onTargetBlur()"
placeholder="tag1:value1, tag2:value2">
<div class="gf-form-inline"
ng-show="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Show</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input"
ng-model="ctrl.target.showProblems"
ng-options="v.value as v.text for v in ctrl.showProblemsOptions"
ng-change="ctrl.onTargetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Min severity</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input"
ng-model="ctrl.target.options.minSeverity"
ng-options="v.val as v.text for v in ctrl.severityOptions"
ng-change="ctrl.onTargetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.TRIGGERS">
<label class="gf-form-label query-keyword width-7">Min Severity</label>
<div class="gf-form-select-wrapper width-14">
<select class="gf-form-input"
ng-change="ctrl.onTargetBlur()"
ng-model="ctrl.target.triggers.minSeverity"
ng-options="s.val as s.text for s in ctrl.severityOptions">
</select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- Text mode options -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.TEXT">
<!-- Text metric regex -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Text filter</label>
<input type="text"
class="gf-form-input"
ng-model="ctrl.target.textFilter"
spellcheck='false'
placeholder="Text filter (regex)"
ng-blur="ctrl.onTargetBlur()">
</div>
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Show</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input"
ng-model="ctrl.target.showProblems"
ng-options="v.value as v.text for v in ctrl.showProblemsOptions"
ng-change="ctrl.onTargetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<label class="gf-form-label query-keyword width-7">Min severity</label>
<div class="gf-form-select-wrapper max-width-20">
<select class="gf-form-input"
ng-model="ctrl.target.options.minSeverity"
ng-options="v.val as v.text for v in ctrl.severityOptions"
ng-change="ctrl.onTargetBlur()">
</select>
</div>
</div>
<div class="gf-form max-width-20" ng-show="ctrl.target.queryType == editorMode.TRIGGERS">
<label class="gf-form-label query-keyword width-7">Min Severity</label>
<div class="gf-form-select-wrapper width-14">
<select class="gf-form-input"
ng-change="ctrl.onTargetBlur()"
ng-model="ctrl.target.triggers.minSeverity"
ng-options="s.val as s.text for s in ctrl.severityOptions">
</select>
</div>
<gf-form-switch class="gf-form" label="Use capture groups" checked="ctrl.target.useCaptureGroups"
on-change="ctrl.onTargetBlur()">
</gf-form-switch>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- Text mode options -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.TEXT">
<!-- Text metric regex -->
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Text filter</label>
<input type="text"
class="gf-form-input"
ng-model="ctrl.target.textFilter"
spellcheck='false'
placeholder="Text filter (regex)"
ng-blur="ctrl.onTargetBlur()">
</div>
<gf-form-switch class="gf-form" label="Use capture groups" checked="ctrl.target.useCaptureGroups" on-change="ctrl.onTargetBlur()">
</gf-form-switch>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- Item IDs editor mode -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.ITEMID">
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Item IDs</label>
<input type="text"
ng-model="ctrl.target.itemids"
bs-typeahead="ctrl.getVariables"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
<!-- Item IDs editor mode -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.ITEMID">
<div class="gf-form max-width-20">
<label class="gf-form-label query-keyword width-7">Item IDs</label>
<input type="text"
ng-model="ctrl.target.itemids"
bs-typeahead="ctrl.getVariables"
ng-blur="ctrl.onTargetBlur()"
data-min-length=0
data-items=100
class="gf-form-input"
ng-class="{
'zbx-variable': ctrl.isVariable(ctrl.target.itServiceFilter),
'zbx-regex': ctrl.isRegex(ctrl.target.itServiceFilter)
}">
</input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- Metric processing functions -->
<div class="gf-form-inline" ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.ITEMID || ctrl.target.queryType == editorMode.ITSERVICE">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Functions</label>
</div>
<div ng-repeat="func in ctrl.target.functions" class="gf-form">
<span zabbix-function-editor class="gf-form-label query-part" ng-hide="func.hidden"></span>
</div>
<div class="gf-form dropdown" add-metric-function>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form gf-form--grow">
<label class="gf-form-label gf-form-label--grow">
<a ng-click="ctrl.toggleQueryOptions()">
<i class="fa fa-caret-down" ng-show="ctrl.showQueryOptions"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showQueryOptions"></i>
{{ctrl.queryOptionsText}}
</a>
</label>
</div>
<!-- Query options -->
<div class="gf-form-group offset-width-7" ng-if="ctrl.showQueryOptions">
<div ng-hide="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<gf-form-switch class="gf-form" label-class="width-10"
label="Show disabled items"
checked="ctrl.target.options.showDisabledItems"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form" label-class="width-10"
label="Use Zabbix value mapping"
checked="ctrl.target.options.useZabbixValueMapping"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form" label-class="width-10"
label="Disable data alignment"
checked="ctrl.target.options.disableDataAlignment"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
</div>
<div class="gf-form-group offset-width-7" ng-show="ctrl.target.queryType === editorMode.TEXT && ctrl.target.resultFormat === 'table'">
<div class="gf-form">
<gf-form-switch class="gf-form" label-class="width-10"
label="Skip empty values"
checked="ctrl.target.options.skipEmptyValues"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
</div>
</div>
<div class="gf-form-group" ng-show="ctrl.target.queryType == editorMode.PROBLEMS || ctrl.target.queryType == editorMode.TRIGGERS">
<gf-form-switch class="gf-form" ng-show="ctrl.target.queryType == editorMode.TRIGGERS"
label-class="width-9"
label="Count"
checked="ctrl.target.triggers.count"
on-change="ctrl.onTargetBlur()">
</gf-form-switch>
<div class="gf-form" ng-show="ctrl.target.queryType == editorMode.PROBLEMS || ctrl.target.queryType == editorMode.TRIGGERS">
<label class="gf-form-label width-9">Acknowledged</label>
<div class="gf-form-select-wrapper width-12">
<select class="gf-form-input"
ng-change="ctrl.onQueryOptionChange()"
ng-model="ctrl.target.options.acknowledged"
ng-options="a.value as a.text for a in ctrl.ackFilters">
</select>
</input>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<!-- Metric processing functions -->
<div class="gf-form-inline"
ng-show="ctrl.target.queryType == editorMode.METRICS || ctrl.target.queryType == editorMode.ITEMID || ctrl.target.queryType == editorMode.ITSERVICE">
<div class="gf-form">
<label class="gf-form-label width-9">Sort by</label>
<div class="gf-form-select-wrapper width-12">
<select class="gf-form-input"
ng-model="ctrl.target.options.sortProblems"
ng-options="f.value as f.text for f in ctrl.sortByFields"
ng-change="ctrl.onQueryOptionChange()">
</select>
</div>
<label class="gf-form-label query-keyword width-7">Functions</label>
</div>
<gf-form-switch class="gf-form"
label-class="width-9"
label="Use time range"
checked="ctrl.target.options.useTimeRange"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label-class="width-9"
label="Hosts in maintenance"
checked="ctrl.target.options.hostsInMaintenance"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label-class="width-9"
label="Host proxy"
checked="ctrl.target.options.hostProxy"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<div class="gf-form">
<label class="gf-form-label width-9">Limit triggers</label>
<input class="gf-form-input width-5"
type="number" placeholder="100"
ng-model="ctrl.target.options.limit"
ng-model-onblur ng-change="ctrl.onQueryOptionChange()">
<div ng-repeat="func in ctrl.target.functions" class="gf-form">
<span zabbix-function-editor class="gf-form-label query-part" ng-hide="func.hidden"></span>
</div>
<div class="gf-form dropdown" add-metric-function>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form gf-form--grow">
<label class="gf-form-label gf-form-label--grow">
<a ng-click="ctrl.toggleQueryOptions()">
<i class="fa fa-caret-down" ng-show="ctrl.showQueryOptions"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showQueryOptions"></i>
{{ctrl.queryOptionsText}}
</a>
</label>
</div>
<!-- Query options -->
<div class="gf-form-group offset-width-7" ng-if="ctrl.showQueryOptions">
<div ng-hide="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
<gf-form-switch class="gf-form" label-class="width-10"
label="Show disabled items"
checked="ctrl.target.options.showDisabledItems"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form" label-class="width-10"
label="Use Zabbix value mapping"
checked="ctrl.target.options.useZabbixValueMapping"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form" label-class="width-10"
label="Disable data alignment"
checked="ctrl.target.options.disableDataAlignment"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
</div>
<div class="gf-form-group offset-width-7"
ng-show="ctrl.target.queryType === editorMode.TEXT && ctrl.target.resultFormat === 'table'">
<div class="gf-form">
<gf-form-switch class="gf-form" label-class="width-10"
label="Skip empty values"
checked="ctrl.target.options.skipEmptyValues"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
</div>
</div>
<div class="gf-form-group"
ng-show="ctrl.target.queryType == editorMode.PROBLEMS || ctrl.target.queryType == editorMode.TRIGGERS">
<gf-form-switch class="gf-form" ng-show="ctrl.target.queryType == editorMode.TRIGGERS"
label-class="width-9"
label="Count"
checked="ctrl.target.triggers.count"
on-change="ctrl.onTargetBlur()">
</gf-form-switch>
<div class="gf-form"
ng-show="ctrl.target.queryType == editorMode.PROBLEMS || ctrl.target.queryType == editorMode.TRIGGERS">
<label class="gf-form-label width-9">Acknowledged</label>
<div class="gf-form-select-wrapper width-12">
<select class="gf-form-input"
ng-change="ctrl.onQueryOptionChange()"
ng-model="ctrl.target.options.acknowledged"
ng-options="a.value as a.text for a in ctrl.ackFilters">
</select>
</div>
</div>
<div ng-show="ctrl.target.queryType == editorMode.PROBLEMS">
<div class="gf-form">
<label class="gf-form-label width-9">Sort by</label>
<div class="gf-form-select-wrapper width-12">
<select class="gf-form-input"
ng-model="ctrl.target.options.sortProblems"
ng-options="f.value as f.text for f in ctrl.sortByFields"
ng-change="ctrl.onQueryOptionChange()">
</select>
</div>
</div>
<gf-form-switch class="gf-form"
label-class="width-9"
label="Use time range"
checked="ctrl.target.options.useTimeRange"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label-class="width-9"
label="Hosts in maintenance"
checked="ctrl.target.options.hostsInMaintenance"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label-class="width-9"
label="Host proxy"
checked="ctrl.target.options.hostProxy"
on-change="ctrl.onQueryOptionChange()">
</gf-form-switch>
<div class="gf-form">
<label class="gf-form-label width-9">Limit triggers</label>
<input class="gf-form-input width-5"
type="number" placeholder="100"
ng-model="ctrl.target.options.limit"
ng-model-onblur ng-change="ctrl.onQueryOptionChange()">
</div>
</div>
</div>
</div>
</div>
</div>
</query-editor-row>

View File

@@ -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 => {

View File

@@ -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 {

View File

@@ -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 || [];
}

View File

@@ -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<any[]> {
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)
});
}

View File

@@ -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;

View File

@@ -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<any> {
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<any>;
getMacros: (hostids: any[]) => Promise<any>;
getValueMappings: () => Promise<any>;
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()];