Refactor KapacitorRulePage to work for new and existing rules, and add new rule button
parent
a98a533f93
commit
20b5c96e29
|
@ -37,9 +37,9 @@ export default function queryConfigs(state = {}, action) {
|
|||
}
|
||||
|
||||
case 'LOAD_KAPACITOR_QUERY': {
|
||||
const {queryID, query} = action.payload;
|
||||
const {query} = action.payload;
|
||||
const nextState = Object.assign({}, state, {
|
||||
[queryID]: query,
|
||||
[query.id]: query,
|
||||
});
|
||||
|
||||
return nextState;
|
||||
|
|
|
@ -119,6 +119,7 @@ const Root = React.createClass({
|
|||
<Route path="alerts" component={AlertsApp} />
|
||||
<Route path="alert-rules" component={KapacitorRulesPage} />
|
||||
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
||||
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
||||
</Route>
|
||||
<Route path="tasks" component={TasksPage} />
|
||||
<Route path="*" component={NotFound} />
|
||||
|
|
|
@ -1,14 +1,38 @@
|
|||
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) => {
|
||||
getKapacitor(source).then((kapacitor) => {
|
||||
getRule(kapacitor, ruleID).then(({data: rule}) => {
|
||||
dispatch({
|
||||
type: 'LOAD_RULE',
|
||||
payload: {
|
||||
rule: Object.assign(rule, {queryID: rule.query.id}),
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'LOAD_KAPACITOR_QUERY',
|
||||
payload: {
|
||||
query: rule.query,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loadDefaultRule() {
|
||||
return (dispatch) => {
|
||||
// do some ajax to get the rule. put it in the payload
|
||||
const queryID = uuid.v4();
|
||||
dispatch({
|
||||
type: 'LOAD_RULE',
|
||||
payload: {},
|
||||
type: 'LOAD_DEFAULT_RULE',
|
||||
payload: {
|
||||
queryID,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'ADD_KAPACITOR_QUERY',
|
||||
payload: {
|
||||
|
@ -18,22 +42,17 @@ export function fetchRule() { // ruleID
|
|||
};
|
||||
}
|
||||
|
||||
export function loadDefaultRule() {
|
||||
export function fetchRules(source) {
|
||||
return (dispatch) => {
|
||||
const queryID = uuid.v4();
|
||||
const ruleID = uuid.v4();
|
||||
dispatch({
|
||||
type: 'LOAD_DEFAULT_RULE',
|
||||
payload: {
|
||||
queryID,
|
||||
ruleID,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: 'ADD_KAPACITOR_QUERY',
|
||||
payload: {
|
||||
queryId: queryID,
|
||||
},
|
||||
getKapacitor(source).then((kapacitor) => {
|
||||
getRules(kapacitor).then(({data: {rules}}) => {
|
||||
dispatch({
|
||||
type: 'LOAD_RULES',
|
||||
payload: {
|
||||
rules,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,3 +14,18 @@ export function getRules(kapacitor) {
|
|||
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 SHIFTS = ['1m', '5m', '10m', '30m', '1h', '2h', '1d'];
|
||||
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 {withRouter} from 'react-router';
|
||||
import {connect} from 'react-redux';
|
||||
import _ from 'lodash';
|
||||
import DataSection from '../components/DataSection';
|
||||
|
@ -12,8 +13,8 @@ import LineGraph from 'shared/components/LineGraph';
|
|||
const RefreshingLineGraph = AutoRefresh(LineGraph);
|
||||
import {getKapacitor, getKapacitorConfig} from 'shared/apis/index';
|
||||
import Dropdown from 'shared/components/Dropdown';
|
||||
import {ALERTS} from 'src/kapacitor/constants';
|
||||
import {createRule} from 'src/kapacitor/apis';
|
||||
import {ALERTS, DEFAULT_RULE_ID} from 'src/kapacitor/constants';
|
||||
import {createRule, editRule} from 'src/kapacitor/apis';
|
||||
|
||||
export const KapacitorRulePage = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -49,10 +50,15 @@ export const KapacitorRulePage = React.createClass({
|
|||
};
|
||||
},
|
||||
|
||||
isEditing() {
|
||||
const {params} = this.props;
|
||||
return params.ruleID && params.ruleID !== 'new';
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
const {ruleID} = false; // this.props.params;
|
||||
if (ruleID) {
|
||||
this.props.kapacitorActions.fetchRule(ruleID);
|
||||
const {ruleID} = this.props.params;
|
||||
if (this.isEditing()) {
|
||||
this.props.kapacitorActions.fetchRule(this.props.source, ruleID);
|
||||
} else {
|
||||
this.props.kapacitorActions.loadDefaultRule();
|
||||
}
|
||||
|
@ -64,25 +70,34 @@ export const KapacitorRulePage = React.createClass({
|
|||
});
|
||||
this.setState({kapacitor, enabledAlerts});
|
||||
}).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(() => {
|
||||
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() {
|
||||
const {queryConfigs, rules} = this.props;
|
||||
const rule = rules[Object.keys(rules)[0]]; // this.props.params.taskID
|
||||
const newRule = Object.assign({}, rule, {
|
||||
query: queryConfigs[rule.queryID],
|
||||
});
|
||||
delete newRule.queryID;
|
||||
createRule(this.state.kapacitor, newRule).then(() => {
|
||||
// maybe update the default rule in redux state.. and update the URL
|
||||
}).catch(() => {
|
||||
this.props.addFlashMessage({type: 'failure', message: `There was a problem creating the rule`});
|
||||
});
|
||||
const {queryConfigs, rules, params, source} = this.props;
|
||||
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, {
|
||||
query: queryConfigs[rule.queryID],
|
||||
});
|
||||
delete newRule.queryID;
|
||||
createRule(this.state.kapacitor, newRule).then(() => {
|
||||
this.props.router.push(`sources/${source.id}/alert-rules`);
|
||||
this.props.addFlashMessage({type: 'success', text: `Rule successfully created`});
|
||||
}).catch(() => {
|
||||
this.props.addFlashMessage({type: 'failure', text: `There was a problem creating the rule`});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleChooseAlert(item) {
|
||||
|
@ -132,13 +147,13 @@ export const KapacitorRulePage = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
const {rules, queryConfigs, source} = this.props;
|
||||
const rule = rules[Object.keys(rules)[0]]; // this.props.params.ruleID
|
||||
const {rules, queryConfigs, source, params} = this.props;
|
||||
const rule = this.isEditing() ? rules[params.ruleID] : rules[DEFAULT_RULE_ID];
|
||||
const query = rule && queryConfigs[rule.queryID];
|
||||
const autoRefreshMs = 30000;
|
||||
|
||||
if (!query) { // or somethin like that
|
||||
return null; // or a spinner or somethin
|
||||
if (!query) {
|
||||
return <div className="page-spinner"></div>;
|
||||
}
|
||||
|
||||
const queryText = selectStatement({lower: 'now() - 15m'}, query);
|
||||
|
@ -218,7 +233,7 @@ export const KapacitorRulePage = React.createClass({
|
|||
return (
|
||||
<div className="kapacitor-rule-section">
|
||||
<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>
|
||||
);
|
||||
},
|
||||
|
@ -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 {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {Link} from 'react-router';
|
||||
import {getRules} from 'src/kapacitor/apis';
|
||||
import {getKapacitor} from 'src/shared/apis';
|
||||
import * as kapacitorActionCreators from 'src/kapacitor/actions/view';
|
||||
|
||||
export const KapacitorRulesPage = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired, // 'influx-enterprise'
|
||||
username: PropTypes.string.isRequired,
|
||||
links: PropTypes.shape({
|
||||
proxy: PropTypes.string.isRequired,
|
||||
self: 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,
|
||||
addFlashMessage: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
rules: [],
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
getKapacitor(this.props.source).then((kapacitor) => {
|
||||
getRules(kapacitor).then(({data: {rules}}) => {
|
||||
this.setState({rules});
|
||||
});
|
||||
});
|
||||
this.props.actions.fetchRules(this.props.source);
|
||||
},
|
||||
|
||||
render() {
|
||||
const {rules} = this.state;
|
||||
const {source} = this.props;
|
||||
const {source, rules} = this.props;
|
||||
|
||||
return (
|
||||
<div className="kapacitor-rules-page">
|
||||
|
@ -48,6 +45,7 @@ export const KapacitorRulesPage = React.createClass({
|
|||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<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 className="panel-body">
|
||||
<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) {
|
||||
switch (action.type) {
|
||||
case 'LOAD_DEFAULT_RULE': {
|
||||
const {queryID, ruleID} = action.payload;
|
||||
const {queryID} = action.payload;
|
||||
return Object.assign({}, state, {
|
||||
[ruleID]: {
|
||||
id: ruleID,
|
||||
[DEFAULT_RULE_ID]: {
|
||||
id: DEFAULT_RULE_ID,
|
||||
queryID,
|
||||
trigger: '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': {
|
||||
const {ruleID, rule} = action.payload;
|
||||
const {rule} = action.payload;
|
||||
return Object.assign({}, state, {
|
||||
[ruleID]: rule,
|
||||
[rule.id]: rule,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,13 @@ export const ManageSources = React.createClass({
|
|||
location: PropTypes.shape({
|
||||
pathname: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
source: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
links: PropTypes.shape({
|
||||
proxy: PropTypes.string.isRequired,
|
||||
self: PropTypes.string.isRequired,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
getInitialState() {
|
||||
return {
|
||||
|
@ -59,7 +66,7 @@ export const ManageSources = React.createClass({
|
|||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<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 className="panel-body">
|
||||
<div className="table-responsive margin-bottom-zero">
|
||||
|
|
Loading…
Reference in New Issue