commit
7392848b10
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)} />;
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue