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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user