Merge pull request #1724 from influxdata/feature/pushover_support-1680
Add Pushover alert supportpull/10616/head
commit
d6db7ee084
|
@ -11,6 +11,7 @@
|
|||
1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards
|
||||
1. [#1752](https://github.com/influxdata/chronograf/pull/1752): Clarify BoltPath server flag help text by making example the default path
|
||||
1. [#1738](https://github.com/influxdata/chronograf/pull/1738): Add shared secret JWT authorization to InfluxDB
|
||||
1. [#1724](https://github.com/influxdata/chronograf/pull/1724): Add Pushover alert support
|
||||
|
||||
### UI Improvements
|
||||
1. [#1707](https://github.com/influxdata/chronograf/pull/1707): Polish alerts table in status page to wrap text less
|
||||
|
|
|
@ -21,7 +21,7 @@ func kapaHandler(handler string) (string, error) {
|
|||
return "email", nil
|
||||
case "http":
|
||||
return "post", nil
|
||||
case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec", "log":
|
||||
case "alerta", "sensu", "slack", "email", "talk", "telegram", "post", "tcp", "exec", "log", "pushover":
|
||||
return handler, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported alert handler %s", handler)
|
||||
|
|
|
@ -412,7 +412,6 @@ func Reverse(script chronograf.TICKScript) (chronograf.AlertRule, error) {
|
|||
rule.Query.RetentionPolicy = commonVars.RP
|
||||
rule.Query.Measurement = commonVars.Measurement
|
||||
rule.Query.GroupBy.Tags = commonVars.GroupBy
|
||||
|
||||
if commonVars.Filter.Operator == "==" {
|
||||
rule.Query.AreTagsAccepted = true
|
||||
}
|
||||
|
@ -492,6 +491,7 @@ func extractAlertNodes(p *pipeline.Pipeline, rule *chronograf.AlertRule) {
|
|||
extractSlack(t, rule)
|
||||
extractTalk(t, rule)
|
||||
extractTelegram(t, rule)
|
||||
extractPushover(t, rule)
|
||||
extractTCP(t, rule)
|
||||
extractLog(t, rule)
|
||||
extractExec(t, rule)
|
||||
|
@ -501,7 +501,7 @@ func extractAlertNodes(p *pipeline.Pipeline, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractHipchat(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.HipChatHandlers == nil {
|
||||
if len(node.HipChatHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "hipchat")
|
||||
|
@ -527,7 +527,7 @@ func extractHipchat(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractOpsgenie(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.OpsGenieHandlers == nil {
|
||||
if len(node.OpsGenieHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "opsgenie")
|
||||
|
@ -553,7 +553,7 @@ func extractOpsgenie(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractPagerduty(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.PagerDutyHandlers == nil {
|
||||
if len(node.PagerDutyHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "pagerduty")
|
||||
|
@ -572,7 +572,7 @@ func extractPagerduty(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractVictorops(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.VictorOpsHandlers == nil {
|
||||
if len(node.VictorOpsHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "victorops")
|
||||
|
@ -591,7 +591,7 @@ func extractVictorops(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractEmail(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.EmailHandlers == nil {
|
||||
if len(node.EmailHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "smtp")
|
||||
|
@ -607,7 +607,7 @@ func extractEmail(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractPost(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.HTTPPostHandlers == nil {
|
||||
if len(node.HTTPPostHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "http")
|
||||
|
@ -640,7 +640,7 @@ func extractPost(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractAlerta(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.AlertaHandlers == nil {
|
||||
if len(node.AlertaHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "alerta")
|
||||
|
@ -709,7 +709,7 @@ func extractAlerta(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractSensu(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.SensuHandlers == nil {
|
||||
if len(node.SensuHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "sensu")
|
||||
|
@ -721,7 +721,7 @@ func extractSensu(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractSlack(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.SlackHandlers == nil {
|
||||
if len(node.SlackHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "slack")
|
||||
|
@ -753,7 +753,7 @@ func extractSlack(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
rule.AlertNodes = append(rule.AlertNodes, alert)
|
||||
}
|
||||
func extractTalk(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.TalkHandlers == nil {
|
||||
if len(node.TalkHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "talk")
|
||||
|
@ -764,7 +764,7 @@ func extractTalk(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
rule.AlertNodes = append(rule.AlertNodes, alert)
|
||||
}
|
||||
func extractTelegram(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.TelegramHandlers == nil {
|
||||
if len(node.TelegramHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "telegram")
|
||||
|
@ -802,7 +802,7 @@ func extractTelegram(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractTCP(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.TcpHandlers == nil {
|
||||
if len(node.TcpHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "tcp")
|
||||
|
@ -819,7 +819,7 @@ func extractTCP(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractLog(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.LogHandlers == nil {
|
||||
if len(node.LogHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "log")
|
||||
|
@ -836,7 +836,7 @@ func extractLog(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
}
|
||||
|
||||
func extractExec(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if node.ExecHandlers == nil {
|
||||
if len(node.ExecHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "exec")
|
||||
|
@ -851,3 +851,51 @@ func extractExec(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
|||
|
||||
rule.AlertNodes = append(rule.AlertNodes, alert)
|
||||
}
|
||||
|
||||
func extractPushover(node *pipeline.AlertNode, rule *chronograf.AlertRule) {
|
||||
if len(node.PushoverHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
rule.Alerts = append(rule.Alerts, "pushover")
|
||||
a := node.PushoverHandlers[0]
|
||||
alert := chronograf.KapacitorNode{
|
||||
Name: "pushover",
|
||||
}
|
||||
|
||||
if a.Device != "" {
|
||||
alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
|
||||
Name: "device",
|
||||
Args: []string{a.Device},
|
||||
})
|
||||
}
|
||||
|
||||
if a.Title != "" {
|
||||
alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
|
||||
Name: "title",
|
||||
Args: []string{a.Title},
|
||||
})
|
||||
}
|
||||
|
||||
if a.URL != "" {
|
||||
alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
|
||||
Name: "URL",
|
||||
Args: []string{a.URL},
|
||||
})
|
||||
}
|
||||
|
||||
if a.URLTitle != "" {
|
||||
alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
|
||||
Name: "URLTitle",
|
||||
Args: []string{a.URLTitle},
|
||||
})
|
||||
}
|
||||
|
||||
if a.Sound != "" {
|
||||
alert.Properties = append(alert.Properties, chronograf.KapacitorProperty{
|
||||
Name: "sound",
|
||||
Args: []string{a.Sound},
|
||||
})
|
||||
}
|
||||
|
||||
rule.AlertNodes = append(rule.AlertNodes, alert)
|
||||
}
|
||||
|
|
|
@ -2714,6 +2714,7 @@
|
|||
"hipchat",
|
||||
"opsgenie",
|
||||
"pagerduty",
|
||||
"pushover",
|
||||
"victorops",
|
||||
"smtp",
|
||||
"email",
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
updateMessage,
|
||||
updateAlerts,
|
||||
updateAlertNodes,
|
||||
updateAlertProperty,
|
||||
updateRuleName,
|
||||
deleteRuleSuccess,
|
||||
updateRuleStatusSuccess,
|
||||
|
@ -200,6 +201,106 @@ describe('Kapacitor.Reducers.rules', () => {
|
|||
expect(newState[ruleID].details).to.equal(details)
|
||||
})
|
||||
|
||||
it('can update properties', () => {
|
||||
const ruleID = 1
|
||||
|
||||
const alertNodeName = 'pushover'
|
||||
|
||||
const alertProperty1_Name = 'device'
|
||||
const alertProperty1_ArgsOrig =
|
||||
'pineapple_kingdom_control_room,bob_cOreos_watch'
|
||||
const alertProperty1_ArgsDiff = 'pineapple_kingdom_control_tower'
|
||||
|
||||
const alertProperty2_Name = 'URLTitle'
|
||||
const alertProperty2_ArgsOrig = 'Cubeapple Rising'
|
||||
const alertProperty2_ArgsDiff = 'Cubeapple Falling'
|
||||
|
||||
const alertProperty1_Orig = {
|
||||
name: alertProperty1_Name,
|
||||
args: [alertProperty1_ArgsOrig],
|
||||
}
|
||||
const alertProperty1_Diff = {
|
||||
name: alertProperty1_Name,
|
||||
args: [alertProperty1_ArgsDiff],
|
||||
}
|
||||
const alertProperty2_Orig = {
|
||||
name: alertProperty2_Name,
|
||||
args: [alertProperty2_ArgsOrig],
|
||||
}
|
||||
const alertProperty2_Diff = {
|
||||
name: alertProperty2_Name,
|
||||
args: [alertProperty2_ArgsDiff],
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
[ruleID]: {
|
||||
id: ruleID,
|
||||
alertNodes: [
|
||||
{
|
||||
name: 'pushover',
|
||||
args: null,
|
||||
properties: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const getAlertPropertyArgs = (matchState, propertyName) =>
|
||||
matchState[ruleID].alertNodes
|
||||
.find(node => node.name === alertNodeName)
|
||||
.properties.find(property => property.name === propertyName).args[0]
|
||||
|
||||
// add first property
|
||||
let newState = reducer(
|
||||
initialState,
|
||||
updateAlertProperty(ruleID, alertNodeName, alertProperty1_Orig)
|
||||
)
|
||||
expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
|
||||
alertProperty1_ArgsOrig
|
||||
)
|
||||
|
||||
// change first property
|
||||
newState = reducer(
|
||||
initialState,
|
||||
updateAlertProperty(ruleID, alertNodeName, alertProperty1_Diff)
|
||||
)
|
||||
expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
|
||||
alertProperty1_ArgsDiff
|
||||
)
|
||||
|
||||
// add second property
|
||||
newState = reducer(
|
||||
initialState,
|
||||
updateAlertProperty(ruleID, alertNodeName, alertProperty2_Orig)
|
||||
)
|
||||
expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
|
||||
alertProperty1_ArgsDiff
|
||||
)
|
||||
expect(getAlertPropertyArgs(newState, alertProperty2_Name)).to.equal(
|
||||
alertProperty2_ArgsOrig
|
||||
)
|
||||
expect(
|
||||
newState[ruleID].alertNodes.find(node => node.name === alertNodeName)
|
||||
.properties.length
|
||||
).to.equal(2)
|
||||
|
||||
// change second property
|
||||
newState = reducer(
|
||||
initialState,
|
||||
updateAlertProperty(ruleID, alertNodeName, alertProperty2_Diff)
|
||||
)
|
||||
expect(getAlertPropertyArgs(newState, alertProperty1_Name)).to.equal(
|
||||
alertProperty1_ArgsDiff
|
||||
)
|
||||
expect(getAlertPropertyArgs(newState, alertProperty2_Name)).to.equal(
|
||||
alertProperty2_ArgsDiff
|
||||
)
|
||||
expect(
|
||||
newState[ruleID].alertNodes.find(node => node.name === alertNodeName)
|
||||
.properties.length
|
||||
).to.equal(2)
|
||||
})
|
||||
|
||||
it('can update status', () => {
|
||||
const ruleID = 1
|
||||
const status = 'enabled'
|
||||
|
|
|
@ -114,6 +114,15 @@ export function updateDetails(ruleID, details) {
|
|||
}
|
||||
}
|
||||
|
||||
export const updateAlertProperty = (ruleID, alertNodeName, alertProperty) => ({
|
||||
type: 'UPDATE_RULE_ALERT_PROPERTY',
|
||||
payload: {
|
||||
ruleID,
|
||||
alertNodeName,
|
||||
alertProperty,
|
||||
},
|
||||
})
|
||||
|
||||
export function updateAlerts(ruleID, alerts) {
|
||||
return {
|
||||
type: 'UPDATE_RULE_ALERTS',
|
||||
|
@ -124,12 +133,12 @@ export function updateAlerts(ruleID, alerts) {
|
|||
}
|
||||
}
|
||||
|
||||
export function updateAlertNodes(ruleID, alertType, alertNodesText) {
|
||||
export function updateAlertNodes(ruleID, alertNodeName, alertNodesText) {
|
||||
return {
|
||||
type: 'UPDATE_RULE_ALERT_NODES',
|
||||
payload: {
|
||||
ruleID,
|
||||
alertType,
|
||||
alertNodeName,
|
||||
alertNodesText,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
HipChatConfig,
|
||||
OpsGenieConfig,
|
||||
PagerDutyConfig,
|
||||
PushoverConfig,
|
||||
SensuConfig,
|
||||
SlackConfig,
|
||||
SMTPConfig,
|
||||
|
@ -124,99 +125,97 @@ class AlertTabs extends Component {
|
|||
this.handleTest('slack', properties)
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
const supportedConfigs = {
|
||||
alerta: {
|
||||
type: 'Alerta',
|
||||
component: (
|
||||
renderComponent: () =>
|
||||
<AlertaConfig
|
||||
onSave={p => this.handleSaveConfig('alerta', p)}
|
||||
config={this.getSection(configSections, 'alerta')}
|
||||
/>
|
||||
),
|
||||
/>,
|
||||
},
|
||||
{
|
||||
type: 'SMTP',
|
||||
component: (
|
||||
<SMTPConfig
|
||||
onSave={p => this.handleSaveConfig('smtp', p)}
|
||||
config={this.getSection(configSections, 'smtp')}
|
||||
/>
|
||||
),
|
||||
hipchat: {
|
||||
type: 'HipChat',
|
||||
renderComponent: () =>
|
||||
<HipChatConfig
|
||||
onSave={p => this.handleSaveConfig('hipchat', p)}
|
||||
config={this.getSection(configSections, 'hipchat')}
|
||||
/>,
|
||||
},
|
||||
{
|
||||
opsgenie: {
|
||||
type: 'OpsGenie',
|
||||
renderComponent: () =>
|
||||
<OpsGenieConfig
|
||||
onSave={p => this.handleSaveConfig('opsgenie', p)}
|
||||
config={this.getSection(configSections, 'opsgenie')}
|
||||
/>,
|
||||
},
|
||||
pagerduty: {
|
||||
type: 'PagerDuty',
|
||||
renderComponent: () =>
|
||||
<PagerDutyConfig
|
||||
onSave={p => this.handleSaveConfig('pagerduty', p)}
|
||||
config={this.getSection(configSections, 'pagerduty')}
|
||||
/>,
|
||||
},
|
||||
pushover: {
|
||||
type: 'Pushover',
|
||||
renderComponent: () =>
|
||||
<PushoverConfig
|
||||
onSave={p => this.handleSaveConfig('pushover', p)}
|
||||
config={this.getSection(configSections, 'pushover')}
|
||||
/>,
|
||||
},
|
||||
sensu: {
|
||||
type: 'Sensu',
|
||||
renderComponent: () =>
|
||||
<SensuConfig
|
||||
onSave={p => this.handleSaveConfig('sensu', p)}
|
||||
config={this.getSection(configSections, 'sensu')}
|
||||
/>,
|
||||
},
|
||||
slack: {
|
||||
type: 'Slack',
|
||||
component: (
|
||||
renderComponent: () =>
|
||||
<SlackConfig
|
||||
onSave={p => this.handleSaveConfig('slack', p)}
|
||||
onTest={test}
|
||||
config={this.getSection(configSections, 'slack')}
|
||||
/>
|
||||
),
|
||||
/>,
|
||||
},
|
||||
{
|
||||
type: 'VictorOps',
|
||||
component: (
|
||||
<VictorOpsConfig
|
||||
onSave={p => this.handleSaveConfig('victorops', p)}
|
||||
config={this.getSection(configSections, 'victorops')}
|
||||
/>
|
||||
),
|
||||
smtp: {
|
||||
type: 'SMTP',
|
||||
renderComponent: () =>
|
||||
<SMTPConfig
|
||||
onSave={p => this.handleSaveConfig('smtp', p)}
|
||||
config={this.getSection(configSections, 'smtp')}
|
||||
/>,
|
||||
},
|
||||
{
|
||||
type: 'Telegram',
|
||||
component: (
|
||||
<TelegramConfig
|
||||
onSave={p => this.handleSaveConfig('telegram', p)}
|
||||
config={this.getSection(configSections, 'telegram')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'OpsGenie',
|
||||
component: (
|
||||
<OpsGenieConfig
|
||||
onSave={p => this.handleSaveConfig('opsgenie', p)}
|
||||
config={this.getSection(configSections, 'opsgenie')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'PagerDuty',
|
||||
component: (
|
||||
<PagerDutyConfig
|
||||
onSave={p => this.handleSaveConfig('pagerduty', p)}
|
||||
config={this.getSection(configSections, 'pagerduty')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'HipChat',
|
||||
component: (
|
||||
<HipChatConfig
|
||||
onSave={p => this.handleSaveConfig('hipchat', p)}
|
||||
config={this.getSection(configSections, 'hipchat')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'Sensu',
|
||||
component: (
|
||||
<SensuConfig
|
||||
onSave={p => this.handleSaveConfig('sensu', p)}
|
||||
config={this.getSection(configSections, 'sensu')}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
talk: {
|
||||
type: 'Talk',
|
||||
component: (
|
||||
renderComponent: () =>
|
||||
<TalkConfig
|
||||
onSave={p => this.handleSaveConfig('talk', p)}
|
||||
config={this.getSection(configSections, 'talk')}
|
||||
/>
|
||||
),
|
||||
/>,
|
||||
},
|
||||
]
|
||||
telegram: {
|
||||
type: 'Telegram',
|
||||
renderComponent: () =>
|
||||
<TelegramConfig
|
||||
onSave={p => this.handleSaveConfig('telegram', p)}
|
||||
config={this.getSection(configSections, 'telegram')}
|
||||
/>,
|
||||
},
|
||||
victorops: {
|
||||
type: 'VictorOps',
|
||||
renderComponent: () =>
|
||||
<VictorOpsConfig
|
||||
onSave={p => this.handleSaveConfig('victorops', p)}
|
||||
config={this.getSection(configSections, 'victorops')}
|
||||
/>,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -228,17 +227,31 @@ class AlertTabs extends Component {
|
|||
|
||||
<Tabs tabContentsClass="config-endpoint">
|
||||
<TabList customClass="config-endpoint--tabs">
|
||||
{tabs.map((t, i) =>
|
||||
<Tab key={tabs[i].type}>
|
||||
{tabs[i].type}
|
||||
</Tab>
|
||||
{_.reduce(
|
||||
configSections,
|
||||
(acc, _cur, k) =>
|
||||
supportedConfigs[k]
|
||||
? acc.concat(
|
||||
<Tab key={supportedConfigs[k].type}>
|
||||
{supportedConfigs[k].type}
|
||||
</Tab>
|
||||
)
|
||||
: acc,
|
||||
[]
|
||||
)}
|
||||
</TabList>
|
||||
<TabPanels customClass="config-endpoint--tab-contents">
|
||||
{tabs.map((t, i) =>
|
||||
<TabPanel key={tabs[i].type}>
|
||||
{t.component}
|
||||
</TabPanel>
|
||||
{_.reduce(
|
||||
configSections,
|
||||
(acc, _cur, k) =>
|
||||
supportedConfigs[k]
|
||||
? acc.concat(
|
||||
<TabPanel key={supportedConfigs[k].type}>
|
||||
{supportedConfigs[k].renderComponent()}
|
||||
</TabPanel>
|
||||
)
|
||||
: acc,
|
||||
[]
|
||||
)}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
const CodeData = ({onClickTemplate, template}) =>
|
||||
<code
|
||||
className="rule-builder--message-template"
|
||||
data-tip={template.text}
|
||||
onClick={onClickTemplate}
|
||||
>
|
||||
{template.label}
|
||||
</code>
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
CodeData.propTypes = {
|
||||
onClickTemplate: func,
|
||||
template: shape({
|
||||
label: string,
|
||||
text: string,
|
||||
}),
|
||||
}
|
||||
|
||||
export default CodeData
|
|
@ -1,41 +1,35 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
import RuleMessageAlertConfig from 'src/kapacitor/components/RuleMessageAlertConfig'
|
||||
import RuleMessageOptions from 'src/kapacitor/components/RuleMessageOptions'
|
||||
import RuleMessageText from 'src/kapacitor/components/RuleMessageText'
|
||||
import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates'
|
||||
|
||||
import {RULE_MESSAGE_TEMPLATES as templates, DEFAULT_ALERTS} from '../constants'
|
||||
import {DEFAULT_ALERTS, RULE_ALERT_OPTIONS} from 'src/kapacitor/constants'
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
class RuleMessage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
export const RuleMessage = React.createClass({
|
||||
propTypes: {
|
||||
rule: shape({}).isRequired,
|
||||
actions: shape({
|
||||
updateMessage: func.isRequired,
|
||||
updateDetails: func.isRequired,
|
||||
}).isRequired,
|
||||
enabledAlerts: arrayOf(string.isRequired).isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
selectedAlert: null,
|
||||
selectedAlertProperty: null,
|
||||
this.state = {
|
||||
selectedAlertNodeName: null,
|
||||
}
|
||||
},
|
||||
|
||||
this.handleChangeMessage = ::this.handleChangeMessage
|
||||
this.handleChooseAlert = ::this.handleChooseAlert
|
||||
}
|
||||
|
||||
handleChangeMessage() {
|
||||
const {actions, rule} = this.props
|
||||
actions.updateMessage(rule.id, this.message.value)
|
||||
},
|
||||
}
|
||||
|
||||
handleChooseAlert(item) {
|
||||
const {actions} = this.props
|
||||
actions.updateAlerts(item.ruleID, [item.text])
|
||||
actions.updateAlertNodes(item.ruleID, item.text, '')
|
||||
this.setState({selectedAlert: item.text})
|
||||
},
|
||||
this.setState({selectedAlertNodeName: item.text})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {rule, actions, enabledAlerts} = this.props
|
||||
|
@ -43,13 +37,14 @@ export const RuleMessage = React.createClass({
|
|||
return {text, ruleID: rule.id}
|
||||
})
|
||||
|
||||
const alerts = enabledAlerts
|
||||
.map(text => {
|
||||
const alerts = [
|
||||
...defaultAlertEndpoints,
|
||||
...enabledAlerts.map(text => {
|
||||
return {text, ruleID: rule.id}
|
||||
})
|
||||
.concat(defaultAlertEndpoints)
|
||||
}),
|
||||
]
|
||||
|
||||
const selectedAlert = rule.alerts[0] || alerts[0].text
|
||||
const selectedAlertNodeName = rule.alerts[0] || alerts[0].text
|
||||
|
||||
return (
|
||||
<div className="rule-section">
|
||||
|
@ -58,96 +53,53 @@ export const RuleMessage = React.createClass({
|
|||
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
|
||||
<p>Send this Alert to:</p>
|
||||
<ul className="nav nav-tablist nav-tablist-sm nav-tablist-malachite">
|
||||
{alerts.map(alert =>
|
||||
<li
|
||||
key={alert.text}
|
||||
className={classnames({
|
||||
active: alert.text === selectedAlert,
|
||||
})}
|
||||
onClick={() => this.handleChooseAlert(alert)}
|
||||
>
|
||||
{alert.text}
|
||||
</li>
|
||||
)}
|
||||
{alerts
|
||||
// only display alert endpoints that have rule alert options configured
|
||||
.filter(alert =>
|
||||
Object.keys(RULE_ALERT_OPTIONS).includes(alert.text)
|
||||
)
|
||||
.map(alert =>
|
||||
<li
|
||||
key={alert.text}
|
||||
className={classnames({
|
||||
active: alert.text === selectedAlertNodeName,
|
||||
})}
|
||||
onClick={() => this.handleChooseAlert(alert)}
|
||||
>
|
||||
{alert.text}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<RuleMessageAlertConfig
|
||||
updateAlertNodes={actions.updateAlertNodes}
|
||||
alert={selectedAlert}
|
||||
<RuleMessageOptions
|
||||
rule={rule}
|
||||
alertNodeName={selectedAlertNodeName}
|
||||
updateAlertNodes={actions.updateAlertNodes}
|
||||
updateDetails={actions.updateDetails}
|
||||
updateAlertProperty={actions.updateAlertProperty}
|
||||
/>
|
||||
{selectedAlert === 'smtp'
|
||||
? <div className="rule-section--border-bottom">
|
||||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
placeholder="Email body text goes here"
|
||||
ref={r => (this.details = r)}
|
||||
onChange={() =>
|
||||
actions.updateDetails(rule.id, this.details.value)}
|
||||
value={rule.details}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
ref={r => (this.message = r)}
|
||||
onChange={() => actions.updateMessage(rule.id, this.message.value)}
|
||||
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields "value" }}"
|
||||
value={rule.message}
|
||||
spellCheck={false}
|
||||
<RuleMessageText rule={rule} updateMessage={actions.updateMessage} />
|
||||
<RuleMessageTemplates
|
||||
rule={rule}
|
||||
updateMessage={actions.updateMessage}
|
||||
/>
|
||||
<div className="rule-section--row rule-section--row-last rule-section--border-top">
|
||||
<p>Templates:</p>
|
||||
{Object.keys(templates).map(t => {
|
||||
return (
|
||||
<CodeData
|
||||
key={t}
|
||||
template={templates[t]}
|
||||
onClickTemplate={() =>
|
||||
actions.updateMessage(
|
||||
rule.id,
|
||||
`${this.message.value} ${templates[t].label}`
|
||||
)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: -4}}
|
||||
class="influx-tooltip kapacitor-tooltip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const CodeData = React.createClass({
|
||||
propTypes: {
|
||||
onClickTemplate: func,
|
||||
template: shape({
|
||||
label: string,
|
||||
text: string,
|
||||
}),
|
||||
},
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
|
||||
render() {
|
||||
const {onClickTemplate, template} = this.props
|
||||
const {label, text} = template
|
||||
|
||||
return (
|
||||
<code
|
||||
className="rule-builder--message-template"
|
||||
data-tip={text}
|
||||
onClick={onClickTemplate}
|
||||
>
|
||||
{label}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
})
|
||||
RuleMessage.propTypes = {
|
||||
rule: shape({}).isRequired,
|
||||
actions: shape({
|
||||
updateAlertNodes: func.isRequired,
|
||||
updateMessage: func.isRequired,
|
||||
updateDetails: func.isRequired,
|
||||
updateAlertProperty: func.isRequired,
|
||||
}).isRequired,
|
||||
enabledAlerts: arrayOf(string.isRequired).isRequired,
|
||||
}
|
||||
|
||||
export default RuleMessage
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
import {
|
||||
DEFAULT_ALERT_PLACEHOLDERS,
|
||||
DEFAULT_ALERT_LABELS,
|
||||
ALERT_NODES_ACCESSORS,
|
||||
} from '../constants'
|
||||
|
||||
const RuleMessageAlertConfig = ({updateAlertNodes, alert, rule}) => {
|
||||
if (!Object.keys(DEFAULT_ALERT_PLACEHOLDERS).find(a => a === alert)) {
|
||||
return null
|
||||
}
|
||||
if (!Object.keys(DEFAULT_ALERT_LABELS).find(a => a === alert)) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div className="rule-section--row rule-section--border-bottom">
|
||||
<p>
|
||||
{DEFAULT_ALERT_LABELS[alert]}
|
||||
</p>
|
||||
<input
|
||||
id="alert-input"
|
||||
className="form-control input-sm form-malachite"
|
||||
style={{flex: '1 0 0'}}
|
||||
type="text"
|
||||
placeholder={DEFAULT_ALERT_PLACEHOLDERS[alert]}
|
||||
onChange={e => updateAlertNodes(rule.id, alert, e.target.value)}
|
||||
value={ALERT_NODES_ACCESSORS[alert](rule)}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
RuleMessageAlertConfig.propTypes = {
|
||||
updateAlertNodes: func.isRequired,
|
||||
alert: string,
|
||||
rule: shape({}).isRequired,
|
||||
}
|
||||
|
||||
export default RuleMessageAlertConfig
|
|
@ -0,0 +1,135 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
|
||||
import {
|
||||
RULE_ALERT_OPTIONS,
|
||||
ALERT_NODES_ACCESSORS,
|
||||
} from 'src/kapacitor/constants'
|
||||
|
||||
class RuleMessageOptions extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.getAlertPropertyValue = ::this.getAlertPropertyValue
|
||||
}
|
||||
|
||||
getAlertPropertyValue(properties, name) {
|
||||
if (properties) {
|
||||
const alertNodeProperty = properties.find(
|
||||
property => property.name === name
|
||||
)
|
||||
if (alertNodeProperty) {
|
||||
return alertNodeProperty.args
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
rule,
|
||||
alertNodeName,
|
||||
updateAlertNodes,
|
||||
updateDetails,
|
||||
updateAlertProperty,
|
||||
} = this.props
|
||||
const {args, details, properties} = RULE_ALERT_OPTIONS[alertNodeName]
|
||||
|
||||
return (
|
||||
<div>
|
||||
{args
|
||||
? <div className="rule-section--row rule-section--border-bottom">
|
||||
<p>
|
||||
{args.label}
|
||||
</p>
|
||||
<input
|
||||
id="alert-input"
|
||||
className="form-control input-sm form-malachite"
|
||||
style={{flex: '1 0 0'}}
|
||||
type="text"
|
||||
placeholder={args.placeholder}
|
||||
onChange={e =>
|
||||
updateAlertNodes(rule.id, alertNodeName, e.target.value)}
|
||||
value={ALERT_NODES_ACCESSORS[alertNodeName](rule)}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
{properties && properties.length
|
||||
? <div
|
||||
className="rule-section--row rule-section--border-bottom"
|
||||
style={{display: 'block'}}
|
||||
>
|
||||
<p>Optional Alert Parameters</p>
|
||||
<div style={{display: 'flex', flexWrap: 'wrap'}}>
|
||||
{properties.map(({name: propertyName, label, placeholder}) =>
|
||||
<div
|
||||
key={propertyName}
|
||||
style={{display: 'block', flex: '0 0 33.33%'}}
|
||||
>
|
||||
<label
|
||||
htmlFor={label}
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span style={{flex: '0 0 auto'}}>
|
||||
{label}
|
||||
</span>
|
||||
<input
|
||||
name={label}
|
||||
className="form-control input-sm form-malachite"
|
||||
style={{
|
||||
margin: '0 15px 0 5px',
|
||||
flex: '1 0 0',
|
||||
}}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
onChange={e =>
|
||||
updateAlertProperty(rule.id, alertNodeName, {
|
||||
name: propertyName,
|
||||
args: [e.target.value],
|
||||
})}
|
||||
value={this.getAlertPropertyValue(
|
||||
rule.alertNodes[0].properties,
|
||||
propertyName
|
||||
)}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
{details
|
||||
? <div className="rule-section--border-bottom">
|
||||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
placeholder={details.placeholder ? details.placeholder : ''}
|
||||
ref={r => (this.details = r)}
|
||||
onChange={() => updateDetails(rule.id, this.details.value)}
|
||||
value={rule.details}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
RuleMessageOptions.propTypes = {
|
||||
rule: shape({}).isRequired,
|
||||
alertNodeName: string,
|
||||
updateAlertNodes: func.isRequired,
|
||||
updateDetails: func.isRequired,
|
||||
updateAlertProperty: func.isRequired,
|
||||
}
|
||||
|
||||
export default RuleMessageOptions
|
|
@ -0,0 +1,49 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import _ from 'lodash'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
import CodeData from 'src/kapacitor/components/CodeData'
|
||||
|
||||
import {RULE_MESSAGE_TEMPLATES} from 'src/kapacitor/constants'
|
||||
|
||||
// needs to be React Component for CodeData click handler to work
|
||||
class RuleMessageTemplates extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {rule, updateMessage} = this.props
|
||||
|
||||
return (
|
||||
<div className="rule-section--row rule-section--row-last rule-section--border-top">
|
||||
<p>Templates:</p>
|
||||
{_.map(RULE_MESSAGE_TEMPLATES, (template, key) => {
|
||||
return (
|
||||
<CodeData
|
||||
key={key}
|
||||
template={template}
|
||||
onClickTemplate={() =>
|
||||
updateMessage(rule.id, `${rule.message} ${template.label}`)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: -4}}
|
||||
class="influx-tooltip kapacitor-tooltip"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape} = PropTypes
|
||||
|
||||
RuleMessageTemplates.propTypes = {
|
||||
rule: shape().isRequired,
|
||||
updateMessage: func.isRequired,
|
||||
}
|
||||
|
||||
export default RuleMessageTemplates
|
|
@ -0,0 +1,31 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
|
||||
class RuleMessageText extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {rule, updateMessage} = this.props
|
||||
|
||||
return (
|
||||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
ref={r => (this.message = r)}
|
||||
onChange={() => updateMessage(rule.id, this.message.value)}
|
||||
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields "value" }}"
|
||||
value={rule.message}
|
||||
spellCheck={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape} = PropTypes
|
||||
|
||||
RuleMessageText.propTypes = {
|
||||
rule: shape().isRequired,
|
||||
updateMessage: func.isRequired,
|
||||
}
|
||||
|
||||
export default RuleMessageText
|
|
@ -0,0 +1,98 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
|
||||
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
import {PUSHOVER_DOCS_LINK} from 'src/kapacitor/copy'
|
||||
|
||||
class PushoverConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleSaveAlert = ::this.handleSaveAlert
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
token: this.token.value,
|
||||
url: this.url.value,
|
||||
'user-key': this.userKey.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
const {token, url} = options
|
||||
const userKey = options['user-key']
|
||||
|
||||
return (
|
||||
<form onSubmit={this.handleSaveAlert}>
|
||||
<div className="form-group col-xs-12">
|
||||
<label htmlFor="user-key">
|
||||
User Key
|
||||
<QuestionMarkTooltip
|
||||
tipID="token"
|
||||
tipContent={PUSHOVER_DOCS_LINK}
|
||||
/>
|
||||
</label>
|
||||
<RedactedInput
|
||||
defaultValue={userKey}
|
||||
id="user-key"
|
||||
refFunc={r => (this.userKey = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group col-xs-12">
|
||||
<label htmlFor="token">
|
||||
Token
|
||||
<QuestionMarkTooltip
|
||||
tipID="token"
|
||||
tipContent={PUSHOVER_DOCS_LINK}
|
||||
/>
|
||||
</label>
|
||||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => (this.token = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group col-xs-12">
|
||||
<label htmlFor="url">Pushover URL</label>
|
||||
<input
|
||||
className="form-control"
|
||||
id="url"
|
||||
type="text"
|
||||
ref={r => (this.url = r)}
|
||||
defaultValue={url || ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group-submit col-xs-12 text-center">
|
||||
<button className="btn btn-primary" type="submit">
|
||||
Update Pushover Config
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
PushoverConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
token: bool.isRequired,
|
||||
'user-key': bool.isRequired,
|
||||
url: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default PushoverConfig
|
|
@ -2,6 +2,7 @@ import AlertaConfig from './AlertaConfig'
|
|||
import HipChatConfig from './HipChatConfig'
|
||||
import OpsGenieConfig from './OpsGenieConfig'
|
||||
import PagerDutyConfig from './PagerDutyConfig'
|
||||
import PushoverConfig from './PushoverConfig'
|
||||
import SensuConfig from './SensuConfig'
|
||||
import SlackConfig from './SlackConfig'
|
||||
import SMTPConfig from './SMTPConfig'
|
||||
|
@ -14,6 +15,7 @@ export {
|
|||
HipChatConfig,
|
||||
OpsGenieConfig,
|
||||
PagerDutyConfig,
|
||||
PushoverConfig,
|
||||
SensuConfig,
|
||||
SlackConfig,
|
||||
SMTPConfig,
|
||||
|
|
|
@ -40,6 +40,7 @@ export const ALERTS = [
|
|||
'hipchat',
|
||||
'opsgenie',
|
||||
'pagerduty',
|
||||
'pushover',
|
||||
'sensu',
|
||||
'slack',
|
||||
'smtp',
|
||||
|
@ -82,23 +83,64 @@ export const RULE_MESSAGE_TEMPLATES = {
|
|||
|
||||
export const DEFAULT_ALERTS = ['http', 'tcp', 'exec', 'log']
|
||||
|
||||
export const DEFAULT_ALERT_LABELS = {
|
||||
http: 'URL:',
|
||||
tcp: 'Address:',
|
||||
exec: 'Add Command (Arguments separated by Spaces):',
|
||||
log: 'File:',
|
||||
smtp: 'Email Addresses (Separated by Spaces):',
|
||||
slack: 'Send alerts to Slack channel:',
|
||||
alerta: 'Paste Alerta TICKscript:',
|
||||
}
|
||||
export const DEFAULT_ALERT_PLACEHOLDERS = {
|
||||
http: 'Ex: http://example.com/api/alert',
|
||||
tcp: 'Ex: exampleendpoint.com:5678',
|
||||
exec: 'Ex: woogie boogie',
|
||||
log: 'Ex: /tmp/alerts.log',
|
||||
smtp: 'Ex: benedict@domain.com delaney@domain.com susan@domain.com',
|
||||
slack: '#alerts',
|
||||
alerta: 'alerta()',
|
||||
export const RULE_ALERT_OPTIONS = {
|
||||
http: {
|
||||
args: {
|
||||
label: 'URL:',
|
||||
placeholder: 'Ex: http://example.com/api/alert',
|
||||
},
|
||||
},
|
||||
tcp: {
|
||||
args: {
|
||||
label: 'Address:',
|
||||
placeholder: 'Ex: exampleendpoint.com:5678',
|
||||
},
|
||||
},
|
||||
exec: {
|
||||
args: {
|
||||
label: 'Add Command (Arguments separated by Spaces):',
|
||||
placeholder: 'Ex: woogie boogie',
|
||||
},
|
||||
},
|
||||
log: {
|
||||
args: {
|
||||
label: 'File:',
|
||||
placeholder: 'Ex: /tmp/alerts.log',
|
||||
},
|
||||
},
|
||||
smtp: {
|
||||
args: {
|
||||
label: 'Email Addresses (Separated by Spaces):',
|
||||
placeholder:
|
||||
'Ex: benedict@domain.com delaney@domain.com susan@domain.com',
|
||||
},
|
||||
details: {placeholder: 'Email body text goes here'},
|
||||
},
|
||||
slack: {
|
||||
args: {
|
||||
label: 'Send alerts to Slack channel:',
|
||||
placeholder: '#alerts',
|
||||
},
|
||||
},
|
||||
alerta: {
|
||||
args: {
|
||||
label: 'Paste Alerta TICKscript:',
|
||||
placeholder: 'alerta()',
|
||||
},
|
||||
},
|
||||
pushover: {
|
||||
properties: [
|
||||
{
|
||||
name: 'device',
|
||||
label: 'Device:',
|
||||
placeholder: 'dv1,dv2 (Comma Separated)',
|
||||
},
|
||||
{name: 'title', label: 'Title:', placeholder: 'Important Message'},
|
||||
{name: 'URL', label: 'URL:', placeholder: 'https://influxdata.com'},
|
||||
{name: 'URLTitle', label: 'URL Title:', placeholder: 'InfluxData'},
|
||||
{name: 'sound', label: 'Sound:', placeholder: 'alien'},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const ALERT_NODES_ACCESSORS = {
|
||||
|
|
|
@ -82,7 +82,6 @@ class KapacitorRulePage extends Component {
|
|||
if (!query) {
|
||||
return <div className="page-spinner" />
|
||||
}
|
||||
|
||||
return (
|
||||
<KapacitorRule
|
||||
source={source}
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
// HipChat
|
||||
const hipchatTokenLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#hipchat-api-access-token'
|
||||
export const HIPCHAT_TOKEN_TIP = `<p>Need help creating a token?<br/>Check out <a href='${hipchatTokenLink}' target='_blank'>these steps</a>.</p>`
|
||||
|
||||
// Pushover
|
||||
const pushoverDocsLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/nodes/alert_node/#pushover'
|
||||
export const PUSHOVER_DOCS_LINK = `<p>Need help setting up Pushover?<br/>Check out <a href='${pushoverDocsLink}' target='_blank'>the docs here</a>.</p>`
|
||||
|
||||
// Telegram
|
||||
const telegramChatIDLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-chat-id'
|
||||
export const TELEGRAM_CHAT_ID_TIP = `<p>Need help finding your chat id?<br/>Check out <a target='_blank' href='${telegramChatIDLink}'>these steps</a>.</p>`
|
||||
|
@ -5,7 +16,3 @@ export const TELEGRAM_CHAT_ID_TIP = `<p>Need help finding your chat id?<br/>Chec
|
|||
const telegramTokenLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-api-access-token'
|
||||
export const TELEGRAM_TOKEN_TIP = `<p>Need help finding your token?<br/>Check out <a target='_blank' href='${telegramTokenLink}'>these steps</a>.</p>`
|
||||
|
||||
const hipchatTokenLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#hipchat-api-access-token'
|
||||
export const HIPCHAT_TOKEN_TIP = `<p>Need help creating a token?<br/>Check out <a href='${hipchatTokenLink}' target='_blank'>these steps</a>.</p>`
|
||||
|
|
|
@ -83,18 +83,22 @@ export default function rules(state = {}, action) {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: refactor to allow multiple alert nodes, and change name + refactor
|
||||
// functionality to clearly disambiguate creating an alert node, changing its
|
||||
// type, adding other alert nodes to a single rule, and updating an alert node's
|
||||
// properties vs args vs details vs message.
|
||||
case 'UPDATE_RULE_ALERT_NODES': {
|
||||
const {ruleID, alertType, alertNodesText} = action.payload
|
||||
const {ruleID, alertNodeName, alertNodesText} = action.payload
|
||||
|
||||
let alertNodesByType
|
||||
|
||||
switch (alertType) {
|
||||
switch (alertNodeName) {
|
||||
case 'http':
|
||||
case 'tcp':
|
||||
case 'log':
|
||||
alertNodesByType = [
|
||||
{
|
||||
name: alertType,
|
||||
name: alertNodeName,
|
||||
args: [alertNodesText],
|
||||
properties: [],
|
||||
},
|
||||
|
@ -104,7 +108,7 @@ export default function rules(state = {}, action) {
|
|||
case 'smtp':
|
||||
alertNodesByType = [
|
||||
{
|
||||
name: alertType,
|
||||
name: alertNodeName,
|
||||
args: alertNodesText.split(' '),
|
||||
properties: [],
|
||||
},
|
||||
|
@ -113,7 +117,7 @@ export default function rules(state = {}, action) {
|
|||
case 'slack':
|
||||
alertNodesByType = [
|
||||
{
|
||||
name: alertType,
|
||||
name: alertNodeName,
|
||||
properties: [
|
||||
{
|
||||
name: 'channel',
|
||||
|
@ -126,14 +130,21 @@ export default function rules(state = {}, action) {
|
|||
case 'alerta':
|
||||
alertNodesByType = [
|
||||
{
|
||||
name: alertType,
|
||||
name: alertNodeName,
|
||||
args: [],
|
||||
properties: parseAlerta(alertNodesText),
|
||||
},
|
||||
]
|
||||
break
|
||||
case 'pushover':
|
||||
default:
|
||||
alertNodesByType = []
|
||||
alertNodesByType = [
|
||||
{
|
||||
name: alertNodeName,
|
||||
args: [],
|
||||
properties: [],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return Object.assign({}, state, {
|
||||
|
@ -143,6 +154,38 @@ export default function rules(state = {}, action) {
|
|||
})
|
||||
}
|
||||
|
||||
case 'UPDATE_RULE_ALERT_PROPERTY': {
|
||||
const {ruleID, alertNodeName, alertProperty} = action.payload
|
||||
|
||||
const newAlertNodes = state[ruleID].alertNodes.map(alertNode => {
|
||||
if (alertNode.name !== alertNodeName) {
|
||||
return alertNode
|
||||
}
|
||||
let matched = false
|
||||
|
||||
if (!alertNode.properties) {
|
||||
alertNode.properties = []
|
||||
}
|
||||
alertNode.properties = alertNode.properties.map(property => {
|
||||
if (property.name === alertProperty.name) {
|
||||
matched = true
|
||||
return alertProperty
|
||||
}
|
||||
return property
|
||||
})
|
||||
if (!matched) {
|
||||
alertNode.properties.push(alertProperty)
|
||||
}
|
||||
return alertNode
|
||||
})
|
||||
|
||||
return {
|
||||
...state,
|
||||
[ruleID]: {...state[ruleID]},
|
||||
alertNodes: newAlertNodes,
|
||||
}
|
||||
}
|
||||
|
||||
case 'UPDATE_RULE_NAME': {
|
||||
const {ruleID, name} = action.payload
|
||||
return Object.assign({}, state, {
|
||||
|
|
Loading…
Reference in New Issue