From 6841fa0386d00c5a6e4055f1a28a607a1ca95a2c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 27 May 2020 15:15:36 +0300 Subject: [PATCH] Execute scripts from problem details --- .../zabbix/connectors/zabbix_api/types.ts | 5 +- .../zabbix_api/zabbixAPIConnector.ts | 15 +- src/datasource-zabbix/zabbix/zabbix.ts | 2 +- .../components/ExecScriptModal.tsx | 147 +++++++++--------- .../components/Problems/ProblemDetails.tsx | 12 +- .../components/Problems/Problems.tsx | 7 +- src/panel-triggers/triggers_panel_ctrl.ts | 14 +- 7 files changed, 114 insertions(+), 88 deletions(-) diff --git a/src/datasource-zabbix/zabbix/connectors/zabbix_api/types.ts b/src/datasource-zabbix/zabbix/connectors/zabbix_api/types.ts index 677ad71..57e4423 100644 --- a/src/datasource-zabbix/zabbix/connectors/zabbix_api/types.ts +++ b/src/datasource-zabbix/zabbix/connectors/zabbix_api/types.ts @@ -54,4 +54,7 @@ export interface ZBXScript { execute_on?: string; } -export type APIScriptGetResponse = ZabbixAPIResponse; +export interface APIExecuteScriptResponse { + response: 'success' | 'failed'; + value?: string; +} diff --git a/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts b/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts index 6fa3482..cde6974 100644 --- a/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts +++ b/src/datasource-zabbix/zabbix/connectors/zabbix_api/zabbixAPIConnector.ts @@ -5,7 +5,7 @@ import * as utils from '../../../utils'; import { ZabbixAPICore } from './zabbixAPICore'; import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants'; import { ShowProblemTypes, ZBXProblem } from '../../../types'; -import { JSONRPCRequestParams, APIScriptGetResponse } from './types'; +import { JSONRPCRequestParams, ZBXScript, APIExecuteScriptResponse } from './types'; const DEFAULT_ZABBIX_VERSION = '3.0.0'; @@ -665,13 +665,22 @@ export class ZabbixAPIConnector { return this.request('proxy.get', params); } - getScripts(hostids: string[], options?: any): APIScriptGetResponse { + getScripts(hostids: string[], options?: any): Promise { const params: any = { output: 'extend', hostids, }; - return this.request('script.get', params); + return this.request('script.get', params).then(utils.mustArray); + } + + executeScript(hostid: string, scriptid: string): Promise { + const params: any = { + hostid, + scriptid, + }; + + return this.request('script.execute', params); } } diff --git a/src/datasource-zabbix/zabbix/zabbix.ts b/src/datasource-zabbix/zabbix/zabbix.ts index f4aa4aa..c0d948b 100644 --- a/src/datasource-zabbix/zabbix/zabbix.ts +++ b/src/datasource-zabbix/zabbix/zabbix.ts @@ -30,7 +30,7 @@ const REQUESTS_TO_CACHE = [ const REQUESTS_TO_BIND = [ 'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts', 'getAcknowledges', 'getITService', 'getVersion', 'login', 'acknowledgeEvent', 'getProxies', 'getEventAlerts', - 'getExtendedEventData', 'getScripts' + 'getExtendedEventData', 'getScripts', 'executeScript', ]; export class Zabbix implements ZabbixConnector { diff --git a/src/panel-triggers/components/ExecScriptModal.tsx b/src/panel-triggers/components/ExecScriptModal.tsx index fa1752c..357e8e0 100644 --- a/src/panel-triggers/components/ExecScriptModal.tsx +++ b/src/panel-triggers/components/ExecScriptModal.tsx @@ -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; - onSubmit(data?: AckProblemData): Promise | any; + getScripts(): Promise; + onSubmit(data?: ExecScriptData): Promise | any; onDismiss?(): void; } @@ -24,17 +20,14 @@ interface State { scriptOptions: Array>; 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 { @@ -71,21 +64,9 @@ export class ExecScriptModalUnthemed extends PureComponent { this.setState({ scriptOptions, selectedScript, script }); } - handleKeyUp = (event: React.KeyboardEvent) => { - 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) => { 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 { } 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}

; + }); + 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 { } > -
+