Merge pull request #802 from influxdata/opsg-alert

OpsGenie Alert Config
pull/817/head
Nathan Haugo 2017-01-27 15:17:18 -08:00 committed by GitHub
commit 7392848b10
6 changed files with 220 additions and 2 deletions

View File

@ -6,8 +6,9 @@
### Upcoming Features
1. [#779](https://github.com/influxdata/chronograf/issues/779): Add layout for telegraf's diskio system plugin
1. [#810](https://github.com/influxdata/chronograf/issues/810): Add layout for telegraf's net system plugin
1. [#811](https://github.com/influxdata/chronograf/issues/811): Add layout for telegraf's procstat plugin
2. [#810](https://github.com/influxdata/chronograf/issues/810): Add layout for telegraf's net system plugin
3. [#811](https://github.com/influxdata/chronograf/issues/811): Add layout for telegraf's procstat plugin
3. [#737](https://github.com/influxdata/chronograf/issues/737): Add GUI for OpsGenie kapacitor alert service
### Upcoming UI Improvements

View File

@ -69,10 +69,12 @@ A UI for [Kapacitor](https://github.com/influxdata/kapacitor) alert creation and
* Preview data and alert boundaries while creating an alert
* Configure alert destinations - Currently, Chronograf supports sending alerts to:
* HipChat
* OpsGenie
* PagerDuty
* Sensu
* Slack
* SMTP
* Talk
* Telegram
* VictorOps
* View all active alerts at a glance on the alerting dashboard

View File

@ -3,6 +3,7 @@ import _ from 'lodash';
import {getKapacitorConfig, updateKapacitorConfigSection, testAlertOutput} from 'shared/apis';
import AlertaConfig from './AlertaConfig';
import HipChatConfig from './HipChatConfig';
import OpsGenieConfig from './OpsGenieConfig';
import PagerDutyConfig from './PagerDutyConfig';
import SensuConfig from './SensuConfig';
import SlackConfig from './SlackConfig';
@ -111,6 +112,7 @@ const AlertOutputs = React.createClass({
<label htmlFor="alert-endpoint" className="sr-only">Alert Enpoint</label>
<select value={this.state.selectedEndpoint} className="form-control" id="source" onChange={this.changeSelectedEndpoint}>
<option value="hipchat">HipChat</option>
<option value="opsgenie">OpsGenie</option>
<option value="pagerduty">PagerDuty</option>
<option value="sensu">Sensu</option>
<option value="slack">Slack</option>
@ -159,6 +161,9 @@ const AlertOutputs = React.createClass({
case 'telegram': {
return <TelegramConfig onSave={save} config={this.getSection(configSections, endpoint)} />;
}
case 'opsgenie': {
return <OpsGenieConfig onSave={save} config={this.getSection(configSections, endpoint)} />;
}
case 'pagerduty': {
return <PagerDutyConfig onSave={save} config={this.getSection(configSections, endpoint)} />;
}

View File

@ -0,0 +1,177 @@
import React, {PropTypes} from 'react';
import _ from 'lodash';
const {
array,
arrayOf,
bool,
func,
shape,
string,
} = PropTypes;
const OpsGenieConfig = React.createClass({
propTypes: {
config: shape({
options: shape({
'api-key': bool,
teams: array,
recipients: array,
}).isRequired,
}).isRequired,
onSave: func.isRequired,
},
getInitialState() {
const {teams, recipients} = this.props.config.options;
return {
currentTeams: teams || [],
currentRecipients: recipients || [],
};
},
handleSaveAlert(e) {
e.preventDefault();
const properties = {
'api-key': this.apiKey.value,
teams: this.state.currentTeams,
recipients: this.state.currentRecipients,
};
this.props.onSave(properties);
},
handleAddTeam(team) {
this.setState({currentTeams: this.state.currentTeams.concat(team)});
},
handleAddRecipient(recipient) {
this.setState({currentRecipients: this.state.currentRecipients.concat(recipient)});
},
handleDeleteTeam(team) {
this.setState({currentTeams: this.state.currentTeams.filter(t => t !== team)});
},
handleDeleteRecipient(recipient) {
this.setState({currentRecipients: this.state.currentRecipients.filter(r => r !== recipient)});
},
render() {
const {options} = this.props.config;
const apiKey = options['api-key'];
const {currentTeams, currentRecipients} = this.state;
return (
<div>
<h4 className="text-center">OpsGenie Alert</h4>
<br/>
<p>Have alerts sent to OpsGenie.</p>
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12">
<label htmlFor="api-key">API Key</label>
<input className="form-control" id="api-key" type="text" ref={(r) => this.apiKey = r} defaultValue={apiKey || ''}></input>
<label className="form-helper">Note: a value of <code>true</code> indicates the OpsGenie API key has been set</label>
</div>
<TagInput title="Teams" onAddTag={this.handleAddTeam} onDeleteTag={this.handleDeleteTeam} tags={currentTeams} />
<TagInput title="Recipients" onAddTag={this.handleAddRecipient} onDeleteTag={this.handleDeleteRecipient} tags={currentRecipients} />
<div className="form-group form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button className="btn btn-block btn-primary" type="submit">Save</button>
</div>
</form>
</div>
);
},
});
const TagInput = React.createClass({
propTypes: {
onAddTag: func.isRequired,
onDeleteTag: func.isRequired,
tags: arrayOf(string).isRequired,
title: string.isRequired,
},
handleAddTag(e) {
if (e.key === 'Enter') {
e.preventDefault();
const newItem = e.target.value.trim();
const {tags, onAddTag} = this.props;
if (!this.shouldAddToList(newItem, tags)) {
return;
}
this.input.value = '';
onAddTag(newItem);
}
},
shouldAddToList(item, tags) {
return (!_.isEmpty(item) && !tags.find(l => l === item));
},
render() {
const {title, tags, onDeleteTag} = this.props;
return (
<div className="form-group col-xs-12">
<label htmlFor={title}>{title}</label>
<input
placeholder={`Type and hit 'Enter' to add to list of ${title}`}
autoComplete="off"
className="form-control"
id={title} type="text"
ref={(r) => this.input = r}
onKeyDown={this.handleAddTag}>
</input>
<Tags tags={tags} onDeleteTag={onDeleteTag}/>
</div>
);
},
});
const Tags = React.createClass({
propTypes: {
tags: arrayOf(string),
onDeleteTag: func,
},
render() {
const {tags, onDeleteTag} = this.props;
return (
<div className="input-tag-list">
{
tags.map((item) => {
return (
<Tag key={item} item={item} onDelete={onDeleteTag} />
);
})
}
</div>
);
},
});
const Tag = React.createClass({
propTypes: {
item: string,
onDelete: func,
},
render() {
const {item, onDelete} = this.props;
return (
<span key={item} className="input-tag-item">
<span>{item}</span>
<span className="icon remove" onClick={() => onDelete(item)}></span>
</span>
);
},
});
export default OpsGenieConfig;

View File

@ -26,6 +26,7 @@
@import 'layout/sidebar.scss';
// Components
@import 'components/input-tag-list';
@import 'components/group-by-time-dropdown';
@import 'components/page-header-dropdown';
@import 'components/multi-select-dropdown';

View File

@ -0,0 +1,32 @@
.input-tag-list {
padding: 0 11px;
margin: 2px 0px;
font-size: 0;
}
$input-tag-item-height: 24px;
.input-tag-item {
display: inline-block;
white-space: nowrap;
height: $input-tag-item-height;
line-height: $input-tag-item-height;
padding: 0 9px;
border-radius: 3px;
font-size: 12px;
font-weight: 600;
background-color: $g5-pepper;
color: $g18-cloud;
cursor: default;
margin: 2px;
}
.input-tag-item .icon {
transition: color 0.25s ease;
margin-left: 6px;
position: relative;
top: -0.5px;
&:hover {
color: $c-dreamsicle;
cursor: pointer;
}
}