diff --git a/src/panel-triggers/components/ProblemTimeline.tsx b/src/panel-triggers/components/ProblemTimeline.tsx new file mode 100644 index 0000000..de2b5d4 --- /dev/null +++ b/src/panel-triggers/components/ProblemTimeline.tsx @@ -0,0 +1,105 @@ +import React, { PureComponent } from 'react'; +import { GFTimeRange, ZBXEvent } from 'panel-triggers/types'; + +const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)'; +const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)'; +const EVENT_ITEM_SIZE = 16; + +export interface ProblemTimelineProps { + events: ZBXEvent[]; + timeRange: GFTimeRange; + okColor?: string; + problemColor?: string; +} + +interface ProblemTimelineState { + width: number; +} + +export default class ProblemTimeline extends PureComponent { + rootWidth: number; + rootRef: any; + + static defaultProps = { + okColor: DEFAULT_OK_COLOR, + problemColor: DEFAULT_PROBLEM_COLOR, + }; + + constructor(props) { + super(props); + this.state = { + width: 0 + }; + } + + setRootRef = ref => { + this.rootRef = ref; + const width = this.rootRef && this.rootRef.clientWidth || 0; + this.setState({ width }); + } + + render() { + if (!this.rootRef) { + return
; + } + const { events, timeRange } = this.props; + const { timeFrom, timeTo } = timeRange; + const range = timeTo - timeFrom; + const width = this.state.width; + + let firstItem; + if (events.length) { + const firstTs = events.length ? Number(events[0].clock) : timeTo; + const duration = (firstTs - timeFrom) / range; + const firstEventColor = events[0].value !== '1' ? this.props.problemColor : this.props.okColor; + const firstEventStyle: React.CSSProperties = { + width: duration * width, + left: 0, + background: firstEventColor, + }; + firstItem = ( +
+ ); + } + + const eventsIntervalItems = events.map((event, index) => { + const ts = Number(event.clock); + const nextTs = index < events.length - 1 ? Number(events[index + 1].clock) : timeTo; + const duration = (nextTs - ts) / range; + const posLeft = (ts - timeFrom) / range * width; + const eventColor = event.value === '1' ? this.props.problemColor : this.props.okColor; + const styles: React.CSSProperties = { + width: duration * width, + left: posLeft, + background: eventColor, + }; + + return ( +
+ ); + }); + + const eventsItems = events.map(event => { + const ts = Number(event.clock); + const posLeft = (ts - timeFrom) / range * width - EVENT_ITEM_SIZE / 2; + const eventColor = event.value === '1' ? this.props.problemColor : this.props.okColor; + const styles: React.CSSProperties = { + transform: `translate(${posLeft}px, -2px)`, + // background: eventColor, + borderColor: eventColor, + }; + + return ( +
+ ); + }); + + return ( +
+ {firstItem} + {eventsIntervalItems} + {eventsItems} +
+ ); + } +} diff --git a/src/panel-triggers/components/Problems.tsx b/src/panel-triggers/components/Problems.tsx index 03dc33c..0768e18 100644 --- a/src/panel-triggers/components/Problems.tsx +++ b/src/panel-triggers/components/Problems.tsx @@ -1,15 +1,18 @@ import React, { PureComponent } from 'react'; import ReactTable from 'react-table'; +import * as utils from '../../datasource-zabbix/utils'; +import { ProblemsPanelOptions, Trigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup, ZBXEvent, GFTimeRange } from '../types'; +import { Modal, AckProblemData } from './Modal'; import EventTag from './EventTag'; import Tooltip from './Tooltip'; -import { Modal, AckProblemData } from './Modal'; -import { ProblemsPanelOptions, Trigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup } from '../types'; -import * as utils from '../../datasource-zabbix/utils'; +import ProblemTimeline from './ProblemTimeline'; export interface ProblemListProps { problems: Trigger[]; panelOptions: ProblemsPanelOptions; loading?: boolean; + timeRange?: GFTimeRange; + getProblemEvents: (ids: string[]) => ZBXEvent[]; } interface ProblemListState { @@ -93,7 +96,13 @@ export class ProblemList extends PureComponent } + SubComponent={props => + + } expanded={this.getExpandedPage(this.state.page)} onExpandedChange={this.handleExpandedChange} onPageChange={page => this.setState({ page })} @@ -352,6 +361,7 @@ class ProblemActionButton extends PureComponent { } interface ProblemDetailsState { + events: ZBXEvent[]; show: boolean; showAckDialog: boolean; } @@ -360,12 +370,19 @@ class ProblemDetails extends PureComponent { constructor(props) { super(props); this.state = { + events: [], show: false, showAckDialog: false, }; } componentDidMount() { + const problem = this.props.original; + this.props.getProblemEvents(problem) + .then(events => { + console.log(events, this.props.timeRange); + this.setState({ events }); + }); requestAnimationFrame(() => { this.setState({ show: true }); }); @@ -417,11 +434,14 @@ class ProblemDetails extends PureComponent { {problem.comments}
} -
- {problem.tags && problem.tags.map(tag => - ) - } -
+ {problem.tags && problem.tags.length && +
+ {problem.tags && problem.tags.map(tag => + ) + } +
+ } + {problem.acknowledges && !wideLayout &&
Acknowledges
diff --git a/src/panel-triggers/triggers_panel_ctrl.js b/src/panel-triggers/triggers_panel_ctrl.js index e1b348a..34ab56f 100644 --- a/src/panel-triggers/triggers_panel_ctrl.js +++ b/src/panel-triggers/triggers_panel_ctrl.js @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment'; +import * as dateMath from 'grafana/app/core/utils/datemath'; import * as utils from '../datasource-zabbix/utils'; import {PanelCtrl} from 'grafana/app/plugins/sdk'; import {triggerPanelOptionsTab} from './options_tab'; @@ -74,12 +75,13 @@ const triggerStatusMap = { export class TriggerPanelCtrl extends PanelCtrl { /** @ngInject */ - constructor($scope, $injector, $timeout, datasourceSrv, templateSrv, contextSrv, dashboardSrv) { + constructor($scope, $injector, $timeout, datasourceSrv, templateSrv, contextSrv, dashboardSrv, timeSrv) { super($scope, $injector); this.datasourceSrv = datasourceSrv; this.templateSrv = templateSrv; this.contextSrv = contextSrv; this.dashboardSrv = dashboardSrv; + this.timeSrv = timeSrv; this.scope = $scope; this.$timeout = $timeout; @@ -90,6 +92,7 @@ export class TriggerPanelCtrl extends PanelCtrl { this.triggerList = []; this.currentTriggersPage = []; this.datasources = {}; + this.range = {}; this.panel = migratePanelSchema(this.panel); _.defaultsDeep(this.panel, _.cloneDeep(PANEL_DEFAULTS)); @@ -159,6 +162,8 @@ export class TriggerPanelCtrl extends PanelCtrl { // ignore fetching data if another panel is in fullscreen if (this.otherPanelInFullscreenMode()) { return; } + this.range = this.timeSrv.timeRange(); + // clear loading/error state delete this.error; this.loading = true; @@ -518,6 +523,16 @@ export class TriggerPanelCtrl extends PanelCtrl { }); } + getProblemEvents(trigger) { + const triggerids = [trigger.triggerid]; + const timeFrom = Math.ceil(dateMath.parse(this.range.from) / 1000); + const timeTo = Math.ceil(dateMath.parse(this.range.to) / 1000); + return this.datasourceSrv.get(trigger.datasource) + .then(datasource => { + return datasource.zabbix.getEvents(triggerids, timeFrom, timeTo, [0, 1]); + }); + } + getCurrentTriggersPage() { let pageSize = this.panel.pageSize || PANEL_DEFAULTS.pageSize; let startPos = this.pageIndex * pageSize; @@ -705,6 +720,9 @@ export class TriggerPanelCtrl extends PanelCtrl { function renderProblems() { console.debug('rendering ProblemsList React component'); // console.log(ctrl); + const timeFrom = Math.ceil(dateMath.parse(ctrl.range.from) / 1000); + const timeTo = Math.ceil(dateMath.parse(ctrl.range.to) / 1000); + let panelOptions = {}; for (let prop in PANEL_DEFAULTS) { panelOptions[prop] = ctrl.panel[prop]; @@ -712,6 +730,8 @@ export class TriggerPanelCtrl extends PanelCtrl { const problemsListProps = { problems: ctrl.triggerList, panelOptions, + timeRange: { timeFrom, timeTo }, + getProblemEvents: ctrl.getProblemEvents.bind(ctrl), }; const problemsReactElem = React.createElement(ProblemList, problemsListProps); ReactDOM.render(problemsReactElem, elem.find('.panel-content')[0]); diff --git a/src/panel-triggers/types.ts b/src/panel-triggers/types.ts index 5392f0e..3abbb30 100644 --- a/src/panel-triggers/types.ts +++ b/src/panel-triggers/types.ts @@ -132,6 +132,8 @@ export interface ZBXEvent { object?: string; objectid?: string; acknowledged?: string; + hosts?: ZBXHost[]; + acknowledges?: ZBXAcknowledge[]; } export interface ZBXTag { @@ -158,3 +160,8 @@ export interface ZBXAlert { message: string; error: string; } + +export interface GFTimeRange { + timeFrom: number; + timeTo: number; +} diff --git a/src/sass/_panel-problems.scss b/src/sass/_panel-problems.scss index a88a6b8..7c82672 100644 --- a/src/sass/_panel-problems.scss +++ b/src/sass/_panel-problems.scss @@ -336,6 +336,42 @@ animation: blink-shadow 2s ease-out infinite; } } + + .event-timeline { + display: flex; + position: relative; + margin: 1.6rem 0; + // margin-top: auto; + + .problem-event-interval { + height: 12px; + position: absolute; + // opacity: 0.7; + + &:hover { + // opacity: 1; + border: 1px solid $blue; + box-shadow: 0px 0px 5px rgba($blue, 0.5); + } + } + + .problem-event-item { + position: absolute; + width: 16px; + height: 16px; + // transform: translate(0px, -2px); + background: $problem-statusbar-background; + border: 4px solid $blue; + border-radius: 16px; + z-index: 10; + + &:hover { + box-shadow: 0px 0px 6px 1px rgba($orange, 1); + background-color: $zbx-text-highlighted; + z-index: 11; + } + } + } } // .rt-tr-group {