* Problems count mode * Use tooltip from grafana ui * Add editors for new modes * Fix macro mode * Fix bugs * Unify editors to use one Triggers editor for all count queries * Use time range toggle for triggers query, #918 * Add item tags suport for triggers count mode * Fix triggers count by items * Use data frames for triggers data, #1441 * Return empty result if no items found * Add migration for problems count mode * bump version to 4.3.0-pre * Add zip task to makefile * Add schema to query model * Minor refactor * Refactor: move components to separate files * Minor refactor * Support url in event tags * Add tooltip with link url * Update grafana packages * Fix adding new problems panel * ProblemDetails: rewrite as a functional component * minor refactor
248 lines
9.1 KiB
TypeScript
248 lines
9.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { css } from '@emotion/css';
|
|
// eslint-disable-next-line
|
|
import moment from 'moment';
|
|
import { TimeRange, DataSourceRef, GrafanaTheme2 } from '@grafana/data';
|
|
import { Tooltip, useStyles2 } from '@grafana/ui';
|
|
import { getDataSourceSrv } from '@grafana/runtime';
|
|
import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXTag } from '../../../datasource/types';
|
|
import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource/zabbix/connectors/zabbix_api/types';
|
|
import { AckModal, AckProblemData } from '../AckModal';
|
|
import { EventTag } from '../EventTag';
|
|
import AcknowledgesList from './AcknowledgesList';
|
|
import ProblemTimeline from './ProblemTimeline';
|
|
import { AckButton, ExecScriptButton, ExploreButton, FAIcon, ModalController } from '../../../components';
|
|
import { ExecScriptData, ExecScriptModal } from '../ExecScriptModal';
|
|
import ProblemStatusBar from './ProblemStatusBar';
|
|
import { RTRow } from '../../types';
|
|
import { ProblemItems } from './ProblemItems';
|
|
import { ProblemHosts, ProblemHostsDescription } from './ProblemHosts';
|
|
import { ProblemGroups } from './ProblemGroups';
|
|
import { ProblemExpression } from './ProblemExpression';
|
|
|
|
interface Props extends RTRow<ProblemDTO> {
|
|
rootWidth: number;
|
|
timeRange: TimeRange;
|
|
showTimeline?: boolean;
|
|
panelId?: number;
|
|
getProblemEvents: (problem: ProblemDTO) => Promise<ZBXEvent[]>;
|
|
getProblemAlerts: (problem: ProblemDTO) => Promise<ZBXAlert[]>;
|
|
getScripts: (problem: ProblemDTO) => Promise<ZBXScript[]>;
|
|
onExecuteScript(problem: ProblemDTO, scriptid: string): Promise<APIExecuteScriptResponse>;
|
|
onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => Promise<any> | any;
|
|
onTagClick?: (tag: ZBXTag, datasource: DataSourceRef | string, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
|
}
|
|
|
|
export const ProblemDetails = ({
|
|
original,
|
|
rootWidth,
|
|
timeRange,
|
|
showTimeline,
|
|
panelId,
|
|
getProblemAlerts,
|
|
getProblemEvents,
|
|
getScripts,
|
|
onExecuteScript,
|
|
onProblemAck,
|
|
onTagClick,
|
|
}: Props) => {
|
|
const [events, setEvents] = useState([]);
|
|
const [alerts, setAletrs] = useState([]);
|
|
const [show, setShow] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (showTimeline) {
|
|
fetchProblemEvents();
|
|
}
|
|
fetchProblemAlerts();
|
|
requestAnimationFrame(() => {
|
|
setShow(true);
|
|
});
|
|
}, []);
|
|
|
|
const fetchProblemEvents = async () => {
|
|
const problem = original;
|
|
const events = await getProblemEvents(problem);
|
|
setEvents(events);
|
|
};
|
|
|
|
const fetchProblemAlerts = async () => {
|
|
const problem = original;
|
|
const alerts = await getProblemAlerts(problem);
|
|
setAletrs(alerts);
|
|
};
|
|
|
|
const handleTagClick = (tag: ZBXTag, datasource: DataSourceRef | string, ctrlKey?: boolean, shiftKey?: boolean) => {
|
|
if (onTagClick) {
|
|
onTagClick(tag, datasource, ctrlKey, shiftKey);
|
|
}
|
|
};
|
|
|
|
const ackProblem = (data: AckProblemData) => {
|
|
const problem = original as ProblemDTO;
|
|
return onProblemAck(problem, data);
|
|
};
|
|
|
|
const getScriptsInternal = () => {
|
|
const problem = original as ProblemDTO;
|
|
return getScripts(problem);
|
|
};
|
|
|
|
const onExecuteScriptInternal = (data: ExecScriptData) => {
|
|
const problem = original as ProblemDTO;
|
|
return onExecuteScript(problem, data.scriptid);
|
|
};
|
|
|
|
const problem = original as ProblemDTO;
|
|
const displayClass = show ? 'show' : '';
|
|
const wideLayout = rootWidth > 1200;
|
|
const compactStatusBar = rootWidth < 800 || (problem.acknowledges && wideLayout && rootWidth < 1400);
|
|
const age = moment.unix(problem.timestamp).fromNow(true);
|
|
const showAcknowledges = problem.acknowledges && problem.acknowledges.length !== 0;
|
|
const problemSeverity = Number(problem.severity);
|
|
|
|
let dsName: string = original.datasource as string;
|
|
if ((original.datasource as DataSourceRef)?.uid) {
|
|
const dsInstance = getDataSourceSrv().getInstanceSettings((original.datasource as DataSourceRef).uid);
|
|
dsName = dsInstance.name;
|
|
}
|
|
const styles = useStyles2(getStyles);
|
|
|
|
return (
|
|
<div className={`problem-details-container ${displayClass}`}>
|
|
<div className="problem-details-body">
|
|
<div className={styles.problemDetails}>
|
|
<div className="problem-details-head">
|
|
<div className="problem-actions-left">
|
|
<ExploreButton problem={problem} panelId={panelId} range={timeRange} />
|
|
</div>
|
|
{problem.showAckButton && (
|
|
<div className="problem-actions">
|
|
<ModalController>
|
|
{({ showModal, hideModal }) => (
|
|
<ExecScriptButton
|
|
className="problem-action-button"
|
|
onClick={() => {
|
|
showModal(ExecScriptModal, {
|
|
getScripts: getScriptsInternal,
|
|
onSubmit: onExecuteScriptInternal,
|
|
onDismiss: hideModal,
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
</ModalController>
|
|
<ModalController>
|
|
{({ showModal, hideModal }) => (
|
|
<AckButton
|
|
className="problem-action-button"
|
|
onClick={() => {
|
|
showModal(AckModal, {
|
|
canClose: problem.manual_close === '1',
|
|
severity: problemSeverity,
|
|
onSubmit: ackProblem,
|
|
onDismiss: hideModal,
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
</ModalController>
|
|
</div>
|
|
)}
|
|
<ProblemStatusBar problem={problem} alerts={alerts} className={compactStatusBar && 'compact'} />
|
|
</div>
|
|
<div className="problem-details-row">
|
|
<div className="problem-value-container">
|
|
<div className="problem-age">
|
|
<FAIcon icon="clock-o" />
|
|
<span>{age}</span>
|
|
</div>
|
|
{problem.items && <ProblemItems items={problem.items} />}
|
|
</div>
|
|
</div>
|
|
{problem.comments && (
|
|
<div className="problem-description-row">
|
|
<div className="problem-description">
|
|
<Tooltip placement="right" content={<span dangerouslySetInnerHTML={{ __html: problem.comments }} />}>
|
|
<span className="description-label">Description: </span>
|
|
</Tooltip>
|
|
{/* <span>{problem.comments}</span> */}
|
|
<span dangerouslySetInnerHTML={{ __html: problem.comments }} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{problem.items && (
|
|
<div className="problem-description-row">
|
|
<ProblemExpression problem={problem} />
|
|
</div>
|
|
)}
|
|
{problem.hosts && (
|
|
<div className="problem-description-row">
|
|
<ProblemHostsDescription hosts={problem.hosts} />
|
|
</div>
|
|
)}
|
|
{problem.tags && problem.tags.length > 0 && (
|
|
<div className="problem-tags">
|
|
{problem.tags &&
|
|
problem.tags.map((tag) => (
|
|
<EventTag
|
|
key={tag.tag + tag.value}
|
|
tag={tag}
|
|
datasource={problem.datasource}
|
|
highlight={tag.tag === problem.correlation_tag}
|
|
onClick={handleTagClick}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
{showTimeline && events.length > 0 && <ProblemTimeline events={events} timeRange={timeRange} />}
|
|
{showAcknowledges && !wideLayout && (
|
|
<div className="problem-ack-container">
|
|
<h6>
|
|
<FAIcon icon="reply-all" /> Acknowledges
|
|
</h6>
|
|
<AcknowledgesList acknowledges={problem.acknowledges} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
{showAcknowledges && wideLayout && (
|
|
<div className="problem-details-middle">
|
|
<div className="problem-ack-container">
|
|
<h6>
|
|
<FAIcon icon="reply-all" /> Acknowledges
|
|
</h6>
|
|
<AcknowledgesList acknowledges={problem.acknowledges} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="problem-details-right">
|
|
<div className="problem-details-right-item">
|
|
<FAIcon icon="database" />
|
|
<span>{dsName}</span>
|
|
</div>
|
|
{problem.proxy && (
|
|
<div className="problem-details-right-item">
|
|
<FAIcon icon="cloud" />
|
|
<span>{problem.proxy}</span>
|
|
</div>
|
|
)}
|
|
{problem.groups && <ProblemGroups groups={problem.groups} />}
|
|
{problem.hosts && <ProblemHosts hosts={problem.hosts} />}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => ({
|
|
problemDetails: css`
|
|
position: relative;
|
|
flex: 10 1 auto;
|
|
// padding: 0.5rem 1rem 0.5rem 1.2rem;
|
|
padding: ${theme.spacing(0.5)} ${theme.spacing(1)} ${theme.spacing(0.5)} ${theme.spacing(1.2)}
|
|
display: flex;
|
|
flex-direction: column;
|
|
// white-space: pre-line;
|
|
`,
|
|
});
|