SLA support in Zabbix 6.0 (#1547)
This commit is contained in:
@@ -11,7 +11,7 @@ import { QueryOptionsEditor } from './QueryEditor/QueryOptionsEditor';
|
||||
import { TextMetricsQueryEditor } from './QueryEditor/TextMetricsQueryEditor';
|
||||
import { ProblemsQueryEditor } from './QueryEditor/ProblemsQueryEditor';
|
||||
import { ItemIdQueryEditor } from './QueryEditor/ItemIdQueryEditor';
|
||||
import { ITServicesQueryEditor } from './QueryEditor/ITServicesQueryEditor';
|
||||
import { ServicesQueryEditor } from './QueryEditor/ServicesQueryEditor';
|
||||
import { TriggersQueryEditor } from './QueryEditor/TriggersQueryEditor';
|
||||
|
||||
const zabbixQueryTypeOptions: Array<SelectableValue<string>> = [
|
||||
@@ -27,8 +27,8 @@ const zabbixQueryTypeOptions: Array<SelectableValue<string>> = [
|
||||
},
|
||||
{
|
||||
value: c.MODE_ITSERVICE,
|
||||
label: 'IT Services',
|
||||
description: 'Query IT Services data',
|
||||
label: 'Services',
|
||||
description: 'Query services SLA',
|
||||
},
|
||||
{
|
||||
value: c.MODE_ITEMID,
|
||||
@@ -75,9 +75,10 @@ const getDefaultQuery: () => Partial<ZabbixMetricsQuery> = () => ({
|
||||
},
|
||||
});
|
||||
|
||||
function getSLAQueryDefaults() {
|
||||
function getSLAQueryDefaults(): Partial<ZabbixMetricsQuery> {
|
||||
return {
|
||||
itServiceFilter: '',
|
||||
slaFilter: '',
|
||||
slaProperty: 'sla',
|
||||
slaInterval: 'none',
|
||||
};
|
||||
@@ -166,7 +167,7 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ
|
||||
const renderITServicesEditor = () => {
|
||||
return (
|
||||
<>
|
||||
<ITServicesQueryEditor query={query} datasource={datasource} onChange={onChangeInternal} />
|
||||
<ServicesQueryEditor query={query} datasource={datasource} onChange={onChangeInternal} />
|
||||
<QueryFunctionsEditor query={query} onChange={onChangeInternal} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, Select } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const slaPropertyList: Array<SelectableValue<string>> = [
|
||||
{ label: 'Status', value: 'status' },
|
||||
{ label: 'SLA', value: 'sla' },
|
||||
{ label: 'OK time', value: 'okTime' },
|
||||
{ label: 'Problem time', value: 'problemTime' },
|
||||
{ label: 'Down time', value: 'downtimeTime' },
|
||||
];
|
||||
|
||||
const slaIntervals: Array<SelectableValue<string>> = [
|
||||
{ label: 'No interval', value: 'none' },
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: '1 hour', value: '1h' },
|
||||
{ label: '12 hours', value: '12h' },
|
||||
{ label: '24 hours', value: '1d' },
|
||||
{ label: '1 week', value: '1w' },
|
||||
{ label: '1 month', value: '1M' },
|
||||
];
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const ITServicesQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadITServiceOptions = async () => {
|
||||
const services = await datasource.zabbix.getITService();
|
||||
const options = services?.map((s) => ({
|
||||
value: s.name,
|
||||
label: s.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: itServicesLoading, value: itServicesOptions }, fetchITServices] = useAsyncFn(async () => {
|
||||
const options = await loadITServiceOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchITServices();
|
||||
}, []);
|
||||
|
||||
const onPropChange = (prop: string) => {
|
||||
return (option: SelectableValue) => {
|
||||
if (option.value) {
|
||||
onChange({ ...query, [prop]: option.value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onITServiceChange = (value: string) => {
|
||||
if (value !== null) {
|
||||
onChange({ ...query, itServiceFilter: value });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryEditorRow>
|
||||
<InlineField label="IT Service" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.itServiceFilter}
|
||||
options={itServicesOptions}
|
||||
isLoading={itServicesLoading}
|
||||
onChange={onITServiceChange}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Property" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.slaProperty}
|
||||
options={slaPropertyList}
|
||||
onChange={onPropChange('slaProperty')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Interval" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.slaInterval}
|
||||
options={slaIntervals}
|
||||
onChange={onPropChange('slaInterval')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
);
|
||||
};
|
||||
133
src/datasource/components/QueryEditor/ServicesQueryEditor.tsx
Normal file
133
src/datasource/components/QueryEditor/ServicesQueryEditor.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, Select } from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const slaPropertyList: Array<SelectableValue<string>> = [
|
||||
{ label: 'Status', value: 'status' },
|
||||
{ label: 'SLI', value: 'sli' },
|
||||
{ label: 'Uptime', value: 'uptime' },
|
||||
{ label: 'Downtime', value: 'downtime' },
|
||||
{ label: 'Error budget', value: 'error_budget' },
|
||||
];
|
||||
|
||||
const slaIntervals: Array<SelectableValue<string>> = [
|
||||
{ label: 'No interval', value: 'none' },
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: '1 hour', value: '1h' },
|
||||
{ label: '12 hours', value: '12h' },
|
||||
{ label: '24 hours', value: '1d' },
|
||||
{ label: '1 week', value: '1w' },
|
||||
{ label: '1 month', value: '1M' },
|
||||
];
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
datasource: ZabbixDatasource;
|
||||
onChange: (query: ZabbixMetricsQuery) => void;
|
||||
}
|
||||
|
||||
export const ServicesQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const loadITServiceOptions = async () => {
|
||||
const services = await datasource.zabbix.getITService();
|
||||
const options = services?.map((s) => ({
|
||||
value: s.name,
|
||||
label: s.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: itServicesLoading, value: itServicesOptions }, fetchITServices] = useAsyncFn(async () => {
|
||||
const options = await loadITServiceOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
const loadSLAOptions = async () => {
|
||||
const slaOptions = await datasource.zabbix.getSLAList();
|
||||
const options = slaOptions?.map((s) => ({
|
||||
value: s.name,
|
||||
label: s.name,
|
||||
}));
|
||||
options.unshift(...getVariableOptions());
|
||||
return options;
|
||||
};
|
||||
|
||||
const [{ loading: slaLoading, value: slaOptions }, fetchSLAOptions] = useAsyncFn(async () => {
|
||||
const options = await loadSLAOptions();
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchITServices();
|
||||
fetchSLAOptions();
|
||||
}, []);
|
||||
|
||||
const onPropChange = (prop: string) => {
|
||||
return (option: SelectableValue) => {
|
||||
if (option.value) {
|
||||
onChange({ ...query, [prop]: option.value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onStringPropChange = (prop: string) => {
|
||||
return (value: string) => {
|
||||
if (value !== undefined) {
|
||||
onChange({ ...query, [prop]: value });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Service" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.itServiceFilter}
|
||||
options={itServicesOptions}
|
||||
isLoading={itServicesLoading}
|
||||
onChange={onStringPropChange('itServiceFilter')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="SLA" labelWidth={12}>
|
||||
<MetricPicker
|
||||
width={24}
|
||||
value={query.slaFilter}
|
||||
options={slaOptions}
|
||||
isLoading={slaLoading}
|
||||
onChange={onStringPropChange('slaFilter')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
<QueryEditorRow>
|
||||
<InlineField label="Property" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.slaProperty}
|
||||
options={slaPropertyList}
|
||||
onChange={onPropChange('slaProperty')}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Interval" labelWidth={12}>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
width={24}
|
||||
value={query.slaInterval}
|
||||
options={slaIntervals}
|
||||
onChange={onPropChange('slaInterval')}
|
||||
/>
|
||||
</InlineField>
|
||||
</QueryEditorRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -470,14 +470,14 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
/**
|
||||
* Query target data for IT Services
|
||||
*/
|
||||
async queryITServiceData(target, timeRange, request) {
|
||||
async queryITServiceData(target: ZabbixMetricsQuery, timeRange, request) {
|
||||
// Don't show undefined and hidden targets
|
||||
if (target.hide || (!target.itservice && !target.itServiceFilter) || !target.slaProperty) {
|
||||
if (target.hide || (!(target as any).itservice && !target.itServiceFilter) || !target.slaProperty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let itServiceFilter;
|
||||
request.isOldVersion = target.itservice && !target.itServiceFilter;
|
||||
request.isOldVersion = (target as any).itservice && !target.itServiceFilter;
|
||||
|
||||
if (request.isOldVersion) {
|
||||
// Backward compatibility
|
||||
@@ -490,7 +490,13 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
|
||||
let itservices = await this.zabbix.getITServices(itServiceFilter);
|
||||
if (request.isOldVersion) {
|
||||
itservices = _.filter(itservices, { serviceid: target.itservice?.serviceid });
|
||||
itservices = _.filter(itservices, { serviceid: (target as any).itservice?.serviceid });
|
||||
}
|
||||
if (target.slaFilter !== undefined) {
|
||||
const slaFilter = this.replaceTemplateVars(target.slaFilter, request.scopedVars);
|
||||
const slas = await this.zabbix.getSLAs(slaFilter);
|
||||
const result = await this.zabbix.getSLI(itservices, slas, timeRange, target, request);
|
||||
return result;
|
||||
}
|
||||
const itservicesdp = await this.zabbix.getSLA(itservices, timeRange, target, request);
|
||||
const backendRequest = responseHandler.itServiceResponseToTimeSeries(itservicesdp, target.slaInterval);
|
||||
|
||||
@@ -467,7 +467,105 @@ function extractText(str, pattern, useCaptureGroups) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function handleSLAResponse(itservice, slaProperty, slaObject) {
|
||||
export function handleSLIResponse(response: any, itservices: any[], target: ZabbixMetricsQuery) {
|
||||
const timestamps = [];
|
||||
for (let i = 0; i < response?.periods?.length; i++) {
|
||||
const period = response.periods[i];
|
||||
if (i === 0) {
|
||||
timestamps.push(period.period_from * 1000);
|
||||
}
|
||||
timestamps.push(period.period_to * 1000);
|
||||
}
|
||||
|
||||
const timeFiled: Field = {
|
||||
name: TIME_SERIES_TIME_FIELD_NAME,
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
custom: {},
|
||||
},
|
||||
values: new ArrayVector<number>(timestamps),
|
||||
};
|
||||
|
||||
const valueFields: Field[] = [];
|
||||
const values: number[][] = [];
|
||||
|
||||
let slaProperty = mapLegacySLAProperty(target.slaProperty);
|
||||
for (let i = 0; i < response?.sli?.length; i++) {
|
||||
const slis = response.sli[i];
|
||||
for (let j = 0; j < slis.length; j++) {
|
||||
const sli = slis[j];
|
||||
const value = sli[slaProperty];
|
||||
if (!values[j]) {
|
||||
values[j] = [];
|
||||
}
|
||||
if (i === 0) {
|
||||
values[j].push(value);
|
||||
}
|
||||
values[j].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < response?.serviceids?.length; i++) {
|
||||
const serviceId = response?.serviceids[i].toString();
|
||||
const service = itservices.find((s) => s.serviceid === serviceId);
|
||||
valueFields.push({
|
||||
name: service ? service.name : serviceId,
|
||||
type: FieldType.number,
|
||||
config: {},
|
||||
values: new ArrayVector<number>(values[i]),
|
||||
});
|
||||
}
|
||||
|
||||
return new MutableDataFrame({
|
||||
refId: target.refId,
|
||||
name: 'SLI',
|
||||
fields: [timeFiled, ...valueFields],
|
||||
});
|
||||
}
|
||||
|
||||
function mapLegacySLAProperty(property: string) {
|
||||
switch (property) {
|
||||
case 'sla':
|
||||
return 'sli';
|
||||
case 'okTime':
|
||||
return 'uptime';
|
||||
case 'downtimeTime':
|
||||
return 'downtime';
|
||||
default:
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleServiceResponse(response: any, itservices: any[], target: ZabbixMetricsQuery) {
|
||||
const valueFields: Field[] = [];
|
||||
for (let i = 0; i < response?.length; i++) {
|
||||
const service = response[i];
|
||||
const status = Number(service.status);
|
||||
valueFields.push({
|
||||
name: service ? service.name : i,
|
||||
type: FieldType.number,
|
||||
config: {},
|
||||
values: new ArrayVector<number>([status]),
|
||||
});
|
||||
}
|
||||
|
||||
const timeFiled: Field = {
|
||||
name: TIME_SERIES_TIME_FIELD_NAME,
|
||||
type: FieldType.time,
|
||||
config: {
|
||||
custom: {},
|
||||
},
|
||||
values: new ArrayVector<number>([Date.now()]),
|
||||
};
|
||||
|
||||
return new MutableDataFrame({
|
||||
refId: target.refId,
|
||||
name: 'Service status',
|
||||
fields: [timeFiled, ...valueFields],
|
||||
});
|
||||
}
|
||||
|
||||
export function handleSLAResponse(itservice, slaProperty, slaObject) {
|
||||
const targetSLA = slaObject[itservice.serviceid].sla;
|
||||
if (slaProperty === 'status') {
|
||||
const targetStatus = parseInt(slaObject[itservice.serviceid].status, 10);
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface ZabbixMetricsQuery extends DataQuery {
|
||||
proxy?: { filter: string };
|
||||
trigger?: { filter: string };
|
||||
itServiceFilter?: string;
|
||||
slaFilter?: string;
|
||||
slaProperty?: any;
|
||||
slaInterval?: string;
|
||||
tags?: { filter: string };
|
||||
|
||||
@@ -330,7 +330,7 @@ export class ZabbixAPIConnector {
|
||||
return self.request('trend.get', params);
|
||||
}
|
||||
|
||||
getITService(serviceids?) {
|
||||
getITService(serviceids?: any[]) {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
serviceids: serviceids,
|
||||
@@ -338,6 +338,22 @@ export class ZabbixAPIConnector {
|
||||
return this.request('service.get', params);
|
||||
}
|
||||
|
||||
// Returns services. Non-cached method (for getting actual service status).
|
||||
getServices(serviceids?: any[]) {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
serviceids: serviceids,
|
||||
};
|
||||
return this.request('service.get', params);
|
||||
}
|
||||
|
||||
getSLAList() {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
};
|
||||
return this.request('sla.get', params);
|
||||
}
|
||||
|
||||
getSLA(serviceids, timeRange, options) {
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
let intervals = [{ from: timeFrom, to: timeTo }];
|
||||
@@ -416,6 +432,29 @@ export class ZabbixAPIConnector {
|
||||
return slaLikeResponse;
|
||||
}
|
||||
|
||||
async getSLI(slaid, serviceids, timeRange, options) {
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
let intervals = [{ from: timeFrom, to: timeTo }];
|
||||
if (options.slaInterval === 'auto') {
|
||||
const interval = getSLAInterval(options.intervalMs);
|
||||
intervals = buildSLAIntervals(timeRange, interval);
|
||||
} else if (options.slaInterval !== 'none') {
|
||||
const interval = utils.parseInterval(options.slaInterval) / 1000;
|
||||
intervals = buildSLAIntervals(timeRange, interval);
|
||||
}
|
||||
|
||||
const sliParams: any = {
|
||||
slaid,
|
||||
serviceids,
|
||||
period_from: timeFrom,
|
||||
period_to: timeTo,
|
||||
periods: Math.min(intervals.length, 100),
|
||||
};
|
||||
|
||||
const sliResponse = await this.request('sla.getsli', sliParams);
|
||||
return sliResponse;
|
||||
}
|
||||
|
||||
getProblems(groupids, hostids, applicationids, options): Promise<ZBXProblem[]> {
|
||||
const { timeFrom, timeTo, recent, severities, limit, acknowledged, tags } = options;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import semver from 'semver';
|
||||
import * as utils from '../utils';
|
||||
import responseHandler from '../responseHandler';
|
||||
import responseHandler, { handleServiceResponse, handleSLIResponse } from '../responseHandler';
|
||||
import { CachingProxy } from './proxy/cachingProxy';
|
||||
import { DBConnector } from './connectors/dbConnector';
|
||||
import { ZabbixAPIConnector } from './connectors/zabbix_api/zabbixAPIConnector';
|
||||
@@ -11,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, ZBXItem, ZBXItemTag } from '../types';
|
||||
import { ProblemDTO, ZabbixMetricsQuery, ZBXItem, ZBXItemTag } from '../types';
|
||||
|
||||
interface AppsResponse extends Array<any> {
|
||||
appFilterEmpty?: boolean;
|
||||
@@ -41,6 +41,7 @@ const REQUESTS_TO_PROXYFY = [
|
||||
'getTriggersByIds',
|
||||
'getScripts',
|
||||
'getValueMappings',
|
||||
'getSLAList',
|
||||
];
|
||||
|
||||
const REQUESTS_TO_CACHE = [
|
||||
@@ -53,6 +54,7 @@ const REQUESTS_TO_CACHE = [
|
||||
'getITService',
|
||||
'getProxies',
|
||||
'getValueMappings',
|
||||
'getSLAList',
|
||||
];
|
||||
|
||||
const REQUESTS_TO_BIND = [
|
||||
@@ -72,6 +74,7 @@ const REQUESTS_TO_BIND = [
|
||||
'getScripts',
|
||||
'executeScript',
|
||||
'getValueMappings',
|
||||
'getSLAList',
|
||||
];
|
||||
|
||||
export class Zabbix implements ZabbixConnector {
|
||||
@@ -97,6 +100,7 @@ export class Zabbix implements ZabbixConnector {
|
||||
getExtendedEventData: (eventids) => Promise<any>;
|
||||
getMacros: (hostids: any[]) => Promise<any>;
|
||||
getValueMappings: () => Promise<any>;
|
||||
getSLAList: () => Promise<any>;
|
||||
|
||||
constructor(options) {
|
||||
const {
|
||||
@@ -396,8 +400,14 @@ export class Zabbix implements ZabbixConnector {
|
||||
});
|
||||
}
|
||||
|
||||
getITServices(itServiceFilter) {
|
||||
return this.zabbixAPI.getITService().then((itServices) => findByFilter(itServices, itServiceFilter));
|
||||
async getITServices(itServiceFilter: string) {
|
||||
const itServices = await this.zabbixAPI.getITService();
|
||||
return findByFilter(itServices, itServiceFilter);
|
||||
}
|
||||
|
||||
async getSLAs(slaFilter: string) {
|
||||
const slas = await this.zabbixAPI.getSLAList();
|
||||
return findByFilter(slas, slaFilter);
|
||||
}
|
||||
|
||||
getProblems(groupFilter, hostFilter, appFilter, proxyFilter?, options?): Promise<ProblemDTO[]> {
|
||||
@@ -538,6 +548,19 @@ export class Zabbix implements ZabbixConnector {
|
||||
}
|
||||
}
|
||||
|
||||
async getSLI(itservices: any[], slas: any[], timeRange, target: ZabbixMetricsQuery, options) {
|
||||
const itServiceIds = itservices.map((s) => s.serviceid);
|
||||
if (target.slaProperty === 'status') {
|
||||
const res = await this.zabbixAPI.getServices(itServiceIds);
|
||||
return handleServiceResponse(res, itservices, target);
|
||||
}
|
||||
|
||||
const slaIds = slas.map((s) => s.slaid);
|
||||
const slaId = slaIds?.length > 0 ? slaIds[0] : undefined;
|
||||
const result = await this.zabbixAPI.getSLI(slaId, itServiceIds, timeRange, options);
|
||||
return handleSLIResponse(result, itservices, target);
|
||||
}
|
||||
|
||||
async getSLA(itservices, timeRange, target, options) {
|
||||
const itServiceIds = _.map(itservices, 'serviceid');
|
||||
if (this.supportSLA()) {
|
||||
|
||||
Reference in New Issue
Block a user