problems panel: ack dialog
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"@types/lodash": "^4.14.104",
|
"@types/lodash": "^4.14.104",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
"@types/react": "^16.4.6",
|
"@types/react": "^16.4.6",
|
||||||
|
"@types/react-dom": "^16.0.11",
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-jest": "^23.6.0",
|
"babel-jest": "^23.6.0",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
|
|||||||
100
src/panel-triggers/components/Modal.tsx
Normal file
100
src/panel-triggers/components/Modal.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
interface ModalProps {
|
||||||
|
isOpen?: boolean;
|
||||||
|
withBackdrop?: boolean;
|
||||||
|
onSubmit: (data?: AckProblemData) => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModalState {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AckProblemData {
|
||||||
|
message: string;
|
||||||
|
closeProblem?: boolean;
|
||||||
|
action?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Modal extends PureComponent<ModalProps, ModalState> {
|
||||||
|
modalContainer: HTMLElement;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { value: '' };
|
||||||
|
|
||||||
|
this.modalContainer = document.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ value: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss = () => {
|
||||||
|
console.log('dismiss');
|
||||||
|
this.setState({ value: '' });
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = () => {
|
||||||
|
console.log('submit', this.state.value);
|
||||||
|
this.props.onSubmit({
|
||||||
|
message: this.state.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.props.isOpen || !this.modalContainer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalNode = (
|
||||||
|
<div className="modal modal--narrow" key="modal">
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h2 className="modal-header-title">
|
||||||
|
<i className="fa fa-reply-all"></i>
|
||||||
|
<span className="p-l-1">Acknowledge Problem</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<a className="modal-header-close" onClick={this.dismiss}>
|
||||||
|
<i className="fa fa-remove"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="modal-content">
|
||||||
|
{/* Message */}
|
||||||
|
<div className="gf-form">
|
||||||
|
<label className="gf-form-hint">
|
||||||
|
<input className="gf-form-input"
|
||||||
|
type="text"
|
||||||
|
name="message"
|
||||||
|
placeholder="Message"
|
||||||
|
maxLength={64}
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus={true}
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={this.handleChange}>
|
||||||
|
</input>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="gf-form-button-row text-center">
|
||||||
|
<button className="btn btn-success" onClick={this.submit}>Acknowledge</button>
|
||||||
|
<button className="btn btn-inverse" onClick={this.dismiss}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const modalNodeWithBackdrop = [
|
||||||
|
modalNode,
|
||||||
|
<div className="modal-backdrop in" key="modal-backdrop"></div>
|
||||||
|
];
|
||||||
|
|
||||||
|
const modal = this.props.withBackdrop ? modalNodeWithBackdrop : modalNode;
|
||||||
|
return ReactDOM.createPortal(modal, this.modalContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import EventTag from './EventTag';
|
import EventTag from './EventTag';
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
|
import { Modal, AckProblemData } from './Modal';
|
||||||
import { ProblemsPanelOptions, Trigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup } from '../types';
|
import { ProblemsPanelOptions, Trigger, ZBXItem, ZBXAcknowledge, ZBXHost, ZBXGroup } from '../types';
|
||||||
import * as utils from '../../datasource-zabbix/utils';
|
import * as utils from '../../datasource-zabbix/utils';
|
||||||
|
|
||||||
@@ -200,17 +201,15 @@ interface ProblemGroupsProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProblemGroups(props: ProblemGroupsProps) {
|
class ProblemGroups extends PureComponent<ProblemGroupsProps> {
|
||||||
let groups = "";
|
render() {
|
||||||
if (props.groups && props.groups.length) {
|
return this.props.groups.map(g => (
|
||||||
groups = props.groups.map(g => g.name).join(', ');
|
<div className={this.props.className || ''} key={g.groupid}>
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={props.className}>
|
|
||||||
<FAIcon icon="folder" />
|
<FAIcon icon="folder" />
|
||||||
<span>{groups}</span>
|
<span>{g.name}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProblemHostsProps {
|
interface ProblemHostsProps {
|
||||||
@@ -218,25 +217,24 @@ interface ProblemHostsProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProblemHosts(props: ProblemHostsProps) {
|
class ProblemHosts extends PureComponent<ProblemHostsProps> {
|
||||||
let hosts = "";
|
render() {
|
||||||
if (props.hosts && props.hosts.length) {
|
return this.props.hosts.map(h => (
|
||||||
hosts = props.hosts.map(g => g.name).join(', ');
|
<div className={this.props.className || ''} key={h.hostid}>
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={props.className}>
|
|
||||||
<FAIcon icon="server" />
|
<FAIcon icon="server" />
|
||||||
<span>{hosts}</span>
|
<span>{h.name}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProblemStatusBarProps {
|
interface ProblemStatusBarProps {
|
||||||
problem: Trigger;
|
problem: Trigger;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProblemStatusBar(props: ProblemStatusBarProps) {
|
function ProblemStatusBar(props: ProblemStatusBarProps) {
|
||||||
const { problem } = props;
|
const { problem, className } = props;
|
||||||
const multiEvent = problem.type === '1';
|
const multiEvent = problem.type === '1';
|
||||||
const link = problem.url && problem.url !== '';
|
const link = problem.url && problem.url !== '';
|
||||||
const maintenance = problem.maintenance;
|
const maintenance = problem.maintenance;
|
||||||
@@ -244,8 +242,9 @@ function ProblemStatusBar(props: ProblemStatusBarProps) {
|
|||||||
const error = problem.error && problem.error !== '';
|
const error = problem.error && problem.error !== '';
|
||||||
const stateUnknown = problem.state === '1';
|
const stateUnknown = problem.state === '1';
|
||||||
const closeByTag = problem.correlation_mode === '1';
|
const closeByTag = problem.correlation_mode === '1';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="problem-statusbar">
|
<div className={`problem-statusbar ${className || ''}`}>
|
||||||
<ProblemStatusBarItem icon="wrench" fired={maintenance} tooltip="Host maintenance" />
|
<ProblemStatusBarItem icon="wrench" fired={maintenance} tooltip="Host maintenance" />
|
||||||
<ProblemStatusBarItem icon="globe" fired={link} link={link && problem.url} tooltip="External link" />
|
<ProblemStatusBarItem icon="globe" fired={link} link={link && problem.url} tooltip="External link" />
|
||||||
<ProblemStatusBarItem icon="bullhorn" fired={multiEvent} tooltip="Trigger generates multiple problem events" />
|
<ProblemStatusBarItem icon="bullhorn" fired={multiEvent} tooltip="Trigger generates multiple problem events" />
|
||||||
@@ -281,11 +280,47 @@ function ProblemStatusBarItem(props: ProblemStatusBarItemProps) {
|
|||||||
return link ? <a href={link} target="_blank">{item}</a> : item;
|
return link ? <a href={link} target="_blank">{item}</a> : item;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProblemDetails extends PureComponent<any, any> {
|
interface ProblemActionButtonProps {
|
||||||
|
icon: string;
|
||||||
|
tooltip?: string;
|
||||||
|
className?: string;
|
||||||
|
onClick?: (event?) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProblemActionButton extends PureComponent<ProblemActionButtonProps> {
|
||||||
|
handleClick = (event) => {
|
||||||
|
this.props.onClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { icon, tooltip, className } = this.props;
|
||||||
|
let button = (
|
||||||
|
<button className={`btn problem-action-button ${className || ''}`} onClick={this.handleClick}>
|
||||||
|
<FAIcon icon={icon} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
if (tooltip) {
|
||||||
|
button = (
|
||||||
|
<Tooltip placement="bottom" content={tooltip}>
|
||||||
|
{button}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProblemDetailsState {
|
||||||
|
show: boolean;
|
||||||
|
showAckDialog: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProblemDetails extends PureComponent<any, ProblemDetailsState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
show: false
|
show: false,
|
||||||
|
showAckDialog: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,11 +330,25 @@ class ProblemDetails extends PureComponent<any, any> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ackProblem = (data: AckProblemData) => {
|
||||||
|
const problem = this.props.original as Trigger;
|
||||||
|
console.log(problem.lastEvent && problem.lastEvent.eventid, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
showAckDialog = () => {
|
||||||
|
this.setState({ showAckDialog: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAckDialog = () => {
|
||||||
|
this.setState({ showAckDialog: false });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const problem = this.props.original as Trigger;
|
const problem = this.props.original as Trigger;
|
||||||
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 > 1000;
|
const wideLayout = rootWidth > 1000;
|
||||||
|
const compactStatusBar = rootWidth < 800;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`problem-details-container ${displayClass}`}>
|
<div className={`problem-details-container ${displayClass}`}>
|
||||||
@@ -313,7 +362,13 @@ class ProblemDetails extends PureComponent<any, any> {
|
|||||||
</div>
|
</div>
|
||||||
{problem.items && <ProblemItems items={problem.items} />}
|
{problem.items && <ProblemItems items={problem.items} />}
|
||||||
</div>
|
</div>
|
||||||
<ProblemStatusBar problem={problem} />
|
<ProblemStatusBar problem={problem} className={compactStatusBar && 'compact'} />
|
||||||
|
<div className="problem-actions">
|
||||||
|
<ProblemActionButton className="navbar-button navbar-button--settings"
|
||||||
|
icon="reply-all"
|
||||||
|
tooltip="Acknowledge problem"
|
||||||
|
onClick={this.showAckDialog} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{problem.comments &&
|
{problem.comments &&
|
||||||
<div className="problem-description">
|
<div className="problem-description">
|
||||||
@@ -353,6 +408,10 @@ class ProblemDetails extends PureComponent<any, any> {
|
|||||||
{problem.groups && <ProblemGroups groups={problem.groups} className="problem-details-right-item" />}
|
{problem.groups && <ProblemGroups groups={problem.groups} className="problem-details-right-item" />}
|
||||||
{problem.hosts && <ProblemHosts hosts={problem.hosts} className="problem-details-right-item" />}
|
{problem.hosts && <ProblemHosts hosts={problem.hosts} className="problem-details-right-item" />}
|
||||||
</div>
|
</div>
|
||||||
|
<Modal withBackdrop={true}
|
||||||
|
isOpen={this.state.showAckDialog}
|
||||||
|
onSubmit={this.ackProblem}
|
||||||
|
onClose={this.closeAckDialog} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,11 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
// max-width: 13rem;
|
// max-width: 13rem;
|
||||||
|
|
||||||
|
&.compact .problem-statusbar-item {
|
||||||
|
width: 2.5rem;
|
||||||
|
padding: 0.4rem 0.4rem 0.4rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.problem-statusbar-item {
|
.problem-statusbar-item {
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
@@ -246,6 +251,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.problem-actions {
|
||||||
|
margin-left: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-action-button {
|
||||||
|
&.btn {
|
||||||
|
width: 3rem;
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
|
background-image: none;
|
||||||
|
background-color: $action-button-color;
|
||||||
|
border: 1px solid darken($action-button-color, 6%);
|
||||||
|
border-radius: 1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: darken($action-button-color, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.problem-details-middle {
|
.problem-details-middle {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.6rem 1rem;
|
||||||
@@ -288,6 +313,10 @@
|
|||||||
// background: $dark-4;
|
// background: $dark-4;
|
||||||
color: $text-color-muted;
|
color: $text-color-muted;
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.problem-details-right-item {
|
.problem-details-right-item {
|
||||||
margin-bottom: 0.2rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,3 +44,4 @@ $problems-table-stripe: $dark-3;
|
|||||||
$problem-details-background: $dark-3;
|
$problem-details-background: $dark-3;
|
||||||
$problem-statusbar-background: $dark-2;
|
$problem-statusbar-background: $dark-2;
|
||||||
$problem-statusbar-muted: $dark-3;
|
$problem-statusbar-muted: $dark-3;
|
||||||
|
$action-button-color: #103e66;
|
||||||
|
|||||||
@@ -43,3 +43,4 @@ $problems-table-stripe: $gray-6;
|
|||||||
$problem-details-background: $gray-6;
|
$problem-details-background: $gray-6;
|
||||||
$problem-statusbar-background: $gray-4;
|
$problem-statusbar-background: $gray-4;
|
||||||
$problem-statusbar-muted: $gray-5;
|
$problem-statusbar-muted: $gray-5;
|
||||||
|
$action-button-color: #103e66;
|
||||||
|
|||||||
15
yarn.lock
15
yarn.lock
@@ -146,6 +146,21 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c"
|
||||||
integrity sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==
|
integrity sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==
|
||||||
|
|
||||||
|
"@types/react-dom@^16.0.11":
|
||||||
|
version "16.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.11.tgz#bd10ccb0d9260343f4b9a49d4f7a8330a5c1f081"
|
||||||
|
integrity sha512-x6zUx9/42B5Kl2Vl9HlopV8JF64wLpX3c+Pst9kc1HgzrsH+mkehe/zmHMQTplIrR48H2gpU7ZqurQolYu8XBA==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react@*":
|
||||||
|
version "16.7.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.13.tgz#d2369ae78377356d42fb54275d30218e84f2247a"
|
||||||
|
integrity sha512-WhqrQLAE9z65hfcvWqZfR6qUtIazFRyb8LXqHo8440R53dAQqNkt2OlVJ3FXwqOwAXXg4nfYxt0qgBvE18o5XA==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
csstype "^2.2.0"
|
||||||
|
|
||||||
"@types/react@^16.4.6":
|
"@types/react@^16.4.6":
|
||||||
version "16.7.7"
|
version "16.7.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.7.tgz#1e5e23e7dd922968ed4b484cdec00a5402c9f31b"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.7.tgz#1e5e23e7dd922968ed4b484cdec00a5402c9f31b"
|
||||||
|
|||||||
Reference in New Issue
Block a user