Migrate problems panel to React (#1532)

* Replace default angular app config editor

* Problems panel: migrate module to ts

* Problems panel options editor to react

* Problems panel react WIP

* Fix explore button

* Problems panel alert list layout WIP

* Refactor

* Minor tweaks on panel options

* remove outdated tests

* Update typescript

* Draft for tag event handling

* Remove unused files
This commit is contained in:
Alexander Zobnin
2022-11-30 14:01:21 +03:00
committed by GitHub
parent 504c9af226
commit 9b2079c1da
35 changed files with 1188 additions and 1630 deletions

View File

@@ -1,27 +1,29 @@
import React, { PureComponent } from 'react';
import { ZBXTrigger } from '../../types';
import { ProblemDTO } from '../../../datasource-zabbix/types';
interface AlertAcknowledgesProps {
problem: ZBXTrigger;
problem: ProblemDTO;
onClick: (event?) => void;
}
export default 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>
);
});
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 className="ack-tooltip">
<table className="table">
@@ -32,17 +34,19 @@ export default class AlertAcknowledges extends PureComponent<AlertAcknowledgesPr
<th className="ack-comments">Comments</th>
</tr>
</thead>
<tbody>
{ackRows}
</tbody>
<tbody>{ackRows}</tbody>
</table>
{problem.showAckButton &&
{problem.showAckButton && (
<div className="ack-add-button">
<button id="add-acknowledge-btn" className="btn btn-mini btn-inverse gf-form-button" onClick={this.handleClick}>
<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>
}
)}
</div>
);
}

View File

@@ -31,25 +31,32 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
ackProblem = (data: AckProblemData) => {
const problem = this.props.problem;
return this.props.onProblemAck(problem, data);
}
};
render() {
const { problem, panelOptions } = this.props;
const showDatasourceName = panelOptions.targets && panelOptions.targets.length > 1;
const cardClass = classNames('alert-rule-item', 'zbx-trigger-card', { 'zbx-trigger-highlighted': panelOptions.highlightBackground });
const descriptionClass = classNames('alert-rule-item__text', { 'zbx-description--newline': panelOptions.descriptionAtNewLine });
const cardClass = classNames('alert-rule-item', 'zbx-trigger-card', {
'zbx-trigger-highlighted': panelOptions.highlightBackground,
});
const descriptionClass = classNames('alert-rule-item__text', {
'zbx-description--newline': panelOptions.descriptionAtNewLine,
});
const problemSeverity = Number(problem.severity);
let severityDesc: TriggerSeverity;
severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === problemSeverity);
severityDesc = _.find(panelOptions.triggerSeverity, (s) => s.priority === problemSeverity);
if (problem.severity) {
severityDesc = _.find(panelOptions.triggerSeverity, s => s.priority === problemSeverity);
severityDesc = _.find(panelOptions.triggerSeverity, (s) => s.priority === problemSeverity);
}
const lastchange = formatLastChange(problem.timestamp, panelOptions.customLastChangeFormat && panelOptions.lastChangeFormat);
const lastchange = formatLastChange(
problem.timestamp,
panelOptions.customLastChangeFormat && panelOptions.lastChangeFormat
);
const age = moment.unix(problem.timestamp).fromNow(true);
let dsName: string = (problem.datasource as string);
let dsName: string = problem.datasource as string;
if ((problem.datasource as DataSourceRef)?.uid) {
const dsInstance = getDataSourceSrv().getInstanceSettings((problem.datasource as DataSourceRef).uid);
dsName = dsInstance.name;
@@ -64,7 +71,7 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
let problemColor: string;
if (problem.value === '0') {
problemColor = panelOptions.okEventColor;
} else if (panelOptions.markAckEvents && problem.acknowledged === "1") {
} else if (panelOptions.markAckEvents && problem.acknowledged === '1') {
problemColor = panelOptions.ackEventColor;
} else {
problemColor = severityDesc.color;
@@ -77,7 +84,12 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
return (
<li className={cardClass} style={cardStyle}>
<AlertIcon problem={problem} color={problemColor} highlightBackground={panelOptions.highlightBackground} blink={blink} />
<AlertIcon
problem={problem}
color={problemColor}
highlightBackground={panelOptions.highlightBackground}
blink={blink}
/>
<div className="alert-rule-item__body">
<div className="alert-rule-item__header">
@@ -90,15 +102,16 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
{panelOptions.showTags && (
<span className="zbx-trigger-tags">
{problem.tags && problem.tags.map(tag =>
<EventTag
key={tag.tag + tag.value}
tag={tag}
datasource={dsName}
highlight={tag.tag === problem.correlation_tag}
onClick={this.handleTagClick}
/>
)}
{problem.tags &&
problem.tags.map((tag) => (
<EventTag
key={tag.tag + tag.value}
tag={tag}
datasource={dsName}
highlight={tag.tag === problem.correlation_tag}
onClick={this.handleTagClick}
/>
))}
</span>
)}
</div>
@@ -106,25 +119,26 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
<div className={descriptionClass}>
{panelOptions.statusField && <AlertStatus problem={problem} blink={blink} />}
{panelOptions.severityField && (
<AlertSeverity severityDesc={severityDesc} blink={blink} highlightBackground={panelOptions.highlightBackground} />
<AlertSeverity
severityDesc={severityDesc}
blink={blink}
highlightBackground={panelOptions.highlightBackground}
/>
)}
<span className="alert-rule-item__time">
{panelOptions.ageField && "for " + age}
</span>
<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 zbx-description--newline" >
<div className="alert-rule-item__text zbx-description--newline">
<span
className="alert-rule-item__info zbx-description"
dangerouslySetInnerHTML={{ __html: problem.comments }}
/>
</div>
)}
</div>
</div>
@@ -138,30 +152,36 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
)}
<div className="alert-rule-item__time zbx-trigger-lastchange">
<span>{lastchange || "last change unknown"}</span>
<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.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>
<span>
<i className="fa fa-question-circle"></i>
</span>
</Tooltip>
)}
{problem.eventid && (
<ModalController>
{({ showModal, hideModal }) => (
<AlertAcknowledgesButton
problem={problem}
onClick={() => {
showModal(AckModal, {
canClose: problem.manual_close === '1',
severity: problemSeverity,
onSubmit: this.ackProblem,
onDismiss: hideModal,
});
}}
/>
)}
</ModalController>
{({ showModal, hideModal }) => (
<AlertAcknowledgesButton
problem={problem}
onClick={() => {
showModal(AckModal, {
canClose: problem.manual_close === '1',
severity: problemSeverity,
onSubmit: this.ackProblem,
onDismiss: hideModal,
});
}}
/>
)}
</ModalController>
)}
</div>
</div>
@@ -178,7 +198,7 @@ interface AlertHostProps {
function AlertHost(props: AlertHostProps) {
const problem = props.problem;
const panel = props.panelOptions;
let host = "";
let host = '';
if (panel.hostField && panel.hostTechNameField) {
host = `${problem.host} (${problem.hostTechName})`;
} else if (panel.hostField || panel.hostTechNameField) {
@@ -204,15 +224,13 @@ interface AlertGroupProps {
function AlertGroup(props: AlertGroupProps) {
const problem = props.problem;
const panel = props.panelOptions;
let groupNames = "";
let groupNames = '';
if (panel.hostGroups) {
const groups = _.map(problem.groups, 'name').join(', ');
groupNames += `[ ${groups} ]`;
}
return (
<span className="zabbix-hostname">{groupNames}</span>
);
return <span className="zabbix-hostname">{groupNames}</span>;
}
const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)';
@@ -228,11 +246,7 @@ function AlertStatus(props) {
{ 'alert-state-ok': problem.value === '0' },
{ 'zabbix-trigger--blinked': blink }
);
return (
<span className={className}>
{status}
</span>
);
return <span className={className}>{status}</span>;
}
function AlertSeverity(props) {
@@ -257,25 +271,29 @@ interface AlertAcknowledgesButtonProps {
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;
let content = null;
if (problem.acknowledges && problem.acknowledges.length) {
content = (
<Tooltip placement="bottom" content={this.renderTooltipContent}>
<span><i className="fa fa-comments"></i></span>
<Tooltip placement="auto" content={this.renderTooltipContent} interactive>
<span>
<i className="fa fa-comments"></i>
</span>
</Tooltip>
);
} else if (problem.showAckButton) {
content = (
<Tooltip placement="bottom" content="Acknowledge problem">
<span role="button" onClick={this.handleClick}><i className="fa fa-comments-o"></i></span>
<span role="button" onClick={this.handleClick}>
<i className="fa fa-comments-o"></i>
</span>
</Tooltip>
);
}

View File

@@ -14,14 +14,14 @@ export const AlertIcon: FC<Props> = ({ problem, color, blink, highlightBackgroun
const severity = Number(problem.severity);
const status = problem.value === '1' && severity >= 2 ? 'critical' : 'online';
const iconClass = cx(
'icon-gf',
blink && 'zabbix-trigger--blinked',
);
const iconClass = cx('icon-gf', blink && 'zabbix-trigger--blinked');
const wrapperClass = cx(
'alert-rule-item__icon',
!highlightBackground && css`color: ${color}`
!highlightBackground &&
css`
color: ${color};
`
);
return (

View File

@@ -1,6 +1,6 @@
import React, { PureComponent, CSSProperties } from 'react';
import classNames from 'classnames';
import { ProblemsPanelOptions, GFTimeRange } from '../../types';
import { ProblemsPanelOptions } from '../../types';
import { AckProblemData } from '../AckModal';
import AlertCard from './AlertCard';
import { ProblemDTO, ZBXTag } from '../../../datasource-zabbix/types';
@@ -10,7 +10,6 @@ export interface AlertListProps {
problems: ProblemDTO[];
panelOptions: ProblemsPanelOptions;
loading?: boolean;
timeRange?: GFTimeRange;
pageSize?: number;
fontSize?: number;
onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void;
@@ -44,18 +43,17 @@ export default class AlertList extends PureComponent<AlertListProps, AlertListSt
page: newPage,
currentProblems: items,
});
}
};
handleTagClick = (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => {
if (this.props.onTagClick) {
this.props.onTagClick(tag, datasource, ctrlKey, shiftKey);
}
}
};
handleProblemAck = (problem: ProblemDTO, data: AckProblemData) => {
return this.props.onProblemAck(problem, data);
}
};
render() {
const { problems, panelOptions } = this.props;
@@ -68,15 +66,17 @@ export default class AlertList extends PureComponent<AlertListProps, AlertListSt
<div className="triggers-panel-container" key="alertListContainer">
<section className="card-section card-list-layout-list">
<ol className={alertListClass}>
{currentProblems.map((problem, index) =>
{currentProblems.map((problem, index) => (
<AlertCard
key={`${problem.triggerid}-${problem.eventid}-${(problem.datasource as DataSourceRef)?.uid || problem.datasource}-${index}`}
key={`${problem.triggerid}-${problem.eventid}-${
(problem.datasource as DataSourceRef)?.uid || problem.datasource
}-${index}`}
problem={problem}
panelOptions={panelOptions}
onTagClick={this.handleTagClick}
onProblemAck={this.handleProblemAck}
/>
)}
))}
</ol>
</section>
@@ -101,10 +101,9 @@ interface PaginationControlProps {
}
class PaginationControl extends PureComponent<PaginationControlProps> {
handlePageChange = (index: number) => () => {
this.props.onPageChange(index);
}
};
render() {
const { itemsLength, pageIndex, pageSize } = this.props;
@@ -116,23 +115,20 @@ class PaginationControl extends PureComponent<PaginationControlProps> {
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 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>
<a className={pageLinkClass} onClick={this.handlePageChange(i)}>
{value}
</a>
</li>
);
pageLinks.push(pageLinkElem);
}
return (
<ul>
{pageLinks}
</ul>
);
return <ul>{pageLinks}</ul>;
}
}