Execute scripts from problem details
This commit is contained in:
@@ -1,21 +1,17 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { cx, css } from 'emotion';
|
||||
import { ZBX_ACK_ACTION_ADD_MESSAGE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_CHANGE_SEVERITY, ZBX_ACK_ACTION_CLOSE } from '../../datasource-zabbix/constants';
|
||||
import { APIScriptGetResponse, ZBXScript } from '../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { Button, VerticalGroup, Spinner, Modal, Select, Forms, stylesFactory, withTheme, Themeable } from '@grafana/ui';
|
||||
import { FAIcon } from '../../components';
|
||||
|
||||
import * as grafanaUi from '@grafana/ui';
|
||||
import { ZBXScript, APIExecuteScriptResponse } from '../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { Button, Spinner, Modal, Select, stylesFactory, withTheme, Themeable } from '@grafana/ui';
|
||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
const Checkbox: any = Forms?.Checkbox || (grafanaUi as any).Checkbox;
|
||||
const RadioButtonGroup: any = Forms?.RadioButtonGroup || (grafanaUi as any).RadioButtonGroup;
|
||||
import { FAIcon } from '../../components';
|
||||
|
||||
const KEYBOARD_ENTER_KEY = 13;
|
||||
const KEYBOARD_ESCAPE_KEY = 27;
|
||||
|
||||
interface Props extends Themeable {
|
||||
getScripts(): Promise<APIScriptGetResponse>;
|
||||
onSubmit(data?: AckProblemData): Promise<any> | any;
|
||||
getScripts(): Promise<ZBXScript[]>;
|
||||
onSubmit(data?: ExecScriptData): Promise<any> | any;
|
||||
onDismiss?(): void;
|
||||
}
|
||||
|
||||
@@ -24,17 +20,14 @@ interface State {
|
||||
scriptOptions: Array<SelectableValue<string>>;
|
||||
script: ZBXScript;
|
||||
error: boolean;
|
||||
errorMessage: string;
|
||||
errorMessage: string | JSX.Element;
|
||||
result: string | JSX.Element;
|
||||
selectError: string;
|
||||
result: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface AckProblemData {
|
||||
message: string;
|
||||
closeProblem?: boolean;
|
||||
action?: number;
|
||||
severity?: number;
|
||||
export interface ExecScriptData {
|
||||
scriptid: string;
|
||||
}
|
||||
|
||||
export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
||||
@@ -71,21 +64,9 @@ export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
||||
this.setState({ scriptOptions, selectedScript, script });
|
||||
}
|
||||
|
||||
handleKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
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();
|
||||
}
|
||||
|
||||
onChangeSelectedScript = (v: SelectableValue<string>) => {
|
||||
const script = this.scripts.find(s => v.value === s.scriptid);
|
||||
this.setState({ selectedScript: v, script });
|
||||
this.setState({ selectedScript: v, script, errorMessage: '', loading: false, result: '' });
|
||||
};
|
||||
|
||||
dismiss = () => {
|
||||
@@ -94,55 +75,54 @@ export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
// const { acknowledge, changeSeverity, closeProblem } = this.state;
|
||||
const { selectedScript } = 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'
|
||||
// });
|
||||
// }
|
||||
if (!selectedScript) {
|
||||
return this.setState({
|
||||
selectError: 'Select a script to execute.'
|
||||
});
|
||||
}
|
||||
|
||||
// this.setState({ ackError: '', loading: true });
|
||||
this.setState({ errorMessage: '', loading: true, result: '' });
|
||||
|
||||
// const ackData: AckProblemData = {
|
||||
// message: this.state.value,
|
||||
// };
|
||||
const data: ExecScriptData = {
|
||||
scriptid: selectedScript.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(data).then((result: APIExecuteScriptResponse) => {
|
||||
const message = this.formatResult(result?.value || '');
|
||||
if (result?.response === 'success') {
|
||||
this.setState({ result: message, loading: false });
|
||||
} else {
|
||||
this.setState({ error: true, errorMessage: message, loading: false });
|
||||
}
|
||||
}).catch(err => {
|
||||
let errorMessage = err.message || err.data || '';
|
||||
errorMessage = this.formatResult(errorMessage);
|
||||
this.setState({
|
||||
error: true,
|
||||
loading: false,
|
||||
errorMessage,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// this.props.onSubmit(ackData).then(() => {
|
||||
// this.dismiss();
|
||||
// }).catch(err => {
|
||||
// this.setState({
|
||||
// ackError: err.message || err.data,
|
||||
// loading: false,
|
||||
// });
|
||||
// });
|
||||
formatResult = (result: string) => {
|
||||
const formatted = result.split('\n').map((p, i) => {
|
||||
return <p key={i}>{p}</p>;
|
||||
});
|
||||
return <>{formatted}</>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
const { scriptOptions, selectedScript, script, selectError, errorMessage } = this.state;
|
||||
const { scriptOptions, selectedScript, script, result, selectError, errorMessage, error } = this.state;
|
||||
|
||||
const styles = getStyles(theme);
|
||||
const modalClass = cx(styles.modal);
|
||||
const modalTitleClass = cx(styles.modalHeaderTitle);
|
||||
const inputGroupClass = cx('gf-form', styles.inputGroup);
|
||||
const inputHintClass = cx('gf-form-hint-text', styles.inputHint);
|
||||
const inputErrorClass = cx('gf-form-hint-text', styles.inputError);
|
||||
const selectErrorClass = cx('gf-form-hint-text', styles.inputError);
|
||||
const scriptCommandContainerClass = cx('gf-form', styles.scriptCommandContainer);
|
||||
const scriptCommandClass = cx('gf-form-hint-text', styles.scriptCommand);
|
||||
|
||||
return (
|
||||
@@ -157,28 +137,30 @@ export class ExecScriptModalUnthemed extends PureComponent<Props, State> {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={inputGroupClass}>
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-hint">
|
||||
<Select
|
||||
options={scriptOptions}
|
||||
value={selectedScript}
|
||||
onChange={this.onChangeSelectedScript}
|
||||
/>
|
||||
<small className={inputHintClass}>Press Enter to execute</small>
|
||||
{selectError &&
|
||||
<small className={inputErrorClass}>{selectError}</small>
|
||||
<small className={selectErrorClass}>{selectError}</small>
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<div className={scriptCommandContainerClass}>
|
||||
{script && <small className={scriptCommandClass}>{script.command}</small>}
|
||||
</div>
|
||||
|
||||
{this.state.error &&
|
||||
<div className="gf-form ack-request-error">
|
||||
<span className={styles.execError}>{errorMessage}</span>
|
||||
</div>
|
||||
<div className={styles.resultContainer}>
|
||||
{result &&
|
||||
<span className={styles.execResult}>{result}</span>
|
||||
}
|
||||
{error &&
|
||||
<span className={styles.execError}>{errorMessage}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="gf-form-button-row text-center">
|
||||
<Button variant="primary" onClick={this.submit}>Execute</Button>
|
||||
@@ -193,7 +175,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const red = theme.colors.red || (theme as any).palette.red;
|
||||
return {
|
||||
modal: css`
|
||||
width: 500px;
|
||||
width: 600px;
|
||||
`,
|
||||
modalHeaderTitle: css`
|
||||
font-size: ${theme.typography.heading.h3};
|
||||
@@ -201,16 +183,16 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
margin: 0 ${theme.spacing.md};
|
||||
display: flex;
|
||||
`,
|
||||
inputGroup: css`
|
||||
`,
|
||||
input: css`
|
||||
border-color: ${red};
|
||||
border-radius: 2px;
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 2px ${theme.colors.pageBg || (theme as any).colors.bg1}, 0 0 0px 4px ${red};
|
||||
`,
|
||||
scriptCommandContainer: css`
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
`,
|
||||
scriptCommand: css`
|
||||
float: left;
|
||||
color: ${theme.colors.textWeak};
|
||||
text-align: left;
|
||||
font-family: ${theme.typography.fontFamily.monospace};
|
||||
@@ -224,6 +206,17 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
float: left;
|
||||
color: ${red};
|
||||
`,
|
||||
resultContainer: css`
|
||||
min-height: 50px;
|
||||
font-family: ${theme.typography.fontFamily.monospace};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
p {
|
||||
font-size: ${theme.typography.size.sm};
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
`,
|
||||
execResult: css`
|
||||
`,
|
||||
execError: css`
|
||||
color: ${red};
|
||||
`,
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import moment from 'moment';
|
||||
import * as utils from '../../../datasource-zabbix/utils';
|
||||
import { ProblemDTO, ZBXHost, ZBXGroup, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types';
|
||||
import { ZBXScript } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { ZBXScript, APIExecuteScriptResponse } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { ZBXItem, GFTimeRange, RTRow } from '../../types';
|
||||
import { AckModal, AckProblemData } from '../AckModal';
|
||||
import EventTag from '../EventTag';
|
||||
@@ -10,7 +10,7 @@ import ProblemStatusBar from './ProblemStatusBar';
|
||||
import AcknowledgesList from './AcknowledgesList';
|
||||
import ProblemTimeline from './ProblemTimeline';
|
||||
import { FAIcon, ExploreButton, AckButton, Tooltip, ModalController, ExecScriptButton } from '../../../components';
|
||||
import { ExecScriptModal } from '../ExecScriptModal';
|
||||
import { ExecScriptModal, ExecScriptData } from '../ExecScriptModal';
|
||||
|
||||
interface ProblemDetailsProps extends RTRow<ProblemDTO> {
|
||||
rootWidth: number;
|
||||
@@ -20,6 +20,7 @@ interface ProblemDetailsProps extends RTRow<ProblemDTO> {
|
||||
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: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
||||
}
|
||||
@@ -82,6 +83,11 @@ export class ProblemDetails extends PureComponent<ProblemDetailsProps, ProblemDe
|
||||
return this.props.getScripts(problem);
|
||||
}
|
||||
|
||||
onExecuteScript = (data: ExecScriptData) => {
|
||||
const problem = this.props.original as ProblemDTO;
|
||||
return this.props.onExecuteScript(problem, data.scriptid);
|
||||
}
|
||||
|
||||
render() {
|
||||
const problem = this.props.original as ProblemDTO;
|
||||
const alerts = this.state.alerts;
|
||||
@@ -117,7 +123,7 @@ export class ProblemDetails extends PureComponent<ProblemDetailsProps, ProblemDe
|
||||
onClick={() => {
|
||||
showModal(ExecScriptModal, {
|
||||
getScripts: this.getScripts,
|
||||
onSubmit: this.ackProblem,
|
||||
onSubmit: this.onExecuteScript,
|
||||
onDismiss: hideModal,
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AckProblemData } from '../AckModal';
|
||||
import { GFHeartIcon, FAIcon } from '../../../components';
|
||||
import { ProblemsPanelOptions, GFTimeRange, RTCell, TriggerSeverity, RTResized } from '../../types';
|
||||
import { ProblemDTO, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types';
|
||||
import { ZBXScript } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { ZBXScript, APIExecuteScriptResponse } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { AckCell } from './AckCell';
|
||||
|
||||
export interface ProblemListProps {
|
||||
@@ -24,6 +24,7 @@ export interface ProblemListProps {
|
||||
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) => void;
|
||||
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
||||
onPageSizeChange?: (pageSize: number, pageIndex: number) => void;
|
||||
@@ -55,6 +56,9 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
|
||||
return this.props.onProblemAck(problem, data);
|
||||
}
|
||||
|
||||
onExecuteScript = (problem: ProblemDTO, data: AckProblemData) => {
|
||||
}
|
||||
|
||||
handlePageSizeChange = (pageSize, pageIndex) => {
|
||||
if (this.props.onPageSizeChange) {
|
||||
this.props.onPageSizeChange(pageSize, pageIndex);
|
||||
@@ -174,6 +178,7 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
|
||||
getProblemAlerts={this.props.getProblemAlerts}
|
||||
getScripts={this.props.getScripts}
|
||||
onProblemAck={this.handleProblemAck}
|
||||
onExecuteScript={this.props.onExecuteScript}
|
||||
onTagClick={this.handleTagClick}
|
||||
subRows={false}
|
||||
/>
|
||||
|
||||
@@ -317,11 +317,11 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
getProblemScripts(problem: ProblemDTO) {
|
||||
const hostIds = problem.hosts?.map(h => h.hostid);
|
||||
const hostid = problem.hosts?.length ? problem.hosts[0].hostid : null;
|
||||
|
||||
return getDataSourceSrv().get(problem.datasource)
|
||||
.then((datasource: any) => {
|
||||
return datasource.zabbix.getScripts(hostIds);
|
||||
return datasource.zabbix.getScripts([hostid]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -361,6 +361,15 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
executeScript(problem: ProblemDTO, scriptid: string) {
|
||||
const hostid = problem.hosts?.length ? problem.hosts[0].hostid : null;
|
||||
|
||||
return getDataSourceSrv().get(problem.datasource)
|
||||
.then((datasource: any) => {
|
||||
return datasource.zabbix.executeScript(hostid, scriptid);
|
||||
});
|
||||
}
|
||||
|
||||
handlePageSizeChange(pageSize, pageIndex) {
|
||||
this.panel.pageSize = pageSize;
|
||||
this.pageIndex = pageIndex;
|
||||
@@ -415,6 +424,7 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
const { message, action, severity } = data;
|
||||
return ctrl.acknowledgeProblem(trigger, message, action, severity);
|
||||
},
|
||||
onExecuteScript: ctrl.executeScript.bind(ctrl),
|
||||
onTagClick: (tag, datasource, ctrlKey, shiftKey) => {
|
||||
if (ctrlKey || shiftKey) {
|
||||
ctrl.removeTagFilter(tag, datasource);
|
||||
|
||||
Reference in New Issue
Block a user