Merge pull request #376 from influxdata/feature/rules-index-#307
Refactor KapacitorRulePagepull/10616/head
commit
ccd98b2b21
|
@ -37,9 +37,9 @@ export default function queryConfigs(state = {}, action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'LOAD_KAPACITOR_QUERY': {
|
case 'LOAD_KAPACITOR_QUERY': {
|
||||||
const {queryID, query} = action.payload;
|
const {query} = action.payload;
|
||||||
const nextState = Object.assign({}, state, {
|
const nextState = Object.assign({}, state, {
|
||||||
[queryID]: query,
|
[query.id]: query,
|
||||||
});
|
});
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
|
|
|
@ -119,6 +119,7 @@ const Root = React.createClass({
|
||||||
<Route path="alerts" component={AlertsApp} />
|
<Route path="alerts" component={AlertsApp} />
|
||||||
<Route path="alert-rules" component={KapacitorRulesPage} />
|
<Route path="alert-rules" component={KapacitorRulesPage} />
|
||||||
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
||||||
|
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="tasks" component={TasksPage} />
|
<Route path="tasks" component={TasksPage} />
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
|
|
|
@ -1,14 +1,38 @@
|
||||||
import uuid from 'node-uuid';
|
import uuid from 'node-uuid';
|
||||||
|
import {getRules, getRule} from 'src/kapacitor/apis';
|
||||||
|
import {getKapacitor} from 'src/shared/apis';
|
||||||
|
|
||||||
export function fetchRule() { // ruleID
|
export function fetchRule(source, ruleID) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
// do some ajax to get the rule. put it in the payload
|
getKapacitor(source).then((kapacitor) => {
|
||||||
const queryID = uuid.v4();
|
getRule(kapacitor, ruleID).then(({data: rule}) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'LOAD_RULE',
|
type: 'LOAD_RULE',
|
||||||
payload: {},
|
payload: {
|
||||||
|
rule: Object.assign(rule, {queryID: rule.query.id}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'LOAD_KAPACITOR_QUERY',
|
||||||
|
payload: {
|
||||||
|
query: rule.query,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadDefaultRule() {
|
||||||
|
return (dispatch) => {
|
||||||
|
const queryID = uuid.v4();
|
||||||
|
dispatch({
|
||||||
|
type: 'LOAD_DEFAULT_RULE',
|
||||||
|
payload: {
|
||||||
|
queryID,
|
||||||
|
},
|
||||||
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'ADD_KAPACITOR_QUERY',
|
type: 'ADD_KAPACITOR_QUERY',
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -18,22 +42,17 @@ export function fetchRule() { // ruleID
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadDefaultRule() {
|
export function fetchRules(source) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
const queryID = uuid.v4();
|
getKapacitor(source).then((kapacitor) => {
|
||||||
const ruleID = uuid.v4();
|
getRules(kapacitor).then(({data: {rules}}) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'LOAD_DEFAULT_RULE',
|
type: 'LOAD_RULES',
|
||||||
payload: {
|
payload: {
|
||||||
queryID,
|
rules,
|
||||||
ruleID,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
dispatch({
|
});
|
||||||
type: 'ADD_KAPACITOR_QUERY',
|
|
||||||
payload: {
|
|
||||||
queryId: queryID,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,3 +14,18 @@ export function getRules(kapacitor) {
|
||||||
url: kapacitor.links.rules,
|
url: kapacitor.links.rules,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRule(kapacitor, ruleID) {
|
||||||
|
return AJAX({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${kapacitor.links.rules}/${ruleID}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function editRule(rule) {
|
||||||
|
return AJAX({
|
||||||
|
method: 'PUT',
|
||||||
|
url: rule.links.self,
|
||||||
|
data: rule,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -24,3 +24,5 @@ export const PERIODS = ['1m', '5m', '10m', '30m', '1h', '2h', '1d'];
|
||||||
export const CHANGES = ['change', '% change'];
|
export const CHANGES = ['change', '% change'];
|
||||||
export const SHIFTS = ['1m', '5m', '10m', '30m', '1h', '2h', '1d'];
|
export const SHIFTS = ['1m', '5m', '10m', '30m', '1h', '2h', '1d'];
|
||||||
export const ALERTS = ['hipchat', 'opsgenie', 'pagerduty', 'sensu', 'slack', 'smtp', 'talk', 'telegram', 'victorops'];
|
export const ALERTS = ['hipchat', 'opsgenie', 'pagerduty', 'sensu', 'slack', 'smtp', 'talk', 'telegram', 'victorops'];
|
||||||
|
|
||||||
|
export const DEFAULT_RULE_ID = 'DEFAULT_RULE_ID';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
|
import {withRouter} from 'react-router';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import DataSection from '../components/DataSection';
|
import DataSection from '../components/DataSection';
|
||||||
|
@ -12,8 +13,8 @@ import LineGraph from 'shared/components/LineGraph';
|
||||||
const RefreshingLineGraph = AutoRefresh(LineGraph);
|
const RefreshingLineGraph = AutoRefresh(LineGraph);
|
||||||
import {getKapacitor, getKapacitorConfig} from 'shared/apis/index';
|
import {getKapacitor, getKapacitorConfig} from 'shared/apis/index';
|
||||||
import Dropdown from 'shared/components/Dropdown';
|
import Dropdown from 'shared/components/Dropdown';
|
||||||
import {ALERTS} from 'src/kapacitor/constants';
|
import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants';
|
||||||
import {createRule} from 'src/kapacitor/apis';
|
import {createRule, editRule} from 'src/kapacitor/apis';
|
||||||
|
|
||||||
export const KapacitorRulePage = React.createClass({
|
export const KapacitorRulePage = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -49,10 +50,15 @@ export const KapacitorRulePage = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isEditing() {
|
||||||
|
const {params} = this.props;
|
||||||
|
return params.ruleID && params.ruleID !== 'new';
|
||||||
|
},
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {ruleID} = false; // this.props.params;
|
const {ruleID} = this.props.params;
|
||||||
if (ruleID) {
|
if (this.isEditing()) {
|
||||||
this.props.kapacitorActions.fetchRule(ruleID);
|
this.props.kapacitorActions.fetchRule(this.props.source, ruleID);
|
||||||
} else {
|
} else {
|
||||||
this.props.kapacitorActions.loadDefaultRule();
|
this.props.kapacitorActions.loadDefaultRule();
|
||||||
}
|
}
|
||||||
|
@ -64,25 +70,34 @@ export const KapacitorRulePage = React.createClass({
|
||||||
});
|
});
|
||||||
this.setState({kapacitor, enabledAlerts});
|
this.setState({kapacitor, enabledAlerts});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.props.addFlashMessage({type: 'failure', message: `There was a problem communicating with Kapacitor`});
|
this.props.addFlashMessage({type: 'failure', text: `There was a problem communicating with Kapacitor`});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.props.addFlashMessage({type: 'failure', message: `We couldn't find a configured Kapacitor for this source`});
|
this.props.addFlashMessage({type: 'failure', text: `We couldn't find a configured Kapacitor for this source`});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSave() {
|
handleSave() {
|
||||||
const {queryConfigs, rules} = this.props;
|
const {queryConfigs, rules, params, source} = this.props;
|
||||||
const rule = rules[Object.keys(rules)[0]]; // this.props.params.taskID
|
if (this.isEditing()) { // If we are editing updated rule if not, create a new one
|
||||||
|
editRule(rules[params.ruleID]).then(() => {
|
||||||
|
this.props.addFlashMessage({type: 'success', text: `Rule successfully updated!`});
|
||||||
|
}).catch(() => {
|
||||||
|
this.props.addFlashMessage({type: 'failure', text: `There was a problem updating the rule`});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const rule = rules[DEFAULT_RULE_ID];
|
||||||
const newRule = Object.assign({}, rule, {
|
const newRule = Object.assign({}, rule, {
|
||||||
query: queryConfigs[rule.queryID],
|
query: queryConfigs[rule.queryID],
|
||||||
});
|
});
|
||||||
delete newRule.queryID;
|
delete newRule.queryID;
|
||||||
createRule(this.state.kapacitor, newRule).then(() => {
|
createRule(this.state.kapacitor, newRule).then(() => {
|
||||||
// maybe update the default rule in redux state.. and update the URL
|
this.props.router.push(`sources/${source.id}/alert-rules`);
|
||||||
|
this.props.addFlashMessage({type: 'success', text: `Rule successfully created`});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.props.addFlashMessage({type: 'failure', message: `There was a problem creating the rule`});
|
this.props.addFlashMessage({type: 'failure', text: `There was a problem creating the rule`});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleChooseAlert(item) {
|
handleChooseAlert(item) {
|
||||||
|
@ -132,13 +147,13 @@ export const KapacitorRulePage = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {rules, queryConfigs, source} = this.props;
|
const {rules, queryConfigs, source, params} = this.props;
|
||||||
const rule = rules[Object.keys(rules)[0]]; // this.props.params.ruleID
|
const rule = this.isEditing() ? rules[params.ruleID] : rules[DEFAULT_RULE_ID];
|
||||||
const query = rule && queryConfigs[rule.queryID];
|
const query = rule && queryConfigs[rule.queryID];
|
||||||
const autoRefreshMs = 30000;
|
const autoRefreshMs = 30000;
|
||||||
|
|
||||||
if (!query) { // or somethin like that
|
if (!query) {
|
||||||
return null; // or a spinner or somethin
|
return <div className="page-spinner"></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryText = selectStatement({lower: 'now() - 15m'}, query);
|
const queryText = selectStatement({lower: 'now() - 15m'}, query);
|
||||||
|
@ -218,7 +233,7 @@ export const KapacitorRulePage = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="kapacitor-rule-section">
|
<div className="kapacitor-rule-section">
|
||||||
<h3>Message</h3>
|
<h3>Message</h3>
|
||||||
<textarea ref={(r) => this.message = r} onChange={() => this.handleMessageChange(rule)} />
|
<textarea ref={(r) => this.message = r} value={rule.message} onChange={() => this.handleMessageChange(rule)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -256,4 +271,4 @@ function mapDispatchToProps(dispatch) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(KapacitorRulePage);
|
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(KapacitorRulePage));
|
||||||
|
|
|
@ -1,39 +1,36 @@
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {bindActionCreators} from 'redux';
|
||||||
import {Link} from 'react-router';
|
import {Link} from 'react-router';
|
||||||
import {getRules} from 'src/kapacitor/apis';
|
import * as kapacitorActionCreators from 'src/kapacitor/actions/view';
|
||||||
import {getKapacitor} from 'src/shared/apis';
|
|
||||||
|
|
||||||
export const KapacitorRulesPage = React.createClass({
|
export const KapacitorRulesPage = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
source: PropTypes.shape({
|
source: PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.string.isRequired, // 'influx-enterprise'
|
|
||||||
username: PropTypes.string.isRequired,
|
|
||||||
links: PropTypes.shape({
|
links: PropTypes.shape({
|
||||||
|
proxy: PropTypes.string.isRequired,
|
||||||
|
self: PropTypes.string.isRequired,
|
||||||
kapacitors: PropTypes.string.isRequired,
|
kapacitors: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}),
|
||||||
|
}),
|
||||||
|
rules: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
trigger: PropTypes.string.isRequired,
|
||||||
|
message: PropTypes.string.isRequired,
|
||||||
|
alerts: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
||||||
|
})).isRequired,
|
||||||
|
actions: PropTypes.shape({
|
||||||
|
fetchRules: PropTypes.func.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
addFlashMessage: PropTypes.func,
|
addFlashMessage: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
rules: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
getKapacitor(this.props.source).then((kapacitor) => {
|
this.props.actions.fetchRules(this.props.source);
|
||||||
getRules(kapacitor).then(({data: {rules}}) => {
|
|
||||||
this.setState({rules});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {rules} = this.state;
|
const {source, rules} = this.props;
|
||||||
const {source} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="kapacitor-rules-page">
|
<div className="kapacitor-rules-page">
|
||||||
|
@ -48,6 +45,7 @@ export const KapacitorRulesPage = React.createClass({
|
||||||
<div className="panel panel-minimal">
|
<div className="panel panel-minimal">
|
||||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||||
<h2 className="panel-title">Alert Rules</h2>
|
<h2 className="panel-title">Alert Rules</h2>
|
||||||
|
<Link to={`/sources/${source.id}/alert-rules/new`} className="btn btn-sm btn-primary">Add New Rule</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
<table className="table v-center">
|
<table className="table v-center">
|
||||||
|
@ -82,4 +80,16 @@ export const KapacitorRulesPage = React.createClass({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default KapacitorRulesPage;
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
rules: Object.values(state.rules),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(kapacitorActionCreators, dispatch),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(KapacitorRulesPage);
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import {defaultRuleConfigs} from 'src/kapacitor/constants';
|
import {defaultRuleConfigs, DEFAULT_RULE_ID} from 'src/kapacitor/constants';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default function rules(state = {}, action) {
|
export default function rules(state = {}, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'LOAD_DEFAULT_RULE': {
|
case 'LOAD_DEFAULT_RULE': {
|
||||||
const {queryID, ruleID} = action.payload;
|
const {queryID} = action.payload;
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[ruleID]: {
|
[DEFAULT_RULE_ID]: {
|
||||||
id: ruleID,
|
id: DEFAULT_RULE_ID,
|
||||||
queryID,
|
queryID,
|
||||||
trigger: 'threshold',
|
trigger: 'threshold',
|
||||||
values: defaultRuleConfigs.threshold,
|
values: defaultRuleConfigs.threshold,
|
||||||
|
@ -18,10 +19,16 @@ export default function rules(state = {}, action) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'LOAD_RULES': {
|
||||||
|
const theRules = action.payload.rules;
|
||||||
|
const ruleIDs = theRules.map(r => r.id);
|
||||||
|
return _.zipObject(ruleIDs, theRules);
|
||||||
|
}
|
||||||
|
|
||||||
case 'LOAD_RULE': {
|
case 'LOAD_RULE': {
|
||||||
const {ruleID, rule} = action.payload;
|
const {rule} = action.payload;
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[ruleID]: rule,
|
[rule.id]: rule,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,13 @@ export const ManageSources = React.createClass({
|
||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
pathname: PropTypes.string.isRequired,
|
pathname: PropTypes.string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
source: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
links: PropTypes.shape({
|
||||||
|
proxy: PropTypes.string.isRequired,
|
||||||
|
self: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
|
@ -59,7 +66,7 @@ export const ManageSources = React.createClass({
|
||||||
<div className="panel panel-minimal">
|
<div className="panel panel-minimal">
|
||||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||||
<h2 className="panel-title">{sourcesTitle}</h2>
|
<h2 className="panel-title">{sourcesTitle}</h2>
|
||||||
<Link to={`/sources/1/manage-sources/new`} className="btn btn-sm btn-primary">Add New Source</Link>
|
<Link to={`/sources/${this.props.source.id}/manage-sources/new`} className="btn btn-sm btn-primary">Add New Source</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
<div className="table-responsive margin-bottom-zero">
|
<div className="table-responsive margin-bottom-zero">
|
||||||
|
|
Loading…
Reference in New Issue