Use Data frames response format (#1099)
* Use data frames for numeric data * Use data frames for text data * Use data frames for IT services * fix multiple series * Convert to the wide format if possible * Fix table format for text data * Add refId to the data frames * Align time series from Zabbix API * Fill gaps with nulls * Fix moving average functions * Option for disabling data alignment * remove unused logging * Add labels to data frames * Detect units * Set min and max for if percent unit used * Use value mapping from Zabbix * Rename unitConverter -> convertZabbixUnit * More units * Add missing points in front of each series * Fix handling table data * fix db connector data frames handling * fix it services data frames handling * Detect all known grafana units * Chore: remove unused logging * Fix problems format * Debug logging: show original units * Add global option for disabling data alignment * Add tooltip for the disableDataAlignment feature * Add note about query options * Functions for aligning timeseries on the backend
This commit is contained in:
@@ -68,12 +68,9 @@ Read [how to configure](./direct_db_datasource) SQL data source in Grafana.
|
||||
|
||||
**MySQL**, **PostgreSQL**, **InfluxDB** are supported as sources of historical data for the plugin.
|
||||
|
||||
### Alerting
|
||||
### Other
|
||||
|
||||
- **Enable alerting**: enable limited alerting support.
|
||||
- **Add thresholds**: get thresholds info from zabbix triggers and add it to graphs.
|
||||
For example, if you have trigger `{Zabbix server:system.cpu.util[,iowait].avg(5m)}>20`, threshold will be set to 20.
|
||||
- **Min severity**: minimum trigger severity for showing alert info (OK/Problem).
|
||||
- **Disable data alignment**: disable time series data alignment. This feature aligns points based on item update interval. For instance, if value collected once per minute, then timestamp of the each point will be set to the start of corresponding minute. This alignment required for proper work of the stacked graphs. If you don't need stacked graphs and want to get exactly the same timestamps as in Zabbix, then you can disable this feature. Also, data alignment can be toggled for each query individually, in the query options.
|
||||
|
||||
Then click _Add_ - datasource will be added and you can check connection using
|
||||
_Test Connection_ button. This feature can help to find some mistakes like invalid user name
|
||||
|
||||
@@ -30,8 +30,6 @@ datasources:
|
||||
alerting: true
|
||||
addThresholds: false
|
||||
alertingMinSeverity: 3
|
||||
# Disable acknowledges for read-only users
|
||||
disableReadOnlyUsersAck: true
|
||||
# Direct DB Connection options
|
||||
dbConnectionEnable: true
|
||||
# Name of existing datasource for Direct DB Connection
|
||||
@@ -39,6 +37,10 @@ datasources:
|
||||
# Retention policy name (InfluxDB only) for fetching long-term stored data.
|
||||
# Leave it blank if only default retention policy used.
|
||||
dbConnectionRetentionPolicy: one_year
|
||||
# Disable acknowledges for read-only users
|
||||
disableReadOnlyUsersAck: true
|
||||
# Disable time series data alignment
|
||||
disableDataAlignment: false
|
||||
version: 1
|
||||
editable: false
|
||||
|
||||
|
||||
49
pkg/datasource/response_handler.go
Normal file
49
pkg/datasource/response_handler.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
)
|
||||
|
||||
func convertHistory(history History, items Items) *data.Frame {
|
||||
timeFileld := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
|
||||
timeFileld.Name = "time"
|
||||
frame := data.NewFrame("History", timeFileld)
|
||||
|
||||
for _, item := range items {
|
||||
field := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, 0)
|
||||
if len(item.Hosts) > 0 {
|
||||
field.Name = fmt.Sprintf("%s: %s", item.Hosts[0].Name, item.ExpandItem())
|
||||
} else {
|
||||
field.Name = item.ExpandItem()
|
||||
}
|
||||
frame.Fields = append(frame.Fields, field)
|
||||
}
|
||||
|
||||
for _, point := range history {
|
||||
for columnIndex, field := range frame.Fields {
|
||||
if columnIndex == 0 {
|
||||
ts := time.Unix(point.Clock, point.NS)
|
||||
field.Append(ts)
|
||||
} else {
|
||||
item := items[columnIndex-1]
|
||||
if point.ItemID == item.ID {
|
||||
value := point.Value
|
||||
field.Append(&value)
|
||||
} else {
|
||||
field.Append(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wideFrame, err := data.LongToWide(frame, &data.FillMissing{Mode: data.FillModeNull})
|
||||
if err != nil {
|
||||
backend.Logger.Debug("Error converting data frame to the wide format", "error", err)
|
||||
return frame
|
||||
}
|
||||
return wideFrame
|
||||
}
|
||||
@@ -374,7 +374,8 @@ func (ds *ZabbixDatasourceInstance) queryNumericDataForItems(ctx context.Context
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return convertHistory(history, items), nil
|
||||
frame := convertHistory(history, items)
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func (ds *ZabbixDatasourceInstance) getTrendValueType(query *QueryModel) string {
|
||||
@@ -472,46 +473,6 @@ func (ds *ZabbixDatasourceInstance) isUseTrend(timeRange backend.TimeRange) bool
|
||||
return false
|
||||
}
|
||||
|
||||
func convertHistory(history History, items Items) *data.Frame {
|
||||
timeFileld := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
|
||||
timeFileld.Name = "time"
|
||||
frame := data.NewFrame("History", timeFileld)
|
||||
|
||||
for _, item := range items {
|
||||
field := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, 0)
|
||||
if len(item.Hosts) > 0 {
|
||||
field.Name = fmt.Sprintf("%s: %s", item.Hosts[0].Name, item.ExpandItem())
|
||||
} else {
|
||||
field.Name = item.ExpandItem()
|
||||
}
|
||||
frame.Fields = append(frame.Fields, field)
|
||||
}
|
||||
|
||||
for _, point := range history {
|
||||
for columnIndex, field := range frame.Fields {
|
||||
if columnIndex == 0 {
|
||||
ts := time.Unix(point.Clock, point.NS)
|
||||
field.Append(ts)
|
||||
} else {
|
||||
item := items[columnIndex-1]
|
||||
if point.ItemID == item.ID {
|
||||
value := point.Value
|
||||
field.Append(&value)
|
||||
} else {
|
||||
field.Append(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: convert to wide format
|
||||
wideFrame, err := data.LongToWide(frame, &data.FillMissing{Mode: data.FillModeNull})
|
||||
if err == nil {
|
||||
return wideFrame
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
func parseFilter(filter string) (*regexp.Regexp, error) {
|
||||
regex := regexp.MustCompile(`^/(.+)/(.*)$`)
|
||||
flagRE := regexp.MustCompile("[imsU]+")
|
||||
|
||||
18
pkg/timeseries/models.go
Normal file
18
pkg/timeseries/models.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package timeseries
|
||||
|
||||
import "time"
|
||||
|
||||
type TimePoint struct {
|
||||
Time time.Time
|
||||
Value *float64
|
||||
}
|
||||
|
||||
type TimeSeries []TimePoint
|
||||
|
||||
func NewTimeSeries() TimeSeries {
|
||||
return make(TimeSeries, 0)
|
||||
}
|
||||
|
||||
func (ts *TimeSeries) Len() int {
|
||||
return len(*ts)
|
||||
}
|
||||
153
pkg/timeseries/timeseries.go
Normal file
153
pkg/timeseries/timeseries.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package timeseries
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
)
|
||||
|
||||
// Aligns point's time stamps according to provided interval.
|
||||
func (ts TimeSeries) Align(interval time.Duration) TimeSeries {
|
||||
if interval <= 0 || ts.Len() < 2 {
|
||||
return ts
|
||||
}
|
||||
|
||||
alignedTs := NewTimeSeries()
|
||||
var frameTs = ts[0].GetTimeFrame(interval)
|
||||
var pointFrameTs time.Time
|
||||
var point TimePoint
|
||||
|
||||
for i := 1; i < ts.Len(); i++ {
|
||||
point = ts[i]
|
||||
pointFrameTs = point.GetTimeFrame(interval)
|
||||
|
||||
if pointFrameTs.After(frameTs) {
|
||||
for frameTs.Before(pointFrameTs) {
|
||||
alignedTs = append(alignedTs, TimePoint{Time: frameTs, Value: nil})
|
||||
frameTs = frameTs.Add(interval)
|
||||
}
|
||||
}
|
||||
|
||||
alignedTs = append(alignedTs, TimePoint{Time: pointFrameTs, Value: point.Value})
|
||||
frameTs = frameTs.Add(interval)
|
||||
}
|
||||
|
||||
return alignedTs
|
||||
}
|
||||
|
||||
// Detects interval between data points in milliseconds based on median delta between points.
|
||||
func (ts TimeSeries) DetectInterval() time.Duration {
|
||||
if ts.Len() < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
deltas := make([]int, 0)
|
||||
for i := 1; i < ts.Len(); i++ {
|
||||
delta := ts[i].Time.Sub(ts[i-1].Time)
|
||||
deltas = append(deltas, int(delta.Milliseconds()))
|
||||
}
|
||||
sort.Ints(deltas)
|
||||
midIndex := int(math.Floor(float64(len(deltas)) * 0.5))
|
||||
return time.Duration(deltas[midIndex]) * time.Millisecond
|
||||
}
|
||||
|
||||
// Gets point timestamp rounded according to provided interval.
|
||||
func (p *TimePoint) GetTimeFrame(interval time.Duration) time.Time {
|
||||
return p.Time.Truncate(interval)
|
||||
}
|
||||
|
||||
func alignDataPoints(frame *data.Frame, interval time.Duration) *data.Frame {
|
||||
if interval <= 0 || frame.Rows() < 2 {
|
||||
return frame
|
||||
}
|
||||
|
||||
timeFieldIdx := getTimeFieldIndex(frame)
|
||||
if timeFieldIdx < 0 {
|
||||
return frame
|
||||
}
|
||||
var frameTs = getPointTimeFrame(getTimestampAt(frame, 0), interval)
|
||||
var pointFrameTs *time.Time
|
||||
var pointsInserted = 0
|
||||
|
||||
for i := 1; i < frame.Rows(); i++ {
|
||||
pointFrameTs = getPointTimeFrame(getTimestampAt(frame, i), interval)
|
||||
if pointFrameTs == nil || frameTs == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if pointFrameTs.After(*frameTs) {
|
||||
for frameTs.Before(*pointFrameTs) {
|
||||
insertAt := i + pointsInserted
|
||||
err := insertNullPointAt(frame, *frameTs, insertAt)
|
||||
if err != nil {
|
||||
backend.Logger.Debug("Error inserting null point", "error", err)
|
||||
}
|
||||
*frameTs = frameTs.Add(interval)
|
||||
pointsInserted++
|
||||
}
|
||||
}
|
||||
|
||||
setTimeAt(frame, *pointFrameTs, i+pointsInserted)
|
||||
*frameTs = frameTs.Add(interval)
|
||||
}
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
func getPointTimeFrame(ts *time.Time, interval time.Duration) *time.Time {
|
||||
if ts == nil {
|
||||
return nil
|
||||
}
|
||||
timeFrame := ts.Truncate(interval)
|
||||
return &timeFrame
|
||||
}
|
||||
|
||||
func getTimeFieldIndex(frame *data.Frame) int {
|
||||
for i := 0; i < len(frame.Fields); i++ {
|
||||
if frame.Fields[i].Type() == data.FieldTypeTime {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func getTimestampAt(frame *data.Frame, index int) *time.Time {
|
||||
timeFieldIdx := getTimeFieldIndex(frame)
|
||||
if timeFieldIdx < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tsValue := frame.Fields[timeFieldIdx].At(index)
|
||||
ts, ok := tsValue.(time.Time)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ts
|
||||
}
|
||||
|
||||
func insertNullPointAt(frame *data.Frame, frameTs time.Time, index int) error {
|
||||
for _, field := range frame.Fields {
|
||||
if field.Type() == data.FieldTypeTime {
|
||||
field.Insert(index, frameTs)
|
||||
} else if field.Type().Nullable() {
|
||||
field.Insert(index, nil)
|
||||
} else {
|
||||
return errors.New("field is not nullable")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTimeAt(frame *data.Frame, frameTs time.Time, index int) {
|
||||
for _, field := range frame.Fields {
|
||||
if field.Type() == data.FieldTypeTime {
|
||||
field.Insert(index, frameTs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export const ConfigEditor = (props: Props) => {
|
||||
trendsRange: '',
|
||||
cacheTTL: '',
|
||||
timeout: '',
|
||||
disableDataAlignment: false,
|
||||
...restJsonData,
|
||||
},
|
||||
});
|
||||
@@ -209,10 +210,20 @@ export const ConfigEditor = (props: Props) => {
|
||||
<h3 className="page-heading">Other</h3>
|
||||
<Switch
|
||||
label="Disable acknowledges for read-only users"
|
||||
labelClass="width-20"
|
||||
labelClass="width-16"
|
||||
checked={options.jsonData.disableReadOnlyUsersAck}
|
||||
onChange={jsonDataSwitchHandler('disableReadOnlyUsersAck', options, onOptionsChange)}
|
||||
/>
|
||||
<Switch
|
||||
label="Disable data alignment"
|
||||
labelClass="width-16"
|
||||
checked={!!options.jsonData.disableDataAlignment}
|
||||
onChange={jsonDataSwitchHandler('disableDataAlignment', options, onOptionsChange)}
|
||||
tooltip="Data alignment feature aligns points based on item update interval.
|
||||
For instance, if value collected once per minute, then timestamp of the each point will be set to the start of corresponding minute.
|
||||
This alignment required for proper work of the stacked graphs.
|
||||
If you don't need stacked graphs and want to get exactly the same timestamps as in Zabbix, then you can disable this feature."
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as utils from './utils';
|
||||
import * as migrations from './migrations';
|
||||
import * as metricFunctions from './metricFunctions';
|
||||
import * as c from './constants';
|
||||
import { align } from './timeseries';
|
||||
import dataProcessor from './dataProcessor';
|
||||
import responseHandler from './responseHandler';
|
||||
import problemsHandler from './problemsHandler';
|
||||
@@ -13,7 +14,7 @@ import { Zabbix } from './zabbix/zabbix';
|
||||
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector';
|
||||
import { ZabbixMetricsQuery, ZabbixDSOptions, VariableQueryTypes, ShowProblemTypes, ProblemDTO } from './types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { DataSourceApi, DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { DataFrame, DataQueryRequest, DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, FieldType, isDataFrame, LoadingState } from '@grafana/data';
|
||||
|
||||
export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDSOptions> {
|
||||
name: string;
|
||||
@@ -25,6 +26,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
trendsRange: string;
|
||||
cacheTTL: any;
|
||||
disableReadOnlyUsersAck: boolean;
|
||||
disableDataAlignment: boolean;
|
||||
enableDirectDBConnection: boolean;
|
||||
dbConnectionDatasourceId: number;
|
||||
dbConnectionDatasourceName: string;
|
||||
@@ -64,6 +66,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
|
||||
// Other options
|
||||
this.disableReadOnlyUsersAck = jsonData.disableReadOnlyUsersAck;
|
||||
this.disableDataAlignment = jsonData.disableDataAlignment;
|
||||
|
||||
// Direct DB Connection options
|
||||
this.enableDirectDBConnection = jsonData.dbConnectionEnable || false;
|
||||
@@ -94,7 +97,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
* @param {Object} options Contains time range, targets and other info.
|
||||
* @return {Object} Grafana metrics object with timeseries data for each target.
|
||||
*/
|
||||
query(options) {
|
||||
query(options: DataQueryRequest<any>): Promise<DataQueryResponse> {
|
||||
// Create request for each target
|
||||
const promises = _.map(options.targets, t => {
|
||||
// Don't request for hidden targets
|
||||
@@ -164,7 +167,20 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
return Promise.all(_.flatten(promises))
|
||||
.then(_.flatten)
|
||||
.then(data => {
|
||||
return { data: 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;
|
||||
}).then(data => {
|
||||
return {
|
||||
data,
|
||||
state: LoadingState.Done,
|
||||
key: options.requestId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,28 +223,31 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
/**
|
||||
* Query target data for Metrics
|
||||
*/
|
||||
queryNumericData(target, timeRange, useTrends, options) {
|
||||
let queryStart, queryEnd;
|
||||
async queryNumericData(target, timeRange, useTrends, options): Promise<DataFrame[]> {
|
||||
const getItemOptions = {
|
||||
itemtype: 'num'
|
||||
};
|
||||
return this.zabbix.getItemsFromTarget(target, getItemOptions)
|
||||
.then(items => {
|
||||
queryStart = new Date().getTime();
|
||||
return this.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||
}).then(result => {
|
||||
queryEnd = new Date().getTime();
|
||||
|
||||
const items = await this.zabbix.getItemsFromTarget(target, getItemOptions);
|
||||
|
||||
const queryStart = new Date().getTime();
|
||||
const result = await this.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||
const queryEnd = new Date().getTime();
|
||||
|
||||
if (this.enableDebugLog) {
|
||||
console.log(`Datasource::Performance Query Time (${this.name}): ${queryEnd - queryStart}`);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const valueMappings = await this.zabbix.getValueMappings();
|
||||
|
||||
const dataFrames = result.map(s => responseHandler.seriesToDataFrame(s, target, valueMappings));
|
||||
return dataFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query history for numeric items
|
||||
*/
|
||||
queryNumericDataForItems(items, target, timeRange, useTrends, options) {
|
||||
queryNumericDataForItems(items, target: ZabbixMetricsQuery, timeRange, useTrends, options) {
|
||||
let getHistoryPromise;
|
||||
options.valueType = this.getTrendValueType(target);
|
||||
options.consolidateBy = getConsolidateBy(target) || options.valueType;
|
||||
@@ -236,7 +255,11 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
if (useTrends) {
|
||||
getHistoryPromise = this.zabbix.getTrends(items, timeRange, options);
|
||||
} else {
|
||||
getHistoryPromise = this.zabbix.getHistoryTS(items, timeRange, options);
|
||||
getHistoryPromise = this.zabbix.getHistoryTS(items, timeRange, options)
|
||||
.then(timeseries => {
|
||||
const disableDataAlignment = this.disableDataAlignment || target.options?.disableDataAlignment;
|
||||
return !disableDataAlignment ? this.alignTimeSeriesData(timeseries) : timeseries;
|
||||
});
|
||||
}
|
||||
|
||||
return getHistoryPromise
|
||||
@@ -253,6 +276,14 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
return trendValueFunc ? trendValueFunc.params[0] : "avg";
|
||||
}
|
||||
|
||||
alignTimeSeriesData(timeseries: any[]) {
|
||||
for (const ts of timeseries) {
|
||||
const interval = utils.parseItemInterval(ts.scopedVars['__zbx_item_interval']?.value);
|
||||
ts.datapoints = align(ts.datapoints, interval);
|
||||
}
|
||||
return timeseries;
|
||||
}
|
||||
|
||||
applyDataProcessingFunctions(timeseries_data, target) {
|
||||
const transformFunctions = bindFunctionDefs(target.functions, 'Transform');
|
||||
const aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate');
|
||||
@@ -319,6 +350,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,6 +374,9 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
return this.zabbix.getItemsByIDs(itemids)
|
||||
.then(items => {
|
||||
return this.queryNumericDataForItems(items, target, timeRange, useTrends, options);
|
||||
})
|
||||
.then(result => {
|
||||
return result.map(s => responseHandler.seriesToDataFrame(s, target));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -367,7 +407,11 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
itservices = _.filter(itservices, {'serviceid': target.itservice?.serviceid});
|
||||
}
|
||||
return this.zabbix.getSLA(itservices, timeRange, target, options);})
|
||||
.then(itservicesdp => this.applyDataProcessingFunctions(itservicesdp, target));
|
||||
.then(itservicesdp => this.applyDataProcessingFunctions(itservicesdp, target))
|
||||
.then(result => {
|
||||
const dataFrames = result.map(s => responseHandler.seriesToDataFrame(s, target));
|
||||
return dataFrames;
|
||||
});
|
||||
}
|
||||
|
||||
queryTriggersData(target, timeRange) {
|
||||
@@ -665,7 +709,10 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
target[p].filter = this.replaceTemplateVars(target[p].filter, options.scopedVars);
|
||||
}
|
||||
});
|
||||
|
||||
if (target.textFilter) {
|
||||
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
||||
}
|
||||
|
||||
_.forEach(target.functions, func => {
|
||||
func.params = _.map(func.params, param => {
|
||||
|
||||
@@ -282,20 +282,28 @@
|
||||
|
||||
<!-- Query options -->
|
||||
<div class="gf-form-group offset-width-7" ng-if="ctrl.showQueryOptions">
|
||||
<div class="gf-form" ng-hide="ctrl.target.queryType == editorMode.TRIGGERS || ctrl.target.queryType == editorMode.PROBLEMS">
|
||||
<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="Disable data alignment"
|
||||
checked="ctrl.target.options.disableDataAlignment"
|
||||
on-change="ctrl.onQueryOptionChange()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.target.queryType === editorMode.TEXT && ctrl.target.resultFormat === 'table'">
|
||||
|
||||
<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"
|
||||
|
||||
@@ -171,7 +171,11 @@ export function toDataFrame(problems: any[]): DataFrame {
|
||||
name: 'Problems',
|
||||
type: FieldType.other,
|
||||
values: new ArrayVector(problems),
|
||||
config: {},
|
||||
config: {
|
||||
custom: {
|
||||
type: 'problems',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response: DataFrame = {
|
||||
|
||||
@@ -27,6 +27,7 @@ function getTargetDefaults() {
|
||||
options: {
|
||||
showDisabledItems: false,
|
||||
skipEmptyValues: false,
|
||||
disableDataAlignment: false,
|
||||
},
|
||||
table: {
|
||||
'skipEmptyValues': false
|
||||
@@ -455,6 +456,7 @@ export class ZabbixQueryController extends QueryCtrl {
|
||||
renderQueryOptionsText() {
|
||||
const metricOptionsMap = {
|
||||
showDisabledItems: "Show disabled items",
|
||||
disableDataAlignment: "Disable data alignment",
|
||||
};
|
||||
|
||||
const problemsOptionsMap = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import TableModel from 'grafana/app/core/table_model';
|
||||
import * as c from './constants';
|
||||
import * as utils from './utils';
|
||||
import { ArrayVector, DataFrame, DataQuery, Field, FieldType, MutableDataFrame, TIME_SERIES_TIME_FIELD_NAME, TIME_SERIES_VALUE_FIELD_NAME } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Convert Zabbix API history.get response to Grafana format
|
||||
@@ -35,6 +37,7 @@ function convertHistory(history, items, addHostName, convertPointCallback) {
|
||||
'__zbx_item': { value: item.name },
|
||||
'__zbx_item_name': { value: item.name },
|
||||
'__zbx_item_key': { value: item.key_ },
|
||||
'__zbx_item_interval': { value: item.delay },
|
||||
};
|
||||
|
||||
if (_.keys(hosts).length > 0) {
|
||||
@@ -52,10 +55,184 @@ function convertHistory(history, items, addHostName, convertPointCallback) {
|
||||
target: alias,
|
||||
datapoints: _.map(hist, convertPointCallback),
|
||||
scopedVars,
|
||||
item
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function seriesToDataFrame(timeseries, target: DataQuery, valueMappings?: any[], fieldType?: FieldType): DataFrame {
|
||||
const { datapoints, scopedVars, target: seriesName, item } = timeseries;
|
||||
|
||||
const timeFiled: Field = {
|
||||
name: TIME_SERIES_TIME_FIELD_NAME,
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
custom: {}
|
||||
},
|
||||
values: new ArrayVector<number>(datapoints.map(p => p[c.DATAPOINT_TS])),
|
||||
};
|
||||
|
||||
let values: ArrayVector<number> | ArrayVector<string>;
|
||||
if (fieldType === FieldType.string) {
|
||||
values = new ArrayVector<string>(datapoints.map(p => p[c.DATAPOINT_VALUE]));
|
||||
} else {
|
||||
values = new ArrayVector<number>(datapoints.map(p => p[c.DATAPOINT_VALUE]));
|
||||
}
|
||||
|
||||
const valueFiled: Field = {
|
||||
name: TIME_SERIES_VALUE_FIELD_NAME,
|
||||
type: fieldType ?? FieldType.number,
|
||||
labels: {},
|
||||
config: {
|
||||
displayName: seriesName,
|
||||
displayNameFromDS: seriesName,
|
||||
custom: {}
|
||||
},
|
||||
values,
|
||||
};
|
||||
|
||||
if (scopedVars) {
|
||||
timeFiled.config.custom = {
|
||||
itemInterval: scopedVars['__zbx_item_interval']?.value,
|
||||
};
|
||||
|
||||
valueFiled.labels = {
|
||||
host: scopedVars['__zbx_host_name']?.value,
|
||||
item: scopedVars['__zbx_item']?.value,
|
||||
item_key: scopedVars['__zbx_item_key']?.value,
|
||||
};
|
||||
|
||||
valueFiled.config.custom = {
|
||||
itemInterval: scopedVars['__zbx_item_interval']?.value,
|
||||
};
|
||||
}
|
||||
|
||||
if (item) {
|
||||
// Try to use unit configured in Zabbix
|
||||
const unit = utils.convertZabbixUnit(item.units);
|
||||
if (unit) {
|
||||
console.log(`Datasource: unit detected: ${unit} (${item.units})`);
|
||||
valueFiled.config.unit = unit;
|
||||
|
||||
if (unit === 'percent') {
|
||||
valueFiled.config.min = 0;
|
||||
valueFiled.config.max = 100;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to use value mapping from Zabbix
|
||||
const mappings = utils.getValueMapping(item, valueMappings);
|
||||
if (mappings) {
|
||||
console.log(`Datasource: value mapping detected`);
|
||||
valueFiled.config.mappings = mappings;
|
||||
}
|
||||
}
|
||||
|
||||
const fields: Field[] = [ timeFiled, valueFiled ];
|
||||
|
||||
const frame: DataFrame = {
|
||||
name: seriesName,
|
||||
refId: target.refId,
|
||||
fields,
|
||||
length: datapoints.length,
|
||||
};
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function isConvertibleToWide(data: DataFrame[]): boolean {
|
||||
if (!data || data.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const first = data[0].fields.find(f => f.type === FieldType.time);
|
||||
if (!first) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
const timeField = data[i].fields.find(f => f.type === FieldType.time);
|
||||
|
||||
for (let j = 0; j < Math.min(data.length, 2); j++) {
|
||||
if (timeField.values.get(j) !== first.values.get(j)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function alignFrames(data: DataFrame[]): DataFrame[] {
|
||||
if (!data || data.length === 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Get oldest time stamp for all frames
|
||||
let minTimestamp = data[0].fields.find(f => f.name === TIME_SERIES_TIME_FIELD_NAME).values.get(0);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const timeField = data[i].fields.find(f => f.name === TIME_SERIES_TIME_FIELD_NAME);
|
||||
const firstTs = timeField.values.get(0);
|
||||
if (firstTs < minTimestamp) {
|
||||
minTimestamp = firstTs;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const frame = data[i];
|
||||
const timeField = frame.fields.find(f => f.name === TIME_SERIES_TIME_FIELD_NAME);
|
||||
const valueField = frame.fields.find(f => f.name === TIME_SERIES_VALUE_FIELD_NAME);
|
||||
const firstTs = timeField.values.get(0);
|
||||
|
||||
if (firstTs > minTimestamp) {
|
||||
console.log('Data frames: adding missing points');
|
||||
let timestamps = timeField.values.toArray();
|
||||
let values = valueField.values.toArray();
|
||||
const missingTimestamps = [];
|
||||
const missingValues = [];
|
||||
const frameInterval: number = timeField.config.custom?.itemInterval;
|
||||
for (let j = minTimestamp; j < firstTs; j+=frameInterval) {
|
||||
missingTimestamps.push(j);
|
||||
missingValues.push(null);
|
||||
}
|
||||
|
||||
timestamps = missingTimestamps.concat(timestamps);
|
||||
values = missingValues.concat(values);
|
||||
timeField.values = new ArrayVector(timestamps);
|
||||
valueField.values = new ArrayVector(values);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function convertToWide(data: DataFrame[]): DataFrame[] {
|
||||
const timeField = data[0].fields.find(f => f.type === FieldType.time);
|
||||
if (!timeField) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fields: Field[] = [ timeField ];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const valueField = data[i].fields.find(f => f.name === TIME_SERIES_VALUE_FIELD_NAME);
|
||||
if (!valueField) {
|
||||
continue;
|
||||
}
|
||||
|
||||
valueField.name = data[i].name;
|
||||
fields.push(valueField);
|
||||
}
|
||||
|
||||
const frame: DataFrame = {
|
||||
name: "wide",
|
||||
fields,
|
||||
length: timeField.values.length,
|
||||
};
|
||||
|
||||
return [frame];
|
||||
}
|
||||
|
||||
function sortTimeseries(timeseries) {
|
||||
// Sort trend data, issue #202
|
||||
_.forEach(timeseries, series => {
|
||||
@@ -256,5 +433,9 @@ export default {
|
||||
handleHistoryAsTable,
|
||||
handleSLAResponse,
|
||||
handleTriggersResponse,
|
||||
sortTimeseries
|
||||
sortTimeseries,
|
||||
seriesToDataFrame,
|
||||
isConvertibleToWide,
|
||||
convertToWide,
|
||||
alignFrames,
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import _ from 'lodash';
|
||||
import * as utils from './utils';
|
||||
import * as c from './constants';
|
||||
import { TimeSeriesPoints, TimeSeriesValue } from '@grafana/data';
|
||||
|
||||
const POINT_VALUE = 0;
|
||||
const POINT_TIMESTAMP = 1;
|
||||
@@ -62,6 +63,61 @@ function downsample(datapoints, time_to, ms_interval, func) {
|
||||
return downsampledSeries.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects interval between data points and aligns time series. If there's no value in the interval, puts null as a value.
|
||||
*/
|
||||
export function align(datapoints: TimeSeriesPoints, interval?: number): TimeSeriesPoints {
|
||||
if (interval) {
|
||||
interval = detectSeriesInterval(datapoints);
|
||||
}
|
||||
|
||||
if (interval <= 0 || datapoints.length <= 1) {
|
||||
return datapoints;
|
||||
}
|
||||
|
||||
const aligned_ts: TimeSeriesPoints = [];
|
||||
let frame_ts = getPointTimeFrame(datapoints[0][POINT_TIMESTAMP], interval);
|
||||
let point_frame_ts = frame_ts;
|
||||
let point: TimeSeriesValue[];
|
||||
for (let i = 0; i < datapoints.length; i++) {
|
||||
point = datapoints[i];
|
||||
point_frame_ts = getPointTimeFrame(point[POINT_TIMESTAMP], interval);
|
||||
|
||||
if (point_frame_ts > frame_ts) {
|
||||
// Move frame window to next non-empty interval and fill empty by null
|
||||
while (frame_ts < point_frame_ts) {
|
||||
aligned_ts.push([null, frame_ts]);
|
||||
frame_ts += interval;
|
||||
}
|
||||
}
|
||||
|
||||
aligned_ts.push([point[POINT_VALUE], point_frame_ts]);
|
||||
frame_ts += interval;
|
||||
}
|
||||
return aligned_ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects interval between data points in milliseconds.
|
||||
*/
|
||||
function detectSeriesInterval(datapoints: TimeSeriesPoints): number {
|
||||
if (datapoints.length < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let deltas = [];
|
||||
for (let i = 1; i < datapoints.length; i++) {
|
||||
// Get deltas (in seconds)
|
||||
const d = (datapoints[i][POINT_TIMESTAMP] - datapoints[i - 1][POINT_TIMESTAMP]) / 1000;
|
||||
deltas.push(Math.round(d));
|
||||
}
|
||||
|
||||
// Use 50th percentile (median) as an interval
|
||||
deltas = _.sortBy(deltas);
|
||||
const intervalSec = deltas[Math.floor(deltas.length * 0.5)];
|
||||
return intervalSec * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group points by given time interval
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
@@ -255,7 +311,10 @@ function rate(datapoints) {
|
||||
return newSeries;
|
||||
}
|
||||
|
||||
function simpleMovingAverage(datapoints, n) {
|
||||
function simpleMovingAverage(datapoints: TimeSeriesPoints, n: number): TimeSeriesPoints {
|
||||
// It's not possible to calculate MA if n greater than number of points
|
||||
n = Math.min(n, datapoints.length);
|
||||
|
||||
const sma = [];
|
||||
let w_sum;
|
||||
let w_avg = null;
|
||||
@@ -298,7 +357,10 @@ function simpleMovingAverage(datapoints, n) {
|
||||
return sma;
|
||||
}
|
||||
|
||||
function expMovingAverage(datapoints, n) {
|
||||
function expMovingAverage(datapoints: TimeSeriesPoints, n: number): TimeSeriesPoints {
|
||||
// It's not possible to calculate MA if n greater than number of points
|
||||
n = Math.min(n, datapoints.length);
|
||||
|
||||
let ema = [datapoints[0]];
|
||||
let ema_prev = datapoints[0][POINT_VALUE];
|
||||
let ema_cur;
|
||||
@@ -526,6 +588,7 @@ const exportedFunctions = {
|
||||
PERCENTILE,
|
||||
sortByTime,
|
||||
flattenDatapoints,
|
||||
align,
|
||||
};
|
||||
|
||||
export default exportedFunctions;
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ZabbixDSOptions extends DataSourceJsonData {
|
||||
dbConnectionDatasourceName?: string;
|
||||
dbConnectionRetentionPolicy?: string;
|
||||
disableReadOnlyUsersAck: boolean;
|
||||
disableDataAlignment: boolean;
|
||||
}
|
||||
|
||||
export interface ZabbixSecureJSONData {
|
||||
@@ -37,7 +38,7 @@ export interface ZabbixMetricsQuery extends DataQuery {
|
||||
queryType: string;
|
||||
datasourceId: number;
|
||||
functions: ZabbixMetricFunction[];
|
||||
options: any;
|
||||
options: ZabbixQueryOptions;
|
||||
textFilter: string;
|
||||
mode: number;
|
||||
itemids: number[];
|
||||
@@ -50,6 +51,19 @@ export interface ZabbixMetricsQuery extends DataQuery {
|
||||
itemFilter: string;
|
||||
}
|
||||
|
||||
export interface ZabbixQueryOptions {
|
||||
showDisabledItems?: boolean;
|
||||
skipEmptyValues?: boolean;
|
||||
disableDataAlignment?: boolean;
|
||||
// Problems options
|
||||
minSeverity?: number;
|
||||
sortProblems?: string;
|
||||
acknowledged?: number;
|
||||
hostsInMaintenance?: boolean;
|
||||
hostProxy?: boolean;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface ZabbixMetricFunction {
|
||||
name: string;
|
||||
params: any;
|
||||
|
||||
@@ -3,6 +3,7 @@ import moment from 'moment';
|
||||
import kbn from 'grafana/app/core/utils/kbn';
|
||||
import * as c from './constants';
|
||||
import { VariableQuery, VariableQueryTypes } from './types';
|
||||
import { arrowTableToDataFrame, isTableData, MappingType, ValueMap, ValueMapping, getValueFormats, DataFrame, FieldType } from '@grafana/data';
|
||||
|
||||
/*
|
||||
* This regex matches 3 types of variable reference with an optional format specifier
|
||||
@@ -235,6 +236,26 @@ export function escapeRegex(value) {
|
||||
return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Zabbix item update interval. Returns 0 in case of custom intervals.
|
||||
*/
|
||||
export function parseItemInterval(interval: string): number {
|
||||
const normalizedInterval = normalizeZabbixInterval(interval);
|
||||
if (normalizedInterval) {
|
||||
return parseInterval(normalizedInterval);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function normalizeZabbixInterval(interval: string): string {
|
||||
const intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)?/g;
|
||||
const parsedInterval = intervalPattern.exec(interval);
|
||||
if (!parsedInterval) {
|
||||
return '';
|
||||
}
|
||||
return parsedInterval[1] + (parsedInterval.length > 2 ? parsedInterval[2] : 's');
|
||||
}
|
||||
|
||||
export function parseInterval(interval: string): number {
|
||||
const intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)/g;
|
||||
const momentInterval: any[] = intervalPattern.exec(interval);
|
||||
@@ -387,3 +408,65 @@ export function parseTags(tagStr: string): any[] {
|
||||
export function mustArray(result: any): any[] {
|
||||
return result || [];
|
||||
}
|
||||
|
||||
const getUnitsMap = () => ({
|
||||
'%': 'percent',
|
||||
'b': 'decbits', // bits(SI)
|
||||
'bps': 'bps', // bits/sec(SI)
|
||||
'B': 'bytes', // bytes(IEC)
|
||||
'Bps': 'binBps', // bytes/sec(IEC)
|
||||
// 'unixtime': 'dateTimeAsSystem',
|
||||
'uptime': 'dtdhms',
|
||||
'qps': 'qps', // requests/sec (rps)
|
||||
'iops': 'iops', // I/O ops/sec (iops)
|
||||
'Hz': 'hertz', // Hertz (1/s)
|
||||
'V': 'volt', // Volt (V)
|
||||
'C': 'celsius', // Celsius (°C)
|
||||
'RPM': 'rotrpm', // Revolutions per minute (rpm)
|
||||
'dBm': 'dBm', // Decibel-milliwatt (dBm)
|
||||
});
|
||||
|
||||
const getKnownGrafanaUnits = () => {
|
||||
const units = {};
|
||||
const categories = getValueFormats();
|
||||
for (const category of categories) {
|
||||
for (const unitDesc of category.submenu) {
|
||||
const unit = unitDesc.value;
|
||||
units[unit] = unit;
|
||||
}
|
||||
}
|
||||
return units;
|
||||
};
|
||||
|
||||
const unitsMap = getUnitsMap();
|
||||
const knownGrafanaUnits = getKnownGrafanaUnits();
|
||||
|
||||
export function convertZabbixUnit(zabbixUnit: string): string {
|
||||
let unit = unitsMap[zabbixUnit];
|
||||
if (!unit) {
|
||||
unit = knownGrafanaUnits[zabbixUnit];
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
export function getValueMapping(item, valueMappings: any[]): ValueMapping[] | null {
|
||||
const { valuemapid } = item;
|
||||
const mapping = valueMappings.find(m => m.valuemapid === valuemapid);
|
||||
if (!mapping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (mapping.mappings as any[]).map((m, i) => {
|
||||
const valueMapping: ValueMapping = {
|
||||
id: i,
|
||||
type: MappingType.ValueToText,
|
||||
value: m.value,
|
||||
text: m.newvalue,
|
||||
};
|
||||
return valueMapping;
|
||||
});
|
||||
}
|
||||
|
||||
export function isProblemsDataFrame(data: DataFrame): boolean {
|
||||
return data.fields.length && data.fields[0].type === FieldType.other && data.fields[0].config.custom['type'] === 'problems';
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ function convertGrafanaTSResponse(time_series, items, addHostName) {
|
||||
'__zbx_item': { value: item.name },
|
||||
'__zbx_item_name': { value: item.name },
|
||||
'__zbx_item_key': { value: item.key_ },
|
||||
'__zbx_item_interval': { value: item.delay },
|
||||
};
|
||||
|
||||
if (_.keys(hosts).length > 0) {
|
||||
@@ -153,6 +154,7 @@ function convertGrafanaTSResponse(time_series, items, addHostName) {
|
||||
target: alias,
|
||||
datapoints,
|
||||
scopedVars,
|
||||
item
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -161,11 +161,15 @@ export class ZabbixAPIConnector {
|
||||
getItems(hostids, appids, itemtype) {
|
||||
const params: any = {
|
||||
output: [
|
||||
'name', 'key_',
|
||||
'name',
|
||||
'key_',
|
||||
'value_type',
|
||||
'hostid',
|
||||
'status',
|
||||
'state'
|
||||
'state',
|
||||
'units',
|
||||
'valuemapid',
|
||||
'delay'
|
||||
],
|
||||
sortfield: 'name',
|
||||
webitems: true,
|
||||
@@ -651,6 +655,15 @@ export class ZabbixAPIConnector {
|
||||
|
||||
return this.request('script.execute', params);
|
||||
}
|
||||
|
||||
getValueMappings() {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
selectMappings: "extend",
|
||||
};
|
||||
|
||||
return this.request('valuemap.get', params);
|
||||
}
|
||||
}
|
||||
|
||||
function filterTriggersByAcknowledge(triggers, acknowledged) {
|
||||
|
||||
@@ -20,17 +20,17 @@ interface AppsResponse extends Array<any> {
|
||||
const REQUESTS_TO_PROXYFY = [
|
||||
'getHistory', 'getTrend', 'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs',
|
||||
'getEvents', 'getAlerts', 'getHostAlerts', 'getAcknowledges', 'getITService', 'getSLA', 'getVersion', 'getProxies',
|
||||
'getEventAlerts', 'getExtendedEventData', 'getProblems', 'getEventsHistory', 'getTriggersByIds', 'getScripts'
|
||||
'getEventAlerts', 'getExtendedEventData', 'getProblems', 'getEventsHistory', 'getTriggersByIds', 'getScripts', 'getValueMappings'
|
||||
];
|
||||
|
||||
const REQUESTS_TO_CACHE = [
|
||||
'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs', 'getITService', 'getProxies'
|
||||
'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs', 'getITService', 'getProxies', 'getValueMappings'
|
||||
];
|
||||
|
||||
const REQUESTS_TO_BIND = [
|
||||
'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts',
|
||||
'getAcknowledges', 'getITService', 'getVersion', 'acknowledgeEvent', 'getProxies', 'getEventAlerts',
|
||||
'getExtendedEventData', 'getScripts', 'executeScript',
|
||||
'getExtendedEventData', 'getScripts', 'executeScript', 'getValueMappings'
|
||||
];
|
||||
|
||||
export class Zabbix implements ZabbixConnector {
|
||||
@@ -55,6 +55,7 @@ export class Zabbix implements ZabbixConnector {
|
||||
getExtendedEventData: (eventids) => Promise<any>;
|
||||
getMacros: (hostids: any[]) => Promise<any>;
|
||||
getVersion: () => Promise<string>;
|
||||
getValueMappings: () => Promise<any>;
|
||||
|
||||
constructor(options) {
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user