problems: implement alert list layout
This commit is contained in:
339
src/panel-triggers/components/AlertList/AlertCard.tsx
Normal file
339
src/panel-triggers/components/AlertList/AlertCard.tsx
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
import React, { PureComponent, CSSProperties } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { isNewProblem, formatLastChange } from '../../utils';
|
||||||
|
import { ProblemsPanelOptions, ZBXTrigger, ZBXTag } from '../../types';
|
||||||
|
import { AckProblemData, Modal } from '.././Modal';
|
||||||
|
import EventTag from '../EventTag';
|
||||||
|
import Tooltip from '.././Tooltip/Tooltip';
|
||||||
|
|
||||||
|
interface AlertCardProps {
|
||||||
|
problem: ZBXTrigger;
|
||||||
|
panelOptions: ProblemsPanelOptions;
|
||||||
|
onTagClick?: (tag: ZBXTag, datasource: string) => void;
|
||||||
|
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => Promise<any> | any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertCardState {
|
||||||
|
showAckDialog: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AlertCard extends PureComponent<AlertCardProps, AlertCardState> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { showAckDialog: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTagClick = (tag: ZBXTag) => {
|
||||||
|
if (this.props.onTagClick) {
|
||||||
|
this.props.onTagClick(tag, this.props.problem.datasource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ackProblem = (data: AckProblemData) => {
|
||||||
|
const problem = this.props.problem;
|
||||||
|
console.log('acknowledge: ', problem.lastEvent && problem.lastEvent.eventid, data);
|
||||||
|
return this.props.onProblemAck(problem, data).then(result => {
|
||||||
|
this.closeAckDialog();
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
this.closeAckDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showAckDialog = () => {
|
||||||
|
this.setState({ showAckDialog: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAckDialog = () => {
|
||||||
|
this.setState({ showAckDialog: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { problem, panelOptions } = this.props;
|
||||||
|
const cardClass = classNames('alert-rule-item', 'zbx-trigger-card', { 'zbx-trigger-highlighted': panelOptions.highlightBackground });
|
||||||
|
const severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === Number(problem.priority));
|
||||||
|
const lastchange = formatLastChange(problem.lastchangeUnix, panelOptions.customLastChangeFormat && panelOptions.lastChangeFormat);
|
||||||
|
const age = moment.unix(problem.lastchangeUnix).fromNow(true);
|
||||||
|
|
||||||
|
let newProblem = false;
|
||||||
|
if (panelOptions.highlightNewerThan) {
|
||||||
|
newProblem = isNewProblem(problem, panelOptions.highlightNewerThan);
|
||||||
|
}
|
||||||
|
const blink = panelOptions.highlightNewEvents && newProblem;
|
||||||
|
|
||||||
|
const cardStyle: CSSProperties = {};
|
||||||
|
if (panelOptions.highlightBackground) {
|
||||||
|
cardStyle.background = severityDesc.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={cardClass} style={cardStyle}>
|
||||||
|
<AlertIcon problem={problem} color={severityDesc.color} highlightBackground={panelOptions.highlightBackground} blink={blink} />
|
||||||
|
|
||||||
|
<div className="alert-rule-item__body">
|
||||||
|
<div className="alert-rule-item__header">
|
||||||
|
<p className="alert-rule-item__name">
|
||||||
|
<span className="zabbix-trigger-name">{problem.description}</span>
|
||||||
|
{(panelOptions.hostField || panelOptions.hostTechNameField) && (
|
||||||
|
<AlertHost problem={problem} panelOptions={panelOptions} />
|
||||||
|
)}
|
||||||
|
{panelOptions.hostGroups && <AlertGroup problem={problem} panelOptions={panelOptions} />}
|
||||||
|
|
||||||
|
{panelOptions.showTags && (
|
||||||
|
<span className="zbx-trigger-tags">
|
||||||
|
{problem.tags && problem.tags.map(tag =>
|
||||||
|
<EventTag
|
||||||
|
key={tag.tag + tag.value}
|
||||||
|
tag={tag}
|
||||||
|
highlight={tag.tag === problem.correlation_tag}
|
||||||
|
onClick={this.handleTagClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="alert-rule-item__text">
|
||||||
|
{panelOptions.statusField && <AlertStatus problem={problem} blink={blink} />}
|
||||||
|
{panelOptions.severityField && (
|
||||||
|
<AlertSeverity severityDesc={severityDesc} blink={blink} highlightBackground={panelOptions.highlightBackground} />
|
||||||
|
)}
|
||||||
|
<span className="alert-rule-item__time">
|
||||||
|
{panelOptions.ageField && "for " + age}
|
||||||
|
</span>
|
||||||
|
{panelOptions.descriptionField && !panelOptions.descriptionAtNewLine && (
|
||||||
|
<span className="zbx-description" dangerouslySetInnerHTML={{ __html: problem.comments }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{panelOptions.descriptionField && panelOptions.descriptionAtNewLine && (
|
||||||
|
<div className="alert-rule-item__text" >
|
||||||
|
<span
|
||||||
|
className="alert-rule-item__info zbx-description zbx-description--newline"
|
||||||
|
dangerouslySetInnerHTML={{ __html: problem.comments }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{panelOptions.datasources.length > 1 && (
|
||||||
|
<div className="alert-rule-item__time zabbix-trigger-source">
|
||||||
|
<span>
|
||||||
|
<i className="fa fa-database"></i>
|
||||||
|
{problem.datasource}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="alert-rule-item__time zbx-trigger-lastchange">
|
||||||
|
<span>{lastchange || "last change unknown"}</span>
|
||||||
|
<div className="trigger-info-block zbx-status-icons">
|
||||||
|
{problem.url && <a href={problem.url} target="_blank"><i className="fa fa-external-link"></i></a>}
|
||||||
|
{problem.state === '1' && (
|
||||||
|
<Tooltip placement="bottom" content={problem.error}>
|
||||||
|
<span><i className="fa fa-question-circle"></i></span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{problem.lastEvent && (
|
||||||
|
<AlertAcknowledgesButton problem={problem} onClick={this.showAckDialog} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal withBackdrop={true}
|
||||||
|
isOpen={this.state.showAckDialog}
|
||||||
|
onSubmit={this.ackProblem}
|
||||||
|
onClose={this.closeAckDialog} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertIconProps {
|
||||||
|
problem: ZBXTrigger;
|
||||||
|
color: string;
|
||||||
|
blink?: boolean;
|
||||||
|
highlightBackground?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertIcon(props: AlertIconProps) {
|
||||||
|
const { problem, color, blink, highlightBackground } = props;
|
||||||
|
const priority = Number(problem.priority);
|
||||||
|
let iconClass = '';
|
||||||
|
if (problem.value === '1') {
|
||||||
|
if (priority >= 3) {
|
||||||
|
iconClass = 'icon-gf-critical';
|
||||||
|
} else {
|
||||||
|
iconClass = 'icon-gf-warning';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iconClass = 'icon-gf-online';
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = classNames('icon-gf', iconClass, { 'zabbix-trigger--blinked': blink });
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
if (!highlightBackground) {
|
||||||
|
style.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="alert-rule-item__icon" style={style}>
|
||||||
|
<i className={className}></i>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertHostProps {
|
||||||
|
problem: ZBXTrigger;
|
||||||
|
panelOptions: ProblemsPanelOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertHost(props: AlertHostProps) {
|
||||||
|
const problem = props.problem;
|
||||||
|
const panel = props.panelOptions;
|
||||||
|
let host = "";
|
||||||
|
if (panel.hostField && panel.hostTechNameField) {
|
||||||
|
host = `${problem.host} (${problem.hostTechName})`;
|
||||||
|
} else if (panel.hostField || panel.hostTechNameField) {
|
||||||
|
host = panel.hostField ? problem.host : problem.hostTechName;
|
||||||
|
}
|
||||||
|
if (panel.hostProxy && problem.proxy) {
|
||||||
|
host = `${problem.proxy}: ${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="zabbix-hostname">
|
||||||
|
{problem.maintenance && <i className="fa fa-wrench zbx-maintenance-icon"></i>}
|
||||||
|
{host}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertGroupProps {
|
||||||
|
problem: ZBXTrigger;
|
||||||
|
panelOptions: ProblemsPanelOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertGroup(props: AlertGroupProps) {
|
||||||
|
const problem = props.problem;
|
||||||
|
const panel = props.panelOptions;
|
||||||
|
let groupNames = "";
|
||||||
|
if (panel.hostGroups) {
|
||||||
|
const groups = _.map(problem.groups, 'name').join(', ');
|
||||||
|
groupNames += `[ ${groups} ]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="zabbix-hostname">{groupNames}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)';
|
||||||
|
const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)';
|
||||||
|
|
||||||
|
function AlertStatus(props) {
|
||||||
|
const { problem, okColor, problemColor, blink } = props;
|
||||||
|
const status = problem.value === '0' ? 'RESOLVED' : 'PROBLEM';
|
||||||
|
const color = problem.value === '0' ? okColor || DEFAULT_OK_COLOR : problemColor || DEFAULT_PROBLEM_COLOR;
|
||||||
|
const className = classNames(
|
||||||
|
'zbx-trigger-state',
|
||||||
|
{ 'alert-state-critical': problem.value === '1' },
|
||||||
|
{ 'alert-state-ok': problem.value === '0' },
|
||||||
|
{ 'zabbix-trigger--blinked': blink }
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertSeverity(props) {
|
||||||
|
const { severityDesc, highlightBackground, blink } = props;
|
||||||
|
const className = classNames('zbx-trigger-severity', { 'zabbix-trigger--blinked': blink });
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
if (!highlightBackground) {
|
||||||
|
style.color = severityDesc.color;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className={className} style={style}>
|
||||||
|
{severityDesc.severity}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertAcknowledgesButtonProps {
|
||||||
|
problem: ZBXTrigger;
|
||||||
|
onClick: (event?) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlertAcknowledgesButton extends PureComponent<AlertAcknowledgesButtonProps> {
|
||||||
|
handleClick = (event) => {
|
||||||
|
this.props.onClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTooltipContent = () => {
|
||||||
|
return <AlertAcknowledges problem={this.props.problem} onClick={this.handleClick} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { problem } = this.props;
|
||||||
|
return (
|
||||||
|
problem.acknowledges && problem.acknowledges.length ?
|
||||||
|
<Tooltip placement="bottom" content={this.renderTooltipContent}>
|
||||||
|
<span><i className="fa fa-comments"></i></span>
|
||||||
|
</Tooltip> :
|
||||||
|
<Tooltip placement="bottom" content="Acknowledge problem">
|
||||||
|
<span role="button" onClick={this.handleClick}><i className="fa fa-comments-o"></i></span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertAcknowledgesProps {
|
||||||
|
problem: ZBXTrigger;
|
||||||
|
onClick: (event?) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlertAcknowledges extends PureComponent<AlertAcknowledgesProps> {
|
||||||
|
handleClick = (event) => {
|
||||||
|
this.props.onClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { problem } = this.props;
|
||||||
|
const ackRows = problem.acknowledges && problem.acknowledges.map(ack => {
|
||||||
|
return (
|
||||||
|
<tr key={ack.acknowledgeid}>
|
||||||
|
<td>{ack.time}</td>
|
||||||
|
<td>{ack.user}</td>
|
||||||
|
<td>{ack.message}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="ack-add-button">
|
||||||
|
<button id="add-acknowledge-btn" className="btn btn-mini btn-inverse gf-form-button" onClick={this.handleClick}>
|
||||||
|
<i className="fa fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="ack-time">Time</th>
|
||||||
|
<th className="ack-user">User</th>
|
||||||
|
<th className="ack-comments">Comments</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{ackRows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/panel-triggers/components/AlertList/AlertList.tsx
Normal file
134
src/panel-triggers/components/AlertList/AlertList.tsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import React, { PureComponent, CSSProperties } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ProblemsPanelOptions, ZBXTrigger, GFTimeRange, ZBXTag } from '../../types';
|
||||||
|
import { AckProblemData } from '.././Modal';
|
||||||
|
import AlertCard from './AlertCard';
|
||||||
|
|
||||||
|
export interface AlertListProps {
|
||||||
|
problems: ZBXTrigger[];
|
||||||
|
panelOptions: ProblemsPanelOptions;
|
||||||
|
loading?: boolean;
|
||||||
|
timeRange?: GFTimeRange;
|
||||||
|
pageSize?: number;
|
||||||
|
fontSize?: number;
|
||||||
|
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => void;
|
||||||
|
onTagClick?: (tag: ZBXTag, datasource: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertListState {
|
||||||
|
page: number;
|
||||||
|
currentProblems: ZBXTrigger[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AlertList extends PureComponent<AlertListProps, AlertListState> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
page: 0,
|
||||||
|
currentProblems: this.getCurrentProblems(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentProblems(page: number) {
|
||||||
|
const { pageSize, problems } = this.props;
|
||||||
|
const start = pageSize * page;
|
||||||
|
const end = Math.min(pageSize * (page + 1), problems.length);
|
||||||
|
return this.props.problems.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePageChange = (newPage: number) => {
|
||||||
|
const items = this.getCurrentProblems(newPage);
|
||||||
|
this.setState({
|
||||||
|
page: newPage,
|
||||||
|
currentProblems: items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleTagClick = (tag: ZBXTag, datasource: string) => {
|
||||||
|
if (this.props.onTagClick) {
|
||||||
|
this.props.onTagClick(tag, datasource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleProblemAck = (problem: ZBXTrigger, data: AckProblemData) => {
|
||||||
|
return this.props.onProblemAck(problem, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { problems, panelOptions } = this.props;
|
||||||
|
const currentProblems = this.getCurrentProblems(this.state.page);
|
||||||
|
|
||||||
|
return [
|
||||||
|
<div className="triggers-panel-container" key="alertListContainer">
|
||||||
|
<div className="triggers-panel-scroll">
|
||||||
|
<section className="card-section card-list-layout-list">
|
||||||
|
<ol className="alert-rule-list">
|
||||||
|
{currentProblems.map(problem =>
|
||||||
|
<AlertCard
|
||||||
|
key={problem.triggerid}
|
||||||
|
problem={problem}
|
||||||
|
panelOptions={panelOptions}
|
||||||
|
onTagClick={this.handleTagClick}
|
||||||
|
onProblemAck={this.handleProblemAck}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
<div className="triggers-panel-footer" key="alertListFooter">
|
||||||
|
<PaginationControl
|
||||||
|
itemsLength={problems.length}
|
||||||
|
pageSize={this.props.pageSize}
|
||||||
|
pageIndex={this.state.page}
|
||||||
|
onPageChange={this.handlePageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaginationControlProps {
|
||||||
|
itemsLength: number;
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
onPageChange: (index: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaginationControl extends PureComponent<PaginationControlProps> {
|
||||||
|
|
||||||
|
handlePageChange = (index: number) => () => {
|
||||||
|
this.props.onPageChange(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { itemsLength, pageIndex, pageSize } = this.props;
|
||||||
|
const pageCount = Math.ceil(itemsLength / pageSize);
|
||||||
|
if (pageCount === 1) {
|
||||||
|
return <ul></ul>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPage = Math.max(pageIndex - 3, 0);
|
||||||
|
const endPage = Math.min(pageCount, startPage + 9);
|
||||||
|
|
||||||
|
|
||||||
|
const pageLinks = [];
|
||||||
|
for (let i = startPage; i < endPage; i++) {
|
||||||
|
const pageLinkClass = classNames('triggers-panel-page-link', 'pointer', { 'active': i === pageIndex });
|
||||||
|
const value = i + 1;
|
||||||
|
const pageLinkElem = (
|
||||||
|
<li key={value.toString()}>
|
||||||
|
<a className={pageLinkClass} onClick={this.handlePageChange(i)}>{value}</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
pageLinks.push(pageLinkElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{pageLinks}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
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 { Trigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup, ZBXEvent, GFTimeRange, RTRow, ZBXTag } from '../types';
|
import { ZBXTrigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup, ZBXEvent, GFTimeRange, RTRow, ZBXTag } 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';
|
||||||
import ProblemTimeline from './ProblemTimeline';
|
import ProblemTimeline from './ProblemTimeline';
|
||||||
import FAIcon from './FAIcon';
|
import FAIcon from './FAIcon';
|
||||||
|
|
||||||
interface ProblemDetailsProps extends RTRow<Trigger> {
|
interface ProblemDetailsProps extends RTRow<ZBXTrigger> {
|
||||||
rootWidth: number;
|
rootWidth: number;
|
||||||
timeRange: GFTimeRange;
|
timeRange: GFTimeRange;
|
||||||
getProblemEvents: (problem: Trigger) => Promise<ZBXEvent[]>;
|
getProblemEvents: (problem: ZBXTrigger) => Promise<ZBXEvent[]>;
|
||||||
onProblemAck?: (problem: Trigger, data: AckProblemData) => Promise<any> | any;
|
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => Promise<any> | any;
|
||||||
onTagClick?: (tag: ZBXTag, datasource: string) => void;
|
onTagClick?: (tag: ZBXTag, datasource: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export default class ProblemDetails extends PureComponent<ProblemDetailsProps, P
|
|||||||
}
|
}
|
||||||
|
|
||||||
ackProblem = (data: AckProblemData) => {
|
ackProblem = (data: AckProblemData) => {
|
||||||
const problem = this.props.original as Trigger;
|
const problem = this.props.original as ZBXTrigger;
|
||||||
console.log('acknowledge: ', problem.lastEvent && problem.lastEvent.eventid, data);
|
console.log('acknowledge: ', problem.lastEvent && problem.lastEvent.eventid, data);
|
||||||
return this.props.onProblemAck(problem, data).then(result => {
|
return this.props.onProblemAck(problem, data).then(result => {
|
||||||
this.closeAckDialog();
|
this.closeAckDialog();
|
||||||
@@ -74,7 +74,7 @@ export default class ProblemDetails extends PureComponent<ProblemDetailsProps, P
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const problem = this.props.original as Trigger;
|
const problem = this.props.original as ZBXTrigger;
|
||||||
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;
|
||||||
@@ -246,7 +246,7 @@ class ProblemHosts extends PureComponent<ProblemHostsProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ProblemStatusBarProps {
|
interface ProblemStatusBarProps {
|
||||||
problem: Trigger;
|
problem: ZBXTrigger;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import classNames from 'classnames';
|
|||||||
import _ from 'lodash';
|
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 { ProblemsPanelOptions, Trigger, ZBXEvent, GFTimeRange, RTCell, ZBXTag, TriggerSeverity, RTResized } from '../types';
|
import { isNewProblem } from '../utils';
|
||||||
|
import { ProblemsPanelOptions, ZBXTrigger, ZBXEvent, GFTimeRange, RTCell, ZBXTag, TriggerSeverity, RTResized } 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';
|
||||||
|
|
||||||
export interface ProblemListProps {
|
export interface ProblemListProps {
|
||||||
problems: Trigger[];
|
problems: ZBXTrigger[];
|
||||||
panelOptions: ProblemsPanelOptions;
|
panelOptions: ProblemsPanelOptions;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
timeRange?: GFTimeRange;
|
timeRange?: GFTimeRange;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
getProblemEvents: (ids: string[]) => ZBXEvent[];
|
getProblemEvents: (ids: string[]) => ZBXEvent[];
|
||||||
onProblemAck?: (problem: Trigger, data: AckProblemData) => void;
|
onProblemAck?: (problem: ZBXTrigger, data: AckProblemData) => void;
|
||||||
onTagClick?: (tag: ZBXTag, datasource: string) => void;
|
onTagClick?: (tag: ZBXTag, datasource: string) => void;
|
||||||
onPageSizeChange?: (pageSize: number, pageIndex: number) => void;
|
onPageSizeChange?: (pageSize: number, pageIndex: number) => void;
|
||||||
onColumnResize?: (newResized: RTResized) => void;
|
onColumnResize?: (newResized: RTResized) => void;
|
||||||
@@ -45,7 +46,7 @@ export class ProblemList extends PureComponent<ProblemListProps, ProblemListStat
|
|||||||
this.rootRef = ref;
|
this.rootRef = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleProblemAck = (problem: Trigger, data: AckProblemData) => {
|
handleProblemAck = (problem: ZBXTrigger, data: AckProblemData) => {
|
||||||
return this.props.onProblemAck(problem, data);
|
return this.props.onProblemAck(problem, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ export class ProblemList extends PureComponent<ProblemListProps, ProblemListStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeverityCell(props: RTCell<Trigger>, problemSeverityDesc: TriggerSeverity[], markAckEvents?: boolean, ackEventColor?: string) {
|
function SeverityCell(props: RTCell<ZBXTrigger>, problemSeverityDesc: TriggerSeverity[], markAckEvents?: boolean, ackEventColor?: string) {
|
||||||
const problem = props.original;
|
const problem = props.original;
|
||||||
let color: string;
|
let color: string;
|
||||||
const severityDesc = _.find(problemSeverityDesc, s => s.priority === Number(props.original.priority));
|
const severityDesc = _.find(problemSeverityDesc, s => s.priority === Number(props.original.priority));
|
||||||
@@ -193,7 +194,7 @@ function SeverityCell(props: RTCell<Trigger>, problemSeverityDesc: TriggerSeveri
|
|||||||
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<Trigger>, okColor = DEFAULT_OK_COLOR, problemColor = DEFAULT_PROBLEM_COLOR, highlightNewerThan?: string) {
|
function StatusCell(props: RTCell<ZBXTrigger>, 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;
|
||||||
@@ -205,7 +206,7 @@ function StatusCell(props: RTCell<Trigger>, okColor = DEFAULT_OK_COLOR, problemC
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusIconCell(props: RTCell<Trigger>, highlightNewerThan?: string) {
|
function StatusIconCell(props: RTCell<ZBXTrigger>, 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) {
|
||||||
@@ -219,7 +220,7 @@ function StatusIconCell(props: RTCell<Trigger>, highlightNewerThan?: string) {
|
|||||||
return <GFHeartIcon status={status} className={className} />;
|
return <GFHeartIcon status={status} className={className} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function GroupCell(props: RTCell<Trigger>) {
|
function GroupCell(props: RTCell<ZBXTrigger>) {
|
||||||
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(', ');
|
||||||
@@ -229,7 +230,7 @@ function GroupCell(props: RTCell<Trigger>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProblemCell(props: RTCell<Trigger>) {
|
function ProblemCell(props: RTCell<ZBXTrigger>) {
|
||||||
const comments = props.original.comments;
|
const comments = props.original.comments;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -239,14 +240,14 @@ function ProblemCell(props: RTCell<Trigger>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AgeCell(props: RTCell<Trigger>) {
|
function AgeCell(props: RTCell<ZBXTrigger>) {
|
||||||
const problem = props.original;
|
const problem = props.original;
|
||||||
const timestamp = moment.unix(problem.lastchangeUnix);
|
const timestamp = moment.unix(problem.lastchangeUnix);
|
||||||
const age = timestamp.fromNow(true);
|
const age = timestamp.fromNow(true);
|
||||||
return <span>{age}</span>;
|
return <span>{age}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function LastChangeCell(props: RTCell<Trigger>, customFormat?: string) {
|
function LastChangeCell(props: RTCell<ZBXTrigger>, 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.lastchangeUnix);
|
||||||
@@ -255,7 +256,7 @@ function LastChangeCell(props: RTCell<Trigger>, customFormat?: string) {
|
|||||||
return <span>{lastchange}</span>;
|
return <span>{lastchange}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagCellProps extends RTCell<Trigger> {
|
interface TagCellProps extends RTCell<ZBXTrigger> {
|
||||||
onTagClick: (tag: ZBXTag, datasource: string) => void;
|
onTagClick: (tag: ZBXTag, datasource: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,13 +282,3 @@ function CustomExpander(props: RTCell<any>) {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNewProblem(problem: Trigger, highlightNewerThan: string): boolean {
|
|
||||||
try {
|
|
||||||
const highlightIntervalMs = utils.parseInterval(highlightNewerThan);
|
|
||||||
const durationSec = (Date.now() - problem.lastchangeUnix * 1000);
|
|
||||||
return durationSec < highlightIntervalMs;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import $ from 'jquery';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as dateMath from 'grafana/app/core/utils/datemath';
|
import * as dateMath from 'grafana/app/core/utils/datemath';
|
||||||
import * as utils from '../datasource-zabbix/utils';
|
import * as utils from '../datasource-zabbix/utils';
|
||||||
import {PanelCtrl} from 'grafana/app/plugins/sdk';
|
import { PanelCtrl } from 'grafana/app/plugins/sdk';
|
||||||
import {triggerPanelOptionsTab} from './options_tab';
|
import { triggerPanelOptionsTab } from './options_tab';
|
||||||
import {triggerPanelTriggersTab} from './triggers_tab';
|
import { triggerPanelTriggersTab } from './triggers_tab';
|
||||||
import {migratePanelSchema, CURRENT_SCHEMA_VERSION} from './migrations';
|
import { migratePanelSchema, CURRENT_SCHEMA_VERSION } from './migrations';
|
||||||
import { ProblemList } from './components/Problems';
|
import { ProblemList } from './components/Problems';
|
||||||
|
import AlertList from './components/AlertList/AlertList';
|
||||||
|
|
||||||
const ZABBIX_DS_ID = 'alexanderzobnin-zabbix-datasource';
|
const ZABBIX_DS_ID = 'alexanderzobnin-zabbix-datasource';
|
||||||
const PROBLEM_EVENTS_LIMIT = 100;
|
const PROBLEM_EVENTS_LIMIT = 100;
|
||||||
@@ -636,7 +637,8 @@ export class TriggerPanelCtrl extends PanelCtrl {
|
|||||||
ctrl.addTagFilter(tag, datasource);
|
ctrl.addTagFilter(tag, datasource);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const problemsReactElem = React.createElement(ProblemList, problemsListProps);
|
// const problemsReactElem = React.createElement(ProblemList, problemsListProps);
|
||||||
|
const problemsReactElem = React.createElement(AlertList, problemsListProps);
|
||||||
ReactDOM.render(problemsReactElem, elem.find('.panel-content')[0]);
|
ReactDOM.render(problemsReactElem, elem.find('.panel-content')[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export interface TriggerSeverity {
|
|||||||
|
|
||||||
export type TriggerColor = string;
|
export type TriggerColor = string;
|
||||||
|
|
||||||
export interface Trigger {
|
export interface ZBXTrigger {
|
||||||
acknowledges?: ZBXAcknowledge[];
|
acknowledges?: ZBXAcknowledge[];
|
||||||
alerts?: ZBXAlert[];
|
alerts?: ZBXAlert[];
|
||||||
age?: string;
|
age?: string;
|
||||||
@@ -104,6 +104,7 @@ export interface Trigger {
|
|||||||
status?: string;
|
status?: string;
|
||||||
tags?: ZBXTag[];
|
tags?: ZBXTag[];
|
||||||
templateid?: string;
|
templateid?: string;
|
||||||
|
triggerid?: string;
|
||||||
/** Whether the trigger can generate multiple problem events. */
|
/** Whether the trigger can generate multiple problem events. */
|
||||||
type?: string;
|
type?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|||||||
22
src/panel-triggers/utils.ts
Normal file
22
src/panel-triggers/utils.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import * as utils from '../datasource-zabbix/utils';
|
||||||
|
import { ZBXTrigger } from './types';
|
||||||
|
|
||||||
|
export function isNewProblem(problem: ZBXTrigger, highlightNewerThan: string): boolean {
|
||||||
|
try {
|
||||||
|
const highlightIntervalMs = utils.parseInterval(highlightNewerThan);
|
||||||
|
const durationSec = (Date.now() - problem.lastchangeUnix * 1000);
|
||||||
|
return durationSec < highlightIntervalMs;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TIME_FORMAT = "DD MMM YYYY HH:mm:ss";
|
||||||
|
|
||||||
|
export function formatLastChange(lastchangeUnix: number, customFormat?: string) {
|
||||||
|
const timestamp = moment.unix(lastchangeUnix);
|
||||||
|
const format = customFormat || DEFAULT_TIME_FORMAT;
|
||||||
|
const lastchange = timestamp.format(format);
|
||||||
|
return lastchange;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user