import React, { PureComponent } from 'react'; import ReactTable from 'react-table-6'; import classNames from 'classnames'; import _ from 'lodash'; import moment from 'moment'; import { isNewProblem } from '../../utils'; import EventTag from '../EventTag'; import ProblemDetails from './ProblemDetails'; import { AckProblemData } from '../AckModal'; import { GFHeartIcon, FAIcon } from '../../../components'; import { ProblemsPanelOptions, GFTimeRange, RTCell, TriggerSeverity, RTResized } from '../../types'; import { ProblemDTO, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types'; import { AckCell } from './AckCell'; export interface ProblemListProps { problems: ProblemDTO[]; panelOptions: ProblemsPanelOptions; loading?: boolean; timeRange?: GFTimeRange; pageSize?: number; fontSize?: number; panelId?: number; getProblemEvents: (problem: ProblemDTO) => Promise; getProblemAlerts: (problem: ProblemDTO) => Promise; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void; onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; onPageSizeChange?: (pageSize: number, pageIndex: number) => void; onColumnResize?: (newResized: RTResized) => void; } interface ProblemListState { expanded: any; page: number; } export default class ProblemList extends PureComponent { rootWidth: number; rootRef: any; constructor(props) { super(props); this.state = { expanded: {}, page: 0, }; } setRootRef = ref => { this.rootRef = ref; } handleProblemAck = (problem: ProblemDTO, data: AckProblemData) => { return this.props.onProblemAck(problem, data); } handlePageSizeChange = (pageSize, pageIndex) => { if (this.props.onPageSizeChange) { this.props.onPageSizeChange(pageSize, pageIndex); } } handleResizedChange = (newResized, event) => { if (this.props.onColumnResize) { this.props.onColumnResize(newResized); } } handleExpandedChange = expanded => { const nextExpanded = { ...this.state.expanded }; nextExpanded[this.state.page] = expanded; this.setState({ expanded: nextExpanded }); } handleTagClick = (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => { if (this.props.onTagClick) { this.props.onTagClick(tag, datasource, ctrlKey, shiftKey); } } getExpandedPage = (page: number) => { return this.state.expanded[page] || {}; } buildColumns() { const result = []; const options = this.props.panelOptions; const highlightNewerThan = options.highlightNewEvents && options.highlightNewerThan; const statusCell = props => StatusCell(props, highlightNewerThan); const statusIconCell = props => StatusIconCell(props, highlightNewerThan); const hostNameCell = props => ; const hostTechNameCell = props => ; const columns = [ { Header: 'Host', id: 'host', show: options.hostField, Cell: hostNameCell }, { Header: 'Host (Technical Name)', id: 'hostTechName', show: options.hostTechNameField, Cell: hostTechNameCell }, { Header: 'Host Groups', accessor: 'groups', show: options.hostGroups, Cell: GroupCell }, { Header: 'Proxy', accessor: 'proxy', show: options.hostProxy }, { Header: 'Severity', show: options.severityField, className: 'problem-severity', width: 120, accessor: problem => problem.priority, id: 'severity', Cell: props => SeverityCell(props, options.triggerSeverity, options.markAckEvents, options.ackEventColor, options.okEventColor), }, { Header: '', id: 'statusIcon', show: options.statusIcon, className: 'problem-status-icon', width: 50, accessor: 'value', Cell: statusIconCell, }, { Header: 'Status', accessor: 'value', show: options.statusField, width: 100, Cell: statusCell }, { Header: 'Problem', accessor: 'description', minWidth: 200, Cell: ProblemCell}, { Header: 'Ack', id: 'ack', show: options.ackField, width: 70, Cell: props => }, { Header: 'Tags', accessor: 'tags', show: options.showTags, className: 'problem-tags', Cell: props => }, { Header: 'Age', className: 'problem-age', width: 100, show: options.ageField, accessor: 'timestamp', id: 'age', Cell: AgeCell, }, { Header: 'Time', className: 'last-change', width: 150, accessor: 'timestamp', id: 'lastchange', Cell: props => LastChangeCell(props, options.customLastChangeFormat && options.lastChangeFormat), }, { Header: '', className: 'custom-expander', width: 60, expander: true, Expander: CustomExpander }, ]; for (const column of columns) { if (column.show || column.show === undefined) { delete column.show; result.push(column); } } return result; } render() { const columns = this.buildColumns(); this.rootWidth = this.rootRef && this.rootRef.clientWidth; const { pageSize, fontSize, panelOptions } = this.props; const panelClass = classNames('panel-problems', { [`font-size--${fontSize}`]: fontSize }); let pageSizeOptions = [5, 10, 20, 25, 50, 100]; if (pageSize) { pageSizeOptions.push(pageSize); pageSizeOptions = _.uniq(_.sortBy(pageSizeOptions)); } return (
} expanded={this.getExpandedPage(this.state.page)} onExpandedChange={this.handleExpandedChange} onPageChange={page => this.setState({ page })} onPageSizeChange={this.handlePageSizeChange} onResizedChange={this.handleResizedChange} />
); } } interface HostCellProps { name: string; maintenance: boolean; } const HostCell: React.FC = ({ name, maintenance }) => { return (
{name} {maintenance && }
); }; function SeverityCell( props: RTCell, problemSeverityDesc: TriggerSeverity[], markAckEvents?: boolean, ackEventColor?: string, okColor = DEFAULT_OK_COLOR ) { const problem = props.original; let color: string; let severityDesc: TriggerSeverity; const severity = Number(problem.severity); severityDesc = _.find(problemSeverityDesc, s => s.priority === severity); if (problem.severity && problem.value === '1') { severityDesc = _.find(problemSeverityDesc, s => s.priority === severity); } color = problem.value === '0' ? okColor : severityDesc.color; // Mark acknowledged triggers with different color if (markAckEvents && problem.acknowledged === "1") { color = ackEventColor; } return (
{severityDesc.severity}
); } const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)'; const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)'; function StatusCell(props: RTCell, highlightNewerThan?: string) { const status = props.value === '0' ? 'RESOLVED' : 'PROBLEM'; const color = props.value === '0' ? DEFAULT_OK_COLOR : DEFAULT_PROBLEM_COLOR; let newProblem = false; if (highlightNewerThan) { newProblem = isNewProblem(props.original, highlightNewerThan); } return ( {status} ); } function StatusIconCell(props: RTCell, highlightNewerThan?: string) { const status = props.value === '0' ? 'ok' : 'problem'; let newProblem = false; if (highlightNewerThan) { newProblem = isNewProblem(props.original, highlightNewerThan); } const className = classNames('zbx-problem-status-icon', { 'problem-status--new': newProblem }, { 'zbx-problem': props.value === '1' }, { 'zbx-ok': props.value === '0' }, ); return ; } function GroupCell(props: RTCell) { let groups = ""; if (props.value && props.value.length) { groups = props.value.map(g => g.name).join(', '); } return ( {groups} ); } function ProblemCell(props: RTCell) { const comments = props.original.comments; return (
{props.value} {/* {comments && } */}
); } function AgeCell(props: RTCell) { const problem = props.original; const timestamp = moment.unix(problem.timestamp); const age = timestamp.fromNow(true); return {age}; } function LastChangeCell(props: RTCell, customFormat?: string) { const DEFAULT_TIME_FORMAT = "DD MMM YYYY HH:mm:ss"; const problem = props.original; const timestamp = moment.unix(problem.timestamp); const format = customFormat || DEFAULT_TIME_FORMAT; const lastchange = timestamp.format(format); return {lastchange}; } interface TagCellProps extends RTCell { onTagClick: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; } class TagCell extends PureComponent { handleTagClick = (tag: ZBXTag, ctrlKey?: boolean, shiftKey?: boolean) => { if (this.props.onTagClick) { this.props.onTagClick(tag, this.props.original.datasource, ctrlKey, shiftKey); } } render() { const tags = this.props.value || []; return [ tags.map(tag => ) ]; } } function CustomExpander(props: RTCell) { return ( ); }