Problems: use problems.get method for fetching triggers, closes #495

This commit is contained in:
Alexander Zobnin
2020-05-15 18:17:54 +03:00
parent 1b11f3d46d
commit 056f82731e
10 changed files with 361 additions and 130 deletions

View File

@@ -412,39 +412,29 @@ export class ZabbixDatasource {
tags: { filter: tagsFilter }, tags: { filter: tagsFilter },
}; };
const triggersOptions: any = { const problemsOptions: any = {
showTriggers: showProblems recent: showProblems === ShowProblemTypes.Recent,
limit: target.options?.limit,
}; };
if (showProblems !== ShowProblemTypes.Problems) { if (showProblems !== ShowProblemTypes.Problems) {
triggersOptions.timeFrom = timeFrom; problemsOptions.timeFrom = timeFrom;
triggersOptions.timeTo = timeTo; problemsOptions.timeTo = timeTo;
} }
const problemsPromises = Promise.all([ const problemsPromises = Promise.all([
this.zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions, proxyFilter), this.zabbix.getProblems(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions),
getProxiesPromise getProxiesPromise
]) ])
.then(([triggers, sourceProxies]) => { .then(([problems, sourceProxies]) => {
proxies = _.keyBy(sourceProxies, 'proxyid'); proxies = _.keyBy(sourceProxies, 'proxyid');
const eventids = _.compact(triggers.map(trigger => { return problems;
return trigger.lastEvent.eventid;
}));
return Promise.all([
this.zabbix.getExtendedEventData(eventids),
Promise.resolve(triggers)
]);
}) })
.then(([events, triggers]) => { .then(problems => problemsHandler.setMaintenanceStatus(problems))
problemsHandler.addEventTags(events, triggers); .then(problems => problemsHandler.setAckButtonStatus(problems, showAckButton))
problemsHandler.addAcknowledges(events, triggers); .then(problems => problemsHandler.filterTriggersPre(problems, replacedTarget))
return triggers; .then(problems => problemsHandler.addTriggerDataSource(problems, target))
}) .then(problems => problemsHandler.addTriggerHostProxy(problems, proxies));
.then(triggers => problemsHandler.setMaintenanceStatus(triggers))
.then(triggers => problemsHandler.setAckButtonStatus(triggers, showAckButton))
.then(triggers => problemsHandler.filterTriggersPre(triggers, replacedTarget))
.then(triggers => problemsHandler.addTriggerDataSource(triggers, target))
.then(triggers => problemsHandler.addTriggerHostProxy(triggers, proxies));
return problemsPromises.then(problems => { return problemsPromises.then(problems => {
const problemsDataFrame = problemsHandler.toDataFrame(problems); const problemsDataFrame = problemsHandler.toDataFrame(problems);
@@ -567,13 +557,13 @@ export class ZabbixDatasource {
hideHostsInMaintenance: false hideHostsInMaintenance: false
}; };
const getTriggers = this.zabbix.getTriggers(this.replaceTemplateVars(annotation.group, {}), const groupFilter = this.replaceTemplateVars(annotation.group, {});
this.replaceTemplateVars(annotation.host, {}), const hostFilter = this.replaceTemplateVars(annotation.host, {});
this.replaceTemplateVars(annotation.application, {}), const appFilter = this.replaceTemplateVars(annotation.application, {});
triggersOptions); const proxyFilter = undefined;
return getTriggers.then(triggers => {
return this.zabbix.getProblems(groupFilter, hostFilter, appFilter, proxyFilter, triggersOptions)
.then(triggers => {
// Filter triggers by description // Filter triggers by description
const triggerName = this.replaceTemplateVars(annotation.trigger, {}); const triggerName = this.replaceTemplateVars(annotation.trigger, {});
if (utils.isRegex(triggerName)) { if (utils.isRegex(triggerName)) {

View File

@@ -4,36 +4,45 @@ import TableModel from 'grafana/app/core/table_model';
import * as utils from '../datasource-zabbix/utils'; import * as utils from '../datasource-zabbix/utils';
import * as c from './constants'; import * as c from './constants';
import { DataFrame, Field, FieldType, ArrayVector } from '@grafana/data'; import { DataFrame, Field, FieldType, ArrayVector } from '@grafana/data';
import { ZBXProblem, ZBXTrigger, ProblemDTO } from './types';
export function addEventTags(events, triggers) { export function joinTriggersWithProblems(problems: ZBXProblem[], triggers: ZBXTrigger[]): ProblemDTO[] {
_.each(triggers, trigger => { const problemDTOList: ProblemDTO[] = [];
const event = _.find(events, event => { for (let i = 0; i < problems.length; i++) {
return event.eventid === trigger.lastEvent.eventid; const p = problems[i];
}); const triggerId = Number(p.objectid);
if (event && event.tags && event.tags.length) { const t = triggers[triggerId];
trigger.tags = event.tags; const problemDTO: ProblemDTO = {
} timestamp: Number(p.clock),
}); triggerid: p.objectid,
return triggers; eventid: p.eventid,
name: p.name,
severity: p.severity,
acknowledged: p.acknowledged,
acknowledges: p.acknowledges,
tags: p.tags,
suppressed: p.suppressed,
suppression_data: p.suppression_data,
description: t.description,
comments: t.comments,
value: t.value,
groups: t.groups,
hosts: t.hosts,
items: t.items,
alerts: t.alerts,
url: t.url,
expression: t.expression,
correlation_mode: t.correlation_mode,
correlation_tag: t.correlation_tag,
manual_close: t.manual_close,
state: t.state,
error: t.error,
};
problemDTOList.push(problemDTO);
} }
export function addAcknowledges(events, triggers) { return problemDTOList;
// Map events to triggers
_.each(triggers, trigger => {
const event = _.find(events, event => {
return event.eventid === trigger.lastEvent.eventid;
});
if (event) {
trigger.acknowledges = event.acknowledges;
}
if (!trigger.lastEvent.eventid) {
trigger.lastEvent = null;
}
});
return triggers;
} }
export function setMaintenanceStatus(triggers) { export function setMaintenanceStatus(triggers) {
@@ -129,8 +138,6 @@ export function toDataFrame(problems: any[]): DataFrame {
} }
const problemsHandler = { const problemsHandler = {
addEventTags,
addAcknowledges,
addTriggerDataSource, addTriggerDataSource,
addTriggerHostProxy, addTriggerHostProxy,
setMaintenanceStatus, setMaintenanceStatus,

View File

@@ -34,3 +34,172 @@ export enum ShowProblemTypes {
Recent = 'recent', Recent = 'recent',
History = 'history', History = 'history',
} }
export interface ProblemDTO {
triggerid?: string;
eventid?: string;
timestamp: number;
/** Name of the trigger. */
name?: string;
/** Same as a name. */
description?: string;
/** Whether the trigger is in OK or problem state. */
value?: string;
datasource?: string;
comments?: string;
host?: string;
hostTechName?: string;
proxy?: string;
severity?: string;
acknowledged?: '1' | '0';
acknowledges?: ZBXAcknowledge[];
groups?: ZBXGroup[];
hosts?: ZBXHost[];
items?: ZBXItem[];
alerts?: ZBXAlert[];
tags?: ZBXTag[];
url?: string;
expression?: string;
correlation_mode?: string;
correlation_tag?: string;
suppressed?: string;
suppression_data?: any[];
state?: string;
maintenance?: boolean;
manual_close?: string;
error?: string;
showAckButton?: boolean;
}
export interface ZBXProblem {
acknowledged?: '1' | '0';
acknowledges?: ZBXAcknowledge[];
clock: string;
ns: string;
correlationid?: string;
datasource?: string;
name?: string;
eventid?: string;
maintenance?: boolean;
object?: string;
objectid?: string;
opdata?: any;
r_eventid?: string;
r_clock?: string;
r_ns?: string;
severity?: string;
showAckButton?: boolean;
source?: string;
suppressed?: string;
suppression_data?: any[];
tags?: ZBXTag[];
userid?: string;
}
export interface ZBXTrigger {
acknowledges?: ZBXAcknowledge[];
showAckButton?: boolean;
alerts?: ZBXAlert[];
age?: string;
color?: string;
comments?: string;
correlation_mode?: string;
correlation_tag?: string;
datasource?: string;
description?: string;
error?: string;
expression?: string;
flags?: string;
groups?: ZBXGroup[];
host?: string;
hostTechName?: string;
hosts?: ZBXHost[];
items?: ZBXItem[];
lastEvent?: ZBXEvent;
lastchange?: string;
lastchangeUnix?: number;
maintenance?: boolean;
manual_close?: string;
priority?: string;
proxy?: string;
recovery_expression?: string;
recovery_mode?: string;
severity?: string;
state?: string;
status?: string;
tags?: ZBXTag[];
templateid?: string;
triggerid?: string;
/** Whether the trigger can generate multiple problem events. */
type?: string;
url?: string;
value?: string;
}
export interface ZBXGroup {
groupid: string;
name: string;
}
export interface ZBXHost {
hostid: string;
name: string;
host: string;
maintenance_status?: string;
proxy_hostid?: string;
}
export interface ZBXItem {
itemid: string;
name: string;
key_: string;
lastvalue?: string;
}
export interface ZBXEvent {
eventid: string;
clock: string;
ns?: string;
value?: string;
source?: string;
object?: string;
objectid?: string;
acknowledged?: string;
severity?: string;
hosts?: ZBXHost[];
acknowledges?: ZBXAcknowledge[];
}
export interface ZBXTag {
tag: string;
value?: string;
}
export interface ZBXAcknowledge {
acknowledgeid: string;
eventid: string;
userid: string;
action: string;
clock: string;
time: string;
message?: string;
user: string;
alias: string;
name: string;
surname: string;
}
export interface ZBXAlert {
eventid: string;
clock: string;
message: string;
error: string;
}

View File

@@ -4,7 +4,7 @@ import kbn from 'grafana/app/core/utils/kbn';
import * as utils from '../../../utils'; import * as utils from '../../../utils';
import { ZabbixAPICore } from './zabbixAPICore'; import { ZabbixAPICore } from './zabbixAPICore';
import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants'; import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants';
import { ShowProblemTypes } from '../../../types'; import { ShowProblemTypes, ZBXProblem } from '../../../types';
const DEFAULT_ZABBIX_VERSION = '3.0.0'; const DEFAULT_ZABBIX_VERSION = '3.0.0';
@@ -367,6 +367,62 @@ export class ZabbixAPIConnector {
return this.request('service.getsla', params); return this.request('service.getsla', params);
} }
getProblems(groupids, hostids, applicationids, options): Promise<ZBXProblem[]> {
const { timeFrom, timeTo, recent, severities, limit } = options;
const params: any = {
output: 'extend',
selectAcknowledges: 'extend',
selectSuppressionData: 'extend',
selectTags: 'extend',
source: '0',
object: '0',
sortfield: ['eventid'],
sortorder: 'DESC',
evaltype: '0',
// preservekeys: '1',
groupids,
hostids,
applicationids,
recent,
};
if (severities) {
params.severities = severities;
}
if (limit) {
params.limit = limit;
}
if (timeFrom || timeTo) {
params.time_from = timeFrom;
params.time_till = timeTo;
}
return this.request('problem.get', params);
}
getTriggersByIds(triggerids: string[]) {
const params: any = {
output: 'extend',
triggerids: triggerids,
expandDescription: true,
expandData: true,
expandComment: true,
monitored: true,
skipDependent: true,
selectGroups: ['name'],
selectHosts: ['name', 'host', 'maintenance_status', 'proxy_hostid'],
selectItems: ['name', 'key_', 'lastvalue'],
// selectLastEvent: 'extend',
// selectTags: 'extend',
preservekeys: '1',
};
return this.request('trigger.get', params);
}
getTriggers(groupids, hostids, applicationids, options) { getTriggers(groupids, hostids, applicationids, options) {
const {showTriggers, maintenance, timeFrom, timeTo} = options; const {showTriggers, maintenance, timeFrom, timeTo} = options;

View File

@@ -8,6 +8,7 @@ import { ZabbixAPIConnector } from './connectors/zabbix_api/zabbixAPIConnector';
import { SQLConnector } from './connectors/sql/sqlConnector'; import { SQLConnector } from './connectors/sql/sqlConnector';
import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector'; import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector';
import { ZabbixConnector } from './types'; import { ZabbixConnector } from './types';
import { joinTriggersWithProblems } from '../problemsHandler';
const REQUESTS_TO_PROXYFY = [ const REQUESTS_TO_PROXYFY = [
'getHistory', 'getTrend', 'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs', 'getHistory', 'getTrend', 'getGroups', 'getHosts', 'getApps', 'getItems', 'getMacros', 'getItemsByIDs',
@@ -286,10 +287,7 @@ export class Zabbix implements ZabbixConnector {
.then(itServices => findByFilter(itServices, itServiceFilter)); .then(itServices => findByFilter(itServices, itServiceFilter));
} }
/** getProblems(groupFilter, hostFilter, appFilter, proxyFilter?, options?) {
* Build query - convert target filters to array of Zabbix items
*/
getTriggers(groupFilter, hostFilter, appFilter, options?, proxyFilter?) {
const promises = [ const promises = [
this.getGroups(groupFilter), this.getGroups(groupFilter),
this.getHosts(groupFilter, hostFilter), this.getHosts(groupFilter, hostFilter),
@@ -313,7 +311,13 @@ export class Zabbix implements ZabbixConnector {
return query; return query;
}) })
.then(query => this.zabbixAPI.getTriggers(query.groupids, query.hostids, query.applicationids, options)) // .then(query => this.zabbixAPI.getTriggers(query.groupids, query.hostids, query.applicationids, options))
.then(query => this.zabbixAPI.getProblems(query.groupids, query.hostids, query.applicationids, options))
.then(problems => {
const triggerids = problems?.map(problem => problem.objectid);
return Promise.all([Promise.resolve(problems), this.zabbixAPI.getTriggersByIds(triggerids)]);
})
.then(([problems, triggers]) => joinTriggersWithProblems(problems, triggers))
.then(triggers => this.filterTriggersByProxy(triggers, proxyFilter)) .then(triggers => this.filterTriggersByProxy(triggers, proxyFilter))
.then(triggers => this.expandUserMacro.bind(this)(triggers, true)); .then(triggers => this.expandUserMacro.bind(this)(triggers, true));
} }
@@ -324,14 +328,13 @@ export class Zabbix implements ZabbixConnector {
if (proxyFilter && proxyFilter !== '/.*/' && triggers) { if (proxyFilter && proxyFilter !== '/.*/' && triggers) {
const proxy_ids = proxies.map(proxy => proxy.proxyid); const proxy_ids = proxies.map(proxy => proxy.proxyid);
triggers = triggers.filter(trigger => { triggers = triggers.filter(trigger => {
let filtered = false;
for (let i = 0; i < trigger.hosts.length; i++) { for (let i = 0; i < trigger.hosts.length; i++) {
const host = trigger.hosts[i]; const host = trigger.hosts[i];
if (proxy_ids.includes(host.proxy_hostid)) { if (proxy_ids.includes(host.proxy_hostid)) {
filtered = true; return true;
} }
} }
return filtered; return false;
}); });
} }
return triggers; return triggers;

View File

@@ -3,18 +3,19 @@ import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { isNewProblem, formatLastChange } from '../../utils'; import { isNewProblem, formatLastChange } from '../../utils';
import { ProblemsPanelOptions, ZBXTrigger, TriggerSeverity, ZBXTag } from '../../types'; import { ProblemsPanelOptions, TriggerSeverity } from '../../types';
import { AckProblemData, Modal } from '.././Modal'; import { AckProblemData, Modal } from '.././Modal';
import EventTag from '../EventTag'; import EventTag from '../EventTag';
import Tooltip from '.././Tooltip/Tooltip'; import Tooltip from '.././Tooltip/Tooltip';
import AlertAcknowledges from './AlertAcknowledges'; import AlertAcknowledges from './AlertAcknowledges';
import AlertIcon from './AlertIcon'; import AlertIcon from './AlertIcon';
import { ProblemDTO, ZBXTag } from '../../../datasource-zabbix/types';
interface AlertCardProps { interface AlertCardProps {
problem: ZBXTrigger; problem: ProblemDTO;
panelOptions: ProblemsPanelOptions; panelOptions: ProblemsPanelOptions;
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => Promise<any> | any; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => Promise<any> | any;
} }
interface AlertCardState { interface AlertCardState {
@@ -61,13 +62,13 @@ export default class AlertCard extends PureComponent<AlertCardProps, AlertCardSt
const descriptionClass = classNames('alert-rule-item__text', { 'zbx-description--newline': panelOptions.descriptionAtNewLine }); const descriptionClass = classNames('alert-rule-item__text', { 'zbx-description--newline': panelOptions.descriptionAtNewLine });
let severityDesc: TriggerSeverity; let severityDesc: TriggerSeverity;
severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === Number(problem.priority)); severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === Number(problem.severity));
if (problem.lastEvent?.severity) { if (problem.severity) {
severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === Number(problem.lastEvent.severity)); severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === Number(problem.severity));
} }
const lastchange = formatLastChange(problem.lastchangeUnix, panelOptions.customLastChangeFormat && panelOptions.lastChangeFormat); const lastchange = formatLastChange(problem.timestamp, panelOptions.customLastChangeFormat && panelOptions.lastChangeFormat);
const age = moment.unix(problem.lastchangeUnix).fromNow(true); const age = moment.unix(problem.timestamp).fromNow(true);
let newProblem = false; let newProblem = false;
if (panelOptions.highlightNewerThan) { if (panelOptions.highlightNewerThan) {
@@ -78,7 +79,7 @@ export default class AlertCard extends PureComponent<AlertCardProps, AlertCardSt
let problemColor: string; let problemColor: string;
if (problem.value === '0') { if (problem.value === '0') {
problemColor = panelOptions.okEventColor; problemColor = panelOptions.okEventColor;
} else if (panelOptions.markAckEvents && problem.lastEvent?.acknowledged === "1") { } else if (panelOptions.markAckEvents && problem.acknowledged === "1") {
problemColor = panelOptions.ackEventColor; problemColor = panelOptions.ackEventColor;
} else { } else {
problemColor = severityDesc.color; problemColor = severityDesc.color;
@@ -159,7 +160,7 @@ export default class AlertCard extends PureComponent<AlertCardProps, AlertCardSt
<span><i className="fa fa-question-circle"></i></span> <span><i className="fa fa-question-circle"></i></span>
</Tooltip> </Tooltip>
)} )}
{problem.lastEvent && ( {problem.eventid && (
<AlertAcknowledgesButton problem={problem} onClick={this.showAckDialog} /> <AlertAcknowledgesButton problem={problem} onClick={this.showAckDialog} />
)} )}
</div> </div>
@@ -174,7 +175,7 @@ export default class AlertCard extends PureComponent<AlertCardProps, AlertCardSt
} }
interface AlertHostProps { interface AlertHostProps {
problem: ZBXTrigger; problem: ProblemDTO;
panelOptions: ProblemsPanelOptions; panelOptions: ProblemsPanelOptions;
} }
@@ -200,7 +201,7 @@ function AlertHost(props: AlertHostProps) {
} }
interface AlertGroupProps { interface AlertGroupProps {
problem: ZBXTrigger; problem: ProblemDTO;
panelOptions: ProblemsPanelOptions; panelOptions: ProblemsPanelOptions;
} }
@@ -253,7 +254,7 @@ function AlertSeverity(props) {
} }
interface AlertAcknowledgesButtonProps { interface AlertAcknowledgesButtonProps {
problem: ZBXTrigger; problem: ProblemDTO;
onClick: (event?) => void; onClick: (event?) => void;
} }

View File

@@ -1,23 +1,24 @@
import React, { PureComponent, CSSProperties } from 'react'; import React, { PureComponent, CSSProperties } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ProblemsPanelOptions, ZBXTrigger, GFTimeRange, ZBXTag } from '../../types'; import { ProblemsPanelOptions, GFTimeRange } from '../../types';
import { AckProblemData } from '.././Modal'; import { AckProblemData } from '.././Modal';
import AlertCard from './AlertCard'; import AlertCard from './AlertCard';
import { ProblemDTO, ZBXTag } from '../../../datasource-zabbix/types';
export interface AlertListProps { export interface AlertListProps {
problems: ZBXTrigger[]; problems: ProblemDTO[];
panelOptions: ProblemsPanelOptions; panelOptions: ProblemsPanelOptions;
loading?: boolean; loading?: boolean;
timeRange?: GFTimeRange; timeRange?: GFTimeRange;
pageSize?: number; pageSize?: number;
fontSize?: number; fontSize?: number;
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => void; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void;
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
} }
interface AlertListState { interface AlertListState {
page: number; page: number;
currentProblems: ZBXTrigger[]; currentProblems: ProblemDTO[];
} }
export default class AlertList extends PureComponent<AlertListProps, AlertListState> { export default class AlertList extends PureComponent<AlertListProps, AlertListState> {
@@ -51,7 +52,7 @@ export default class AlertList extends PureComponent<AlertListProps, AlertListSt
} }
} }
handleProblemAck = (problem: ZBXTrigger, data: AckProblemData) => { handleProblemAck = (problem: ProblemDTO, data: AckProblemData) => {
return this.props.onProblemAck(problem, data); return this.props.onProblemAck(problem, data);
} }

View File

@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import moment from 'moment'; import moment from 'moment';
import * as utils from '../../../datasource-zabbix/utils'; import * as utils from '../../../datasource-zabbix/utils';
import { ZBXTrigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup, ZBXEvent, GFTimeRange, RTRow, ZBXTag, ZBXAlert } from '../../types'; import { ZBXItem, ZBXAcknowledge, GFTimeRange, RTRow } from '../../types';
import { Modal, AckProblemData } from '../Modal'; import { Modal, AckProblemData } from '../Modal';
import EventTag from '../EventTag'; import EventTag from '../EventTag';
import Tooltip from '../Tooltip/Tooltip'; import Tooltip from '../Tooltip/Tooltip';
@@ -9,14 +9,15 @@ import ProblemStatusBar from './ProblemStatusBar';
import AcknowledgesList from './AcknowledgesList'; import AcknowledgesList from './AcknowledgesList';
import ProblemTimeline from './ProblemTimeline'; import ProblemTimeline from './ProblemTimeline';
import FAIcon from '../FAIcon'; import FAIcon from '../FAIcon';
import { ProblemDTO, ZBXHost, ZBXGroup, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types';
interface ProblemDetailsProps extends RTRow<ZBXTrigger> { interface ProblemDetailsProps extends RTRow<ProblemDTO> {
rootWidth: number; rootWidth: number;
timeRange: GFTimeRange; timeRange: GFTimeRange;
showTimeline?: boolean; showTimeline?: boolean;
getProblemEvents: (problem: ZBXTrigger) => Promise<ZBXEvent[]>; getProblemEvents: (problem: ProblemDTO) => Promise<ZBXEvent[]>;
getProblemAlerts: (problem: ZBXTrigger) => Promise<ZBXAlert[]>; getProblemAlerts: (problem: ProblemDTO) => Promise<ZBXAlert[]>;
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => Promise<any> | any; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => Promise<any> | any;
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
} }
@@ -71,7 +72,7 @@ export default class ProblemDetails extends PureComponent<ProblemDetailsProps, P
} }
ackProblem = (data: AckProblemData) => { ackProblem = (data: AckProblemData) => {
const problem = this.props.original as ZBXTrigger; const problem = this.props.original as ProblemDTO;
return this.props.onProblemAck(problem, data).then(result => { return this.props.onProblemAck(problem, data).then(result => {
this.closeAckDialog(); this.closeAckDialog();
}).catch(err => { }).catch(err => {
@@ -89,13 +90,13 @@ export default class ProblemDetails extends PureComponent<ProblemDetailsProps, P
} }
render() { render() {
const problem = this.props.original as ZBXTrigger; const problem = this.props.original as ProblemDTO;
const alerts = this.state.alerts; const alerts = this.state.alerts;
const rootWidth = this.props.rootWidth; const rootWidth = this.props.rootWidth;
const displayClass = this.state.show ? 'show' : ''; const displayClass = this.state.show ? 'show' : '';
const wideLayout = rootWidth > 1200; const wideLayout = rootWidth > 1200;
const compactStatusBar = rootWidth < 800 || problem.acknowledges && wideLayout && rootWidth < 1400; const compactStatusBar = rootWidth < 800 || problem.acknowledges && wideLayout && rootWidth < 1400;
const age = moment.unix(problem.lastchangeUnix).fromNow(true); const age = moment.unix(problem.timestamp).fromNow(true);
const showAcknowledges = problem.acknowledges && problem.acknowledges.length !== 0; const showAcknowledges = problem.acknowledges && problem.acknowledges.length !== 0;
return ( return (

View File

@@ -5,22 +5,23 @@ import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import * as utils from '../../../datasource-zabbix/utils'; import * as utils from '../../../datasource-zabbix/utils';
import { isNewProblem } from '../../utils'; import { isNewProblem } from '../../utils';
import { ProblemsPanelOptions, ZBXTrigger, ZBXEvent, GFTimeRange, RTCell, ZBXTag, TriggerSeverity, RTResized, ZBXAlert } from '../../types';
import EventTag from '../EventTag'; import EventTag from '../EventTag';
import ProblemDetails from './ProblemDetails'; import ProblemDetails from './ProblemDetails';
import { AckProblemData } from '../Modal'; import { AckProblemData } from '../Modal';
import GFHeartIcon from '../GFHeartIcon'; import GFHeartIcon from '../GFHeartIcon';
import { ProblemsPanelOptions, GFTimeRange, RTCell, TriggerSeverity, RTResized } from '../../types';
import { ProblemDTO, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types';
export interface ProblemListProps { export interface ProblemListProps {
problems: ZBXTrigger[]; problems: ProblemDTO[];
panelOptions: ProblemsPanelOptions; panelOptions: ProblemsPanelOptions;
loading?: boolean; loading?: boolean;
timeRange?: GFTimeRange; timeRange?: GFTimeRange;
pageSize?: number; pageSize?: number;
fontSize?: number; fontSize?: number;
getProblemEvents: (problem: ZBXTrigger) => Promise<ZBXEvent[]>; getProblemEvents: (problem: ProblemDTO) => Promise<ZBXEvent[]>;
getProblemAlerts: (problem: ZBXTrigger) => Promise<ZBXAlert[]>; getProblemAlerts: (problem: ProblemDTO) => Promise<ZBXAlert[]>;
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => void; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void;
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
onPageSizeChange?: (pageSize: number, pageIndex: number) => void; onPageSizeChange?: (pageSize: number, pageIndex: number) => void;
onColumnResize?: (newResized: RTResized) => void; onColumnResize?: (newResized: RTResized) => void;
@@ -47,7 +48,7 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
this.rootRef = ref; this.rootRef = ref;
} }
handleProblemAck = (problem: ZBXTrigger, data: AckProblemData) => { handleProblemAck = (problem: ProblemDTO, data: AckProblemData) => {
return this.props.onProblemAck(problem, data); return this.props.onProblemAck(problem, data);
} }
@@ -177,20 +178,21 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
} }
} }
function SeverityCell(props: RTCell<ZBXTrigger>, problemSeverityDesc: TriggerSeverity[], markAckEvents?: boolean, ackEventColor?: string) { function SeverityCell(props: RTCell<ProblemDTO>, problemSeverityDesc: TriggerSeverity[], markAckEvents?: boolean, ackEventColor?: string) {
const problem = props.original; const problem = props.original;
let color: string; let color: string;
let severityDesc: TriggerSeverity; let severityDesc: TriggerSeverity;
severityDesc = _.find(problemSeverityDesc, s => s.priority === Number(problem.priority)); const severity = Number(problem.severity);
if (problem.lastEvent?.severity && problem.value === '1') { severityDesc = _.find(problemSeverityDesc, s => s.priority === severity);
severityDesc = _.find(problemSeverityDesc, s => s.priority === Number(problem.lastEvent.severity)); if (problem.severity && problem.value === '1') {
severityDesc = _.find(problemSeverityDesc, s => s.priority === severity);
} }
color = severityDesc.color; color = severityDesc.color;
// Mark acknowledged triggers with different color // Mark acknowledged triggers with different color
if (markAckEvents && problem.lastEvent?.acknowledged === "1") { if (markAckEvents && problem.acknowledged === "1") {
color = ackEventColor; color = ackEventColor;
} }
@@ -204,7 +206,7 @@ function SeverityCell(props: RTCell<ZBXTrigger>, problemSeverityDesc: TriggerSev
const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)'; const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)';
const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)'; const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)';
function StatusCell(props: RTCell<ZBXTrigger>, okColor = DEFAULT_OK_COLOR, problemColor = DEFAULT_PROBLEM_COLOR, highlightNewerThan?: string) { function StatusCell(props: RTCell<ProblemDTO>, okColor = DEFAULT_OK_COLOR, problemColor = DEFAULT_PROBLEM_COLOR, highlightNewerThan?: string) {
const status = props.value === '0' ? 'RESOLVED' : 'PROBLEM'; const status = props.value === '0' ? 'RESOLVED' : 'PROBLEM';
const color = props.value === '0' ? okColor : problemColor; const color = props.value === '0' ? okColor : problemColor;
let newProblem = false; let newProblem = false;
@@ -216,7 +218,7 @@ function StatusCell(props: RTCell<ZBXTrigger>, okColor = DEFAULT_OK_COLOR, probl
); );
} }
function StatusIconCell(props: RTCell<ZBXTrigger>, highlightNewerThan?: string) { function StatusIconCell(props: RTCell<ProblemDTO>, highlightNewerThan?: string) {
const status = props.value === '0' ? 'ok' : 'problem'; const status = props.value === '0' ? 'ok' : 'problem';
let newProblem = false; let newProblem = false;
if (highlightNewerThan) { if (highlightNewerThan) {
@@ -230,7 +232,7 @@ function StatusIconCell(props: RTCell<ZBXTrigger>, highlightNewerThan?: string)
return <GFHeartIcon status={status} className={className} />; return <GFHeartIcon status={status} className={className} />;
} }
function GroupCell(props: RTCell<ZBXTrigger>) { function GroupCell(props: RTCell<ProblemDTO>) {
let groups = ""; let groups = "";
if (props.value && props.value.length) { if (props.value && props.value.length) {
groups = props.value.map(g => g.name).join(', '); groups = props.value.map(g => g.name).join(', ');
@@ -240,7 +242,7 @@ function GroupCell(props: RTCell<ZBXTrigger>) {
); );
} }
function ProblemCell(props: RTCell<ZBXTrigger>) { function ProblemCell(props: RTCell<ProblemDTO>) {
const comments = props.original.comments; const comments = props.original.comments;
return ( return (
<div> <div>
@@ -250,23 +252,23 @@ function ProblemCell(props: RTCell<ZBXTrigger>) {
); );
} }
function AgeCell(props: RTCell<ZBXTrigger>) { function AgeCell(props: RTCell<ProblemDTO>) {
const problem = props.original; const problem = props.original;
const timestamp = moment.unix(problem.lastchangeUnix); const timestamp = moment.unix(problem.timestamp);
const age = timestamp.fromNow(true); const age = timestamp.fromNow(true);
return <span>{age}</span>; return <span>{age}</span>;
} }
function LastChangeCell(props: RTCell<ZBXTrigger>, customFormat?: string) { function LastChangeCell(props: RTCell<ProblemDTO>, customFormat?: string) {
const DEFAULT_TIME_FORMAT = "DD MMM YYYY HH:mm:ss"; const DEFAULT_TIME_FORMAT = "DD MMM YYYY HH:mm:ss";
const problem = props.original; const problem = props.original;
const timestamp = moment.unix(problem.lastchangeUnix); const timestamp = moment.unix(problem.timestamp);
const format = customFormat || DEFAULT_TIME_FORMAT; const format = customFormat || DEFAULT_TIME_FORMAT;
const lastchange = timestamp.format(format); const lastchange = timestamp.format(format);
return <span>{lastchange}</span>; return <span>{lastchange}</span>;
} }
interface TagCellProps extends RTCell<ZBXTrigger> { interface TagCellProps extends RTCell<ProblemDTO> {
onTagClick: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; onTagClick: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
} }

View File

@@ -10,6 +10,7 @@ import { triggerPanelOptionsTab } from './options_tab';
import { migratePanelSchema, CURRENT_SCHEMA_VERSION } from './migrations'; import { migratePanelSchema, CURRENT_SCHEMA_VERSION } from './migrations';
import ProblemList from './components/Problems/Problems'; import ProblemList from './components/Problems/Problems';
import AlertList from './components/AlertList/AlertList'; import AlertList from './components/AlertList/AlertList';
import { ProblemDTO } from 'datasource-zabbix/types';
const PROBLEM_EVENTS_LIMIT = 100; const PROBLEM_EVENTS_LIMIT = 100;
@@ -174,7 +175,7 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
let triggers = _.cloneDeep(problems); let triggers = _.cloneDeep(problems);
triggers = _.map(triggers, this.formatTrigger.bind(this)); triggers = _.map(triggers, this.formatTrigger.bind(this));
triggers = this.filterTriggersPost(triggers); triggers = this.filterProblems(triggers);
triggers = this.sortTriggers(triggers); triggers = this.sortTriggers(triggers);
this.renderData = triggers; this.renderData = triggers;
@@ -184,30 +185,30 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
}); });
} }
filterTriggersPost(triggers) { filterProblems(problems) {
let triggerList = _.cloneDeep(triggers); let problemsList = _.cloneDeep(problems);
// Filter acknowledged triggers // Filter acknowledged triggers
if (this.panel.showTriggers === 'unacknowledged') { if (this.panel.showTriggers === 'unacknowledged') {
triggerList = _.filter(triggerList, trigger => { problemsList = _.filter(problemsList, trigger => {
return !(trigger.acknowledges && trigger.acknowledges.length); return !(trigger.acknowledges && trigger.acknowledges.length);
}); });
} else if (this.panel.showTriggers === 'acknowledged') { } else if (this.panel.showTriggers === 'acknowledged') {
triggerList = _.filter(triggerList, trigger => { problemsList = _.filter(problemsList, trigger => {
return trigger.acknowledges && trigger.acknowledges.length; return trigger.acknowledges && trigger.acknowledges.length;
}); });
} }
// Filter triggers by severity // Filter triggers by severity
triggerList = _.filter(triggerList, trigger => { problemsList = _.filter(problemsList, problem => {
if (trigger.lastEvent && trigger.lastEvent.severity) { if (problem.severity) {
return this.panel.triggerSeverity[trigger.lastEvent.severity].show; return this.panel.triggerSeverity[problem.severity].show;
} else { } else {
return this.panel.triggerSeverity[trigger.priority].show; return this.panel.triggerSeverity[problem.priority].show;
} }
}); });
return triggerList; return problemsList;
} }
sortTriggers(triggerList) { sortTriggers(triggerList) {
@@ -303,11 +304,11 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
}); });
} }
getProblemAlerts(problem) { getProblemAlerts(problem: ProblemDTO) {
if (!problem.lastEvent || problem.lastEvent.length === 0) { if (!problem.eventid) {
return Promise.resolve([]); return Promise.resolve([]);
} }
const eventids = [problem.lastEvent.eventid]; const eventids = [problem.eventid];
return getDataSourceSrv().get(problem.datasource) return getDataSourceSrv().get(problem.datasource)
.then((datasource: any) => { .then((datasource: any) => {
return datasource.zabbix.getEventAlerts(eventids); return datasource.zabbix.getEventAlerts(eventids);
@@ -327,11 +328,11 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
this.render(); this.render();
} }
acknowledgeTrigger(trigger, message) { acknowledgeProblem(problem: ProblemDTO, message) {
const eventid = trigger.lastEvent ? trigger.lastEvent.eventid : null; const eventid = problem.eventid;
const grafana_user = this.contextSrv.user.name; const grafana_user = this.contextSrv.user.name;
const ack_message = grafana_user + ' (Grafana): ' + message; const ack_message = grafana_user + ' (Grafana): ' + message;
return getDataSourceSrv().get(trigger.datasource) return getDataSourceSrv().get(problem.datasource)
.then((datasource: any) => { .then((datasource: any) => {
const userIsEditor = this.contextSrv.isEditor || this.contextSrv.isGrafanaAdmin; const userIsEditor = this.contextSrv.isEditor || this.contextSrv.isGrafanaAdmin;
if (datasource.disableReadOnlyUsersAck && !userIsEditor) { if (datasource.disableReadOnlyUsersAck && !userIsEditor) {
@@ -399,7 +400,7 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
onColumnResize: ctrl.handleColumnResize.bind(ctrl), onColumnResize: ctrl.handleColumnResize.bind(ctrl),
onProblemAck: (trigger, data) => { onProblemAck: (trigger, data) => {
const message = data.message; const message = data.message;
return ctrl.acknowledgeTrigger(trigger, message); return ctrl.acknowledgeProblem(trigger, message);
}, },
onTagClick: (tag, datasource, ctrlKey, shiftKey) => { onTagClick: (tag, datasource, ctrlKey, shiftKey) => {
if (ctrlKey || shiftKey) { if (ctrlKey || shiftKey) {