Files
grafana-zabbix/src/panel-triggers/components/AckModal.tsx
Gábor Farkas 313a166be6 prettier fixes
2023-09-29 13:34:51 +02:00

304 lines
7.8 KiB
TypeScript

import React, { PureComponent } from 'react';
import { cx, css } from '@emotion/css';
import {
ZBX_ACK_ACTION_ADD_MESSAGE,
ZBX_ACK_ACTION_ACK,
ZBX_ACK_ACTION_CHANGE_SEVERITY,
ZBX_ACK_ACTION_CLOSE,
} from '../../datasource/constants';
import {
Button,
VerticalGroup,
Spinner,
Modal,
Checkbox,
RadioButtonGroup,
stylesFactory,
withTheme,
Themeable,
TextArea,
} from '@grafana/ui';
import { FAIcon } from '../../components';
import { GrafanaTheme } from '@grafana/data';
const KEYBOARD_ENTER_KEY = 13;
const KEYBOARD_ESCAPE_KEY = 27;
interface Props extends Themeable {
canAck?: boolean;
canClose?: boolean;
severity?: number;
withBackdrop?: boolean;
onSubmit: (data?: AckProblemData) => Promise<any> | any;
onDismiss?: () => void;
}
interface State {
value: string;
error: boolean;
errorMessage: string;
ackError: string;
acknowledge: boolean;
closeProblem: boolean;
changeSeverity: boolean;
selectedSeverity: number;
loading: boolean;
}
export interface AckProblemData {
message: string;
closeProblem?: boolean;
action?: number;
severity?: number;
}
const severityOptions = [
{ value: 0, label: 'Not classified' },
{ value: 1, label: 'Information' },
{ value: 2, label: 'Warning' },
{ value: 3, label: 'Average' },
{ value: 4, label: 'High' },
{ value: 5, label: 'Disaster' },
];
export class AckModalUnthemed extends PureComponent<Props, State> {
static defaultProps: Partial<Props> = {
withBackdrop: true,
};
constructor(props) {
super(props);
this.state = {
value: '',
error: false,
errorMessage: '',
ackError: '',
acknowledge: false,
closeProblem: false,
changeSeverity: false,
selectedSeverity: props.severity || 0,
loading: false,
};
}
handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
this.setState({ value: event.target.value, error: false });
};
handleKeyPress = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.which === KEYBOARD_ENTER_KEY || event.key === 'Enter') {
// this.submit();
} else if (event.which === KEYBOARD_ESCAPE_KEY || event.key === 'Escape') {
this.dismiss();
}
};
handleBackdropClick = () => {
this.dismiss();
};
onAcknowledgeToggle = () => {
this.setState({ acknowledge: !this.state.acknowledge, error: false });
};
onChangeSeverityToggle = () => {
this.setState({ changeSeverity: !this.state.changeSeverity, error: false });
};
onCloseProblemToggle = () => {
this.setState({ closeProblem: !this.state.closeProblem, error: false });
};
onChangeSelectedSeverity = (v) => {
this.setState({ selectedSeverity: v });
};
dismiss = () => {
this.setState({ value: '', error: false, errorMessage: '', ackError: '', loading: false });
this.props.onDismiss();
};
submit = () => {
const { acknowledge, changeSeverity, closeProblem } = this.state;
const actionSelected = acknowledge || changeSeverity || closeProblem;
if (!this.state.value && !actionSelected) {
return this.setState({
error: true,
errorMessage: 'Enter message text or select an action',
});
}
this.setState({ ackError: '', loading: true });
const ackData: AckProblemData = {
message: this.state.value,
};
let action = ZBX_ACK_ACTION_ADD_MESSAGE;
if (this.state.acknowledge) {
action += ZBX_ACK_ACTION_ACK;
}
if (this.state.changeSeverity) {
action += ZBX_ACK_ACTION_CHANGE_SEVERITY;
ackData.severity = this.state.selectedSeverity;
}
if (this.state.closeProblem) {
action += ZBX_ACK_ACTION_CLOSE;
}
ackData.action = action;
this.props
.onSubmit(ackData)
.then(() => {
this.dismiss();
})
.catch((err) => {
const errorMessage = err.data?.message || err.data?.error || err.data || err.statusText || '';
this.setState({
ackError: errorMessage,
loading: false,
});
});
};
renderActions() {
const { canClose } = this.props;
const actions = [
<Checkbox key="ack" label="Acknowledge" value={this.state.acknowledge} onChange={this.onAcknowledgeToggle} />,
<Checkbox
key="change-severity"
label="Change severity"
description=""
value={this.state.changeSeverity}
onChange={this.onChangeSeverityToggle}
/>,
this.state.changeSeverity && (
<RadioButtonGroup
key="severity"
size="sm"
options={severityOptions}
value={this.state.selectedSeverity}
onChange={this.onChangeSelectedSeverity}
/>
),
canClose && (
<Checkbox
key="close"
label="Close problem"
disabled={!canClose}
value={this.state.closeProblem}
onChange={this.onCloseProblemToggle}
/>
),
];
// <VerticalGroup /> doesn't handle empty elements properly, so don't return it
return actions.filter((e) => e);
}
render() {
const { theme } = this.props;
const styles = getStyles(theme);
const modalClass = cx(styles.modal);
const modalTitleClass = cx(styles.modalHeaderTitle);
const inputGroupClass = cx('gf-form', styles.inputGroup);
const inputClass = cx(this.state.error && styles.input);
const inputHintClass = cx('gf-form-hint-text', styles.inputHint);
const inputErrorClass = cx('gf-form-hint-text', styles.inputError);
return (
<Modal
isOpen={true}
onDismiss={this.dismiss}
className={modalClass}
title={
<div className={modalTitleClass}>
{this.state.loading ? <Spinner size={18} /> : <FAIcon icon="reply-all" />}
<span className="p-l-1">Acknowledge Problem</span>
</div>
}
>
<div className={inputGroupClass}>
<label className="gf-form-hint">
<TextArea
className={inputClass}
type="text"
name="message"
placeholder="Message"
autoComplete="off"
autoFocus={true}
value={this.state.value}
onChange={this.handleChange}
onKeyDown={this.handleKeyPress}
></TextArea>
<small className={inputHintClass}>Press Enter to submit</small>
{this.state.error && <small className={inputErrorClass}>{this.state.errorMessage}</small>}
</label>
</div>
<div className="gf-form">
<VerticalGroup>{this.renderActions()}</VerticalGroup>
</div>
{this.state.ackError && (
<div className="gf-form ack-request-error">
<span className={styles.ackError}>{this.state.ackError}</span>
</div>
)}
<div className="gf-form-button-row text-center">
<Button variant="primary" onClick={this.submit}>
Update
</Button>
<Button variant="secondary" onClick={this.dismiss}>
Cancel
</Button>
</div>
</Modal>
);
}
}
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const red = theme.palette.red;
return {
modal: css`
width: 500px;
`,
modalHeaderTitle: css`
font-size: ${theme.typography.heading.h3};
padding-top: ${theme.spacing.sm};
margin: 0 ${theme.spacing.md};
display: flex;
`,
inputGroup: css`
margin-bottom: 16px;
`,
input: css`
border-color: ${red};
border-radius: 2px;
outline-offset: 2px;
box-shadow:
0 0 0 2px ${theme.colors.bg1},
0 0 0px 4px ${red};
`,
inputHint: css`
display: inherit;
float: right;
color: ${theme.colors.textWeak};
`,
inputError: css`
float: left;
color: ${red};
`,
ackError: css`
color: ${red};
`,
};
});
export const AckModal = withTheme(AckModalUnthemed);