First attempt to make an alert rule builder
parent
bbf73ab09e
commit
67c8343417
|
@ -7,7 +7,7 @@ import App from 'src/App';
|
|||
import AlertsApp from 'src/alerts';
|
||||
import CheckDataNodes from 'src/CheckDataNodes';
|
||||
import {HostsPage, HostPage} from 'src/hosts';
|
||||
import {KapacitorPage, KapacitorTasksPage} from 'src/kapacitor';
|
||||
import {KapacitorPage, KapacitorRulePage, KapacitorTasksPage} from 'src/kapacitor';
|
||||
import QueriesPage from 'src/queries';
|
||||
import TasksPage from 'src/tasks';
|
||||
import RetentionPoliciesPage from 'src/retention_policies';
|
||||
|
@ -117,6 +117,7 @@ const Root = React.createClass({
|
|||
<Route path="kapacitor-config" component={KapacitorPage} />
|
||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||
<Route path="alerts" component={AlertsApp} />
|
||||
<Route path="alert-rules" component={KapacitorRulePage} />
|
||||
</Route>
|
||||
<Route path="tasks" component={TasksPage} />
|
||||
<Route path="*" component={NotFound} />
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import selectStatement from '../../chronograf/utils/influxql/select';
|
||||
|
||||
import DatabaseList from '../../chronograf/components/DatabaseList';
|
||||
import MeasurementList from '../../chronograf/components/MeasurementList';
|
||||
import FieldList from '../../chronograf/components/FieldList';
|
||||
import TagList from '../../chronograf/components/TagList';
|
||||
|
||||
const DB_TAB = 'databases';
|
||||
const MEASUREMENTS_TAB = 'measurments';
|
||||
const FIELDS_TAB = 'fields';
|
||||
const TAGS_TAB = 'tags';
|
||||
|
||||
export const DataSection = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({
|
||||
links: PropTypes.shape({
|
||||
kapacitors: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
addFlashMessage: PropTypes.func,
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
source: PropTypes.shape({
|
||||
links: PropTypes.shape({
|
||||
proxy: PropTypes.string.isRequired,
|
||||
self: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
getChildContext() {
|
||||
return {source: this.props.source};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
activeTab: DB_TAB,
|
||||
query: {
|
||||
id: 1,
|
||||
database: null,
|
||||
measurement: null,
|
||||
retentionPolicy: null,
|
||||
fields: [],
|
||||
tags: {},
|
||||
groupBy: {
|
||||
time: null,
|
||||
tags: [],
|
||||
},
|
||||
areTagsAccepted: true,
|
||||
rawText: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
handleChooseNamespace(namespace) {
|
||||
this.setState((oldState) => {
|
||||
const newQuery = Object.assign({}, oldState.query, namespace);
|
||||
return Object.assign({}, oldState, {query: newQuery, activeTab: MEASUREMENTS_TAB});
|
||||
});
|
||||
},
|
||||
|
||||
handleChooseMeasurement(measurement) {
|
||||
this.setState((oldState) => {
|
||||
const newQuery = Object.assign({}, oldState.query, {measurement});
|
||||
return Object.assign({}, oldState, {query: newQuery, activeTab: FIELDS_TAB});
|
||||
});
|
||||
},
|
||||
|
||||
handleToggleField({field, funcs}) {
|
||||
this.setState((oldState) => {
|
||||
const isSelected = oldState.query.fields.find((f) => f.field === field);
|
||||
if (isSelected) {
|
||||
const nextFields = oldState.query.fields.filter((f) => f.field !== field);
|
||||
if (!nextFields.length) {
|
||||
const nextGroupBy = Object.assign({}, oldState.query.groupBy, {time: null});
|
||||
return Object.assign({}, oldState, {
|
||||
query: Object.assign({}, oldState.query, {
|
||||
fields: nextFields,
|
||||
groupBy: nextGroupBy,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, oldState, {
|
||||
query: Object.assign({}, oldState.query, {
|
||||
fields: nextFields,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, oldState, {
|
||||
query: Object.assign({}, oldState.query, {
|
||||
fields: oldState.query.fields.concat({field, funcs}),
|
||||
}),
|
||||
})
|
||||
return update(queryConfig, {fields: {$push: [fieldFunc]}});
|
||||
});
|
||||
},
|
||||
|
||||
handleGroupByTime(time) {
|
||||
},
|
||||
|
||||
handleApplyFuncsToField(fieldFunc) {
|
||||
},
|
||||
|
||||
handleChooseTag(tag) {
|
||||
},
|
||||
|
||||
handleToggleTagAcceptance() {
|
||||
},
|
||||
|
||||
handleGroupByTag(tagKey) {
|
||||
},
|
||||
|
||||
handleClickTab(tab) {
|
||||
this.setState({activeTab: tab});
|
||||
},
|
||||
|
||||
render() {
|
||||
const {query} = this.state;
|
||||
const timeRange = {lower: 'now() - 15m', upper: null};
|
||||
const statement = query.rawText || selectStatement(timeRange, query) || `SELECT "fields" FROM "db"."rp"."measurement"`;
|
||||
|
||||
return (
|
||||
<div className="query-editor">
|
||||
<div className="query-editor__code">
|
||||
<pre className={classNames("", {"rq-mode": query.rawText})}><code>{statement}</code></pre>
|
||||
</div>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderEditor() {
|
||||
const {query, activeTab} = this.state;
|
||||
if (query.rawText) {
|
||||
return (
|
||||
<div className="generic-empty-state query-editor-empty-state">
|
||||
<p className="margin-bottom-zero">
|
||||
<span className="icon alert-triangle"></span>
|
||||
Only editable in the <strong>Raw Query</strong> tab.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="query-editor__tabs">
|
||||
<div className="query-editor__tabs-heading">Schema Explorer</div>
|
||||
<div onClick={_.wrap(DB_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === DB_TAB})}>Databases</div>
|
||||
<div onClick={_.wrap(MEASUREMENTS_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === MEASUREMENTS_TAB})}>Measurements</div>
|
||||
<div onClick={_.wrap(FIELDS_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === FIELDS_TAB})}>Fields</div>
|
||||
<div onClick={_.wrap(TAGS_TAB, this.handleClickTab)} className={classNames("query-editor__tab", {active: activeTab === TAGS_TAB})}>Tags</div>
|
||||
</div>
|
||||
{this.renderList()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderList() {
|
||||
const {query} = this.state;
|
||||
|
||||
switch (this.state.activeTab) {
|
||||
case DB_TAB:
|
||||
return (
|
||||
<DatabaseList
|
||||
query={query}
|
||||
onChooseNamespace={this.handleChooseNamespace}
|
||||
/>
|
||||
);
|
||||
case MEASUREMENTS_TAB:
|
||||
return (
|
||||
<MeasurementList
|
||||
query={query}
|
||||
onChooseMeasurement={this.handleChooseMeasurement}
|
||||
/>
|
||||
);
|
||||
case FIELDS_TAB:
|
||||
return (
|
||||
<FieldList
|
||||
query={query}
|
||||
onToggleField={this.handleToggleField}
|
||||
onGroupByTime={this.handleGroupByTime}
|
||||
applyFuncsToField={this.handleApplyFuncsToField}
|
||||
/>
|
||||
);
|
||||
case TAGS_TAB:
|
||||
return (
|
||||
<TagList
|
||||
query={query}
|
||||
onChooseTag={this.handleChooseTag}
|
||||
onGroupByTag={this.handleGroupByTag}
|
||||
onToggleTagAcceptance={this.handleToggleTagAcceptance}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <ul className="query-editor__list"></ul>;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default DataSection;
|
|
@ -0,0 +1,101 @@
|
|||
import React, {PropTypes} from 'react';
|
||||
import DataSection from '../components/DataSection';
|
||||
|
||||
const DATA_SECTION = 'data';
|
||||
const VALUES_SECTION = 'values';
|
||||
const MESSAGE_SECTION = 'message';
|
||||
const ALERTS_SECTION = 'alerts';
|
||||
|
||||
export const KapacitorRulePage = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({
|
||||
links: PropTypes.shape({
|
||||
self: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
addFlashMessage: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
activeSection: DATA_SECTION,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="kapacitor-rule-page">
|
||||
<div className="enterprise-header">
|
||||
<div className="enterprise-header__container">
|
||||
<div className="enterprise-header__left">
|
||||
<h1>Kapacitor Rules</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
{this.renderDataSection()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
{this.renderValuesSection()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
{this.renderMessageSection()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
{this.renderAlertsSection()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderDataSection() {
|
||||
return (
|
||||
<div className="kapacitor-rule-section">
|
||||
<h3>Data</h3>
|
||||
<DataSection source={this.props.source} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderValuesSection() {
|
||||
return (
|
||||
<div className="kapacitor-rule-section">
|
||||
<h3>Values</h3>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderMessageSection() {
|
||||
return (
|
||||
<div className="kapacitor-rule-section">
|
||||
<h3>Message</h3>
|
||||
<textarea />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
renderAlertsSection() {
|
||||
// hit kapacitor config endpoint and filter sections by the "enabled" property
|
||||
const alertOptions = ['Slack', 'VictorOps'].map((destination) => {
|
||||
return <option key={destination}>send to {destination}</option>;
|
||||
})
|
||||
return (
|
||||
<div className="kapacitor-rule-section">
|
||||
<h3>Alerts</h3>
|
||||
<p>The Alert should <select>{alertOptions}</select></p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default KapacitorRulePage;
|
|
@ -1,3 +1,4 @@
|
|||
import KapacitorPage from './containers/KapacitorPage';
|
||||
import KapacitorRulePage from './containers/KapacitorRulePage';
|
||||
import KapacitorTasksPage from './containers/KapacitorTasksPage';
|
||||
export {KapacitorPage, KapacitorTasksPage};
|
||||
export {KapacitorPage, KapacitorRulePage, KapacitorTasksPage};
|
||||
|
|
|
@ -41,8 +41,8 @@ const SideNav = React.createClass({
|
|||
</NavBlock>
|
||||
<NavBlock matcher="alerts" icon="alert-triangle" link={`${sourcePrefix}/alerts`}>
|
||||
<NavHeader link={`${sourcePrefix}/alerts`} title="Alerts" />
|
||||
<NavListItem matcher="view" link={`${sourcePrefix}/alerts`}>View</NavListItem>
|
||||
<NavListItem matcher="rules" link={`${sourcePrefix}/alert/rules`}>Rules</NavListItem>
|
||||
<NavListItem link={`${sourcePrefix}/alerts`}>View</NavListItem>
|
||||
<NavListItem link={`${sourcePrefix}/alert-rules`}>Rules</NavListItem>
|
||||
</NavBlock>
|
||||
</NavBar>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue