Merge pull request #1335 from influxdata/fix/sanitized

improve UX for sanitized kapacitor config
pull/10616/head
Andrew Watkins 2017-04-26 06:34:29 -07:00 committed by GitHub
commit 29cd2df8cb
15 changed files with 511 additions and 178 deletions

View File

@ -14,6 +14,7 @@
1. [#1269](https://github.com/influxdata/chronograf/issues/1269): Add more functionality to the explorer's query generation process
1. [#1318](https://github.com/influxdata/chronograf/issues/1318): Fix JWT refresh for auth-durations of zero and less than five minutes
1. [#1332](https://github.com/influxdata/chronograf/pull/1332): Remove table toggle from dashboard visualization
1. [#1335](https://github.com/influxdata/chronograf/pull/1335): Improve UX for sanitized kapacitor settings
### Features
1. [#1232](https://github.com/influxdata/chronograf/pull/1232): Fuse the query builder and raw query editor

View File

@ -2,7 +2,11 @@ import React, {Component, PropTypes} from 'react'
import _ from 'lodash'
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import {getKapacitorConfig, updateKapacitorConfigSection, testAlertOutput} from 'shared/apis'
import {
getKapacitorConfig,
updateKapacitorConfigSection,
testAlertOutput,
} from 'shared/apis'
import {
AlertaConfig,
@ -42,12 +46,17 @@ class AlertTabs extends Component {
}
refreshKapacitorConfig(kapacitor) {
getKapacitorConfig(kapacitor).then(({data: {sections}}) => {
this.setState({configSections: sections})
}).catch(() => {
this.setState({configSections: null})
this.props.addFlashMessage({type: 'error', text: 'There was an error getting the Kapacitor config'})
})
getKapacitorConfig(kapacitor)
.then(({data: {sections}}) => {
this.setState({configSections: sections})
})
.catch(() => {
this.setState({configSections: null})
this.props.addFlashMessage({
type: 'error',
text: 'There was an error getting the Kapacitor config',
})
})
}
getSection(sections, section) {
@ -57,29 +66,45 @@ class AlertTabs extends Component {
handleSaveConfig(section, properties) {
if (section !== '') {
const propsToSend = this.sanitizeProperties(section, properties)
updateKapacitorConfigSection(this.props.kapacitor, section, propsToSend).then(() => {
this.refreshKapacitorConfig(this.props.kapacitor)
this.props.addFlashMessage({type: 'success', text: `Alert for ${section} successfully saved`})
}).catch(() => {
this.props.addFlashMessage({type: 'error', text: 'There was an error saving the kapacitor config'})
})
updateKapacitorConfigSection(this.props.kapacitor, section, propsToSend)
.then(() => {
this.refreshKapacitorConfig(this.props.kapacitor)
this.props.addFlashMessage({
type: 'success',
text: `Alert for ${section} successfully saved`,
})
})
.catch(() => {
this.props.addFlashMessage({
type: 'error',
text: 'There was an error saving the kapacitor config',
})
})
}
}
handleTest(section, properties) {
const propsToSend = this.sanitizeProperties(section, properties)
testAlertOutput(this.props.kapacitor, section, propsToSend).then(() => {
this.props.addFlashMessage({type: 'success', text: 'Slack test message sent'})
}).catch(() => {
this.props.addFlashMessage({type: 'error', text: 'There was an error testing the slack alert'})
})
testAlertOutput(this.props.kapacitor, section, propsToSend)
.then(() => {
this.props.addFlashMessage({
type: 'success',
text: 'Slack test message sent',
})
})
.catch(() => {
this.props.addFlashMessage({
type: 'error',
text: 'There was an error testing the slack alert',
})
})
}
sanitizeProperties(section, properties) {
const cleanProps = Object.assign({}, properties, {enabled: true})
const {redacted} = this.getSection(this.state.configSections, section)
if (redacted && redacted.length) {
redacted.forEach((badProp) => {
redacted.forEach(badProp => {
if (properties[badProp] === 'true') {
delete cleanProps[badProp]
}
@ -95,50 +120,101 @@ class AlertTabs extends Component {
return null
}
const test = (properties) => {
const test = properties => {
this.handleTest('slack', properties)
}
const tabs = [
{
type: 'Alerta',
component: (<AlertaConfig onSave={(p) => this.handleSaveConfig('alerta', p)} config={this.getSection(configSections, 'alerta')} />),
component: (
<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')} />),
component: (
<SMTPConfig
onSave={p => this.handleSaveConfig('smtp', p)}
config={this.getSection(configSections, 'smtp')}
/>
),
},
{
type: 'Slack',
component: (<SlackConfig onSave={(p) => this.handleSaveConfig('slack', p)} onTest={test} config={this.getSection(configSections, 'slack')} />),
component: (
<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')} />),
component: (
<VictorOpsConfig
onSave={p => this.handleSaveConfig('victorops', p)}
config={this.getSection(configSections, 'victorops')}
/>
),
},
{
type: 'Telegram',
component: (<TelegramConfig onSave={(p) => this.handleSaveConfig('telegram', p)} config={this.getSection(configSections, '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')} />),
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')} />),
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')} />),
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')} />),
component: (
<SensuConfig
onSave={p => this.handleSaveConfig('sensu', p)}
config={this.getSection(configSections, 'sensu')}
/>
),
},
{
type: 'Talk',
component: (<TalkConfig onSave={(p) => this.handleSaveConfig('talk', p)} config={this.getSection(configSections, 'talk')} />),
component: (
<TalkConfig
onSave={p => this.handleSaveConfig('talk', p)}
config={this.getSection(configSections, 'talk')}
/>
),
},
]
@ -152,14 +228,12 @@ 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>))
}
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
</TabList>
<TabPanels customClass="config-endpoint--tab-contents">
{
tabs.map((t, i) => (<TabPanel key={tabs[i].type}>{t.component}</TabPanel>))
}
{tabs.map((t, i) => (
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
))}
</TabPanels>
</Tabs>
</div>
@ -167,11 +241,7 @@ class AlertTabs extends Component {
}
}
const {
func,
shape,
string,
} = PropTypes
const {func, shape, string} = PropTypes
AlertTabs.propTypes = {
source: shape({

View File

@ -1,16 +1,20 @@
import React, {PropTypes} from 'react'
import RedactedInput from './RedactedInput'
const {bool, func, shape, string} = PropTypes
const AlertaConfig = React.createClass({
propTypes: {
config: PropTypes.shape({
options: PropTypes.shape({
environment: PropTypes.string,
origin: PropTypes.string,
token: PropTypes.bool,
url: PropTypes.string,
config: shape({
options: shape({
environment: string,
origin: string,
token: bool,
url: string,
}).isRequired,
}).isRequired,
onSave: PropTypes.func.isRequired,
onSave: func.isRequired,
},
handleSaveAlert(e) {
@ -33,27 +37,50 @@ const AlertaConfig = React.createClass({
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12">
<label htmlFor="environment">Environment</label>
<input className="form-control" id="environment" type="text" ref={(r) => this.environment = r} defaultValue={environment || ''}></input>
<input
className="form-control"
id="environment"
type="text"
ref={r => this.environment = r}
defaultValue={environment || ''}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="origin">Origin</label>
<input className="form-control" id="origin" type="text" ref={(r) => this.origin = r} defaultValue={origin || ''}></input>
<input
className="form-control"
id="origin"
type="text"
ref={r => this.origin = r}
defaultValue={origin || ''}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="token">Token</label>
<input className="form-control" id="token" type="text" ref={(r) => this.token = r} defaultValue={token || ''}></input>
<label className="form-helper">Note: a value of <code>true</code> indicates the Alerta Token has been set</label>
<RedactedInput
defaultValue={token}
id="token"
refFunc={r => this.token = r}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="url">User</label>
<input className="form-control" id="url" type="text" ref={(r) => this.url = r} defaultValue={url || ''}></input>
<input
className="form-control"
id="url"
type="text"
ref={r => this.url = r}
defaultValue={url || ''}
/>
</div>
<div className="form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button className="btn btn-block btn-primary" type="submit">Save</button>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -1,13 +1,10 @@
import React, {PropTypes} from 'react'
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
import {HIPCHAT_TOKEN_TIP} from 'src/kapacitor/copy'
import RedactedInput from './RedactedInput'
const {
bool,
func,
shape,
string,
} = PropTypes
const {bool, func, shape, string} = PropTypes
const HipchatConfig = React.createClass({
propTypes: {
@ -37,7 +34,9 @@ const HipchatConfig = React.createClass({
const {options} = this.props.config
const {url, room, token} = options
const subdomain = url.replace('https://', '').replace('.hipchat.com/v2/room', '')
const subdomain = url
.replace('https://', '')
.replace('.hipchat.com/v2/room', '')
return (
<form onSubmit={this.handleSaveAlert}>
@ -48,7 +47,7 @@ const HipchatConfig = React.createClass({
id="url"
type="text"
placeholder="your-subdomain"
ref={(r) => this.url = r}
ref={r => this.url = r}
defaultValue={subdomain && subdomain.length ? subdomain : ''}
/>
</div>
@ -60,7 +59,7 @@ const HipchatConfig = React.createClass({
id="room"
type="text"
placeholder="your-hipchat-room"
ref={(r) => this.room = r}
ref={r => this.room = r}
defaultValue={room || ''}
/>
</div>
@ -68,24 +67,19 @@ const HipchatConfig = React.createClass({
<div className="form-group col-xs-12">
<label htmlFor="token">
Token
<QuestionMarkTooltip
tipID="token"
tipContent={HIPCHAT_TOKEN_TIP}
/>
<QuestionMarkTooltip tipID="token" tipContent={HIPCHAT_TOKEN_TIP} />
</label>
<input
className="form-control"
<RedactedInput
defaultValue={token}
id="token"
type="text"
placeholder="your-hipchat-token"
ref={(r) => this.token = r}
defaultValue={token || ''}
refFunc={r => this.token = r}
/>
<label className="form-helper">Note: a value of <code>true</code> indicates the HipChat token has been set</label>
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -1,14 +1,9 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
const {
array,
arrayOf,
bool,
func,
shape,
string,
} = PropTypes
import RedactedInput from './RedactedInput'
const {array, arrayOf, bool, func, shape, string} = PropTypes
const OpsGenieConfig = React.createClass({
propTypes: {
@ -47,15 +42,23 @@ const OpsGenieConfig = React.createClass({
},
handleAddRecipient(recipient) {
this.setState({currentRecipients: this.state.currentRecipients.concat(recipient)})
this.setState({
currentRecipients: this.state.currentRecipients.concat(recipient),
})
},
handleDeleteTeam(team) {
this.setState({currentTeams: this.state.currentTeams.filter(t => t !== team)})
this.setState({
currentTeams: this.state.currentTeams.filter(t => t !== team),
})
},
handleDeleteRecipient(recipient) {
this.setState({currentRecipients: this.state.currentRecipients.filter(r => r !== recipient)})
this.setState({
currentRecipients: this.state.currentRecipients.filter(
r => r !== recipient
),
})
},
render() {
@ -67,15 +70,37 @@ const OpsGenieConfig = React.createClass({
<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>
<RedactedInput
defaultValue={apiKey}
id="api-key"
refFunc={r => this.apiKey = r}
/>
<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} />
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)
@ -105,7 +130,7 @@ const TagInput = React.createClass({
},
shouldAddToList(item, tags) {
return (!_.isEmpty(item) && !tags.find(l => l === item))
return !_.isEmpty(item) && !tags.find(l => l === item)
},
render() {
@ -118,11 +143,12 @@ const TagInput = React.createClass({
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}/>
id={title}
type="text"
ref={r => this.input = r}
onKeyDown={this.handleAddTag}
/>
<Tags tags={tags} onDeleteTag={onDeleteTag} />
</div>
)
},
@ -138,13 +164,9 @@ const Tags = React.createClass({
const {tags, onDeleteTag} = this.props
return (
<div className="input-tag-list">
{
tags.map((item) => {
return (
<Tag key={item} item={item} onDelete={onDeleteTag} />
)
})
}
{tags.map(item => {
return <Tag key={item} item={item} onDelete={onDeleteTag} />
})}
</div>
)
},
@ -162,11 +184,10 @@ const Tag = React.createClass({
return (
<span key={item} className="input-tag-item">
<span>{item}</span>
<span className="icon remove" onClick={() => onDelete(item)}></span>
<span className="icon remove" onClick={() => onDelete(item)} />
</span>
)
},
})
export default OpsGenieConfig

View File

@ -31,17 +31,37 @@ const PagerDutyConfig = React.createClass({
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12">
<label htmlFor="service-key">Service Key</label>
<input className="form-control" id="service-key" type="text" ref={(r) => this.serviceKey = r} defaultValue={serviceKey || ''}></input>
<label className="form-helper">Note: a value of <code>true</code> indicates the PagerDuty service key has been set</label>
<input
className="form-control"
id="service-key"
type="text"
ref={r => this.serviceKey = r}
defaultValue={serviceKey || ''}
/>
<label className="form-helper">
Note: a value of
{' '}
<code>true</code>
{' '}
indicates the PagerDuty service key has been set
</label>
</div>
<div className="form-group col-xs-12">
<label htmlFor="url">PagerDuty URL</label>
<input className="form-control" id="url" type="text" ref={(r) => this.url = r} defaultValue={url || ''}></input>
<input
className="form-control"
id="url"
type="text"
ref={r => this.url = r}
defaultValue={url || ''}
/>
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -0,0 +1,60 @@
import React, {Component, PropTypes} from 'react'
class RedactedInput extends Component {
constructor(props) {
super(props)
this.state = {
editing: false,
}
}
render() {
const {defaultValue, id, refFunc} = this.props
const {editing} = this.state
if (defaultValue === true && !editing) {
return (
<div className="alert-value-set">
<span>
value set
<a
href="#"
onClick={() => {
this.setState({editing: true})
}}
>
(change it)
</a>
</span>
<input
className="form-control"
id={id}
type="hidden"
ref={refFunc}
defaultValue={defaultValue}
/>
</div>
)
}
return (
<input
className="form-control"
id={id}
type="text"
ref={refFunc}
defaultValue={''}
/>
)
}
}
const {bool, func, string} = PropTypes
RedactedInput.propTypes = {
id: string.isRequired,
defaultValue: bool,
refFunc: func.isRequired,
}
export default RedactedInput

View File

@ -35,31 +35,64 @@ const SMTPConfig = React.createClass({
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12 col-md-6">
<label htmlFor="smtp-host">SMTP Host</label>
<input className="form-control" id="smtp-host" type="text" ref={(r) => this.host = r} defaultValue={host || ''}></input>
<input
className="form-control"
id="smtp-host"
type="text"
ref={r => this.host = r}
defaultValue={host || ''}
/>
</div>
<div className="form-group col-xs-12 col-md-6">
<label htmlFor="smtp-port">SMTP Port</label>
<input className="form-control" id="smtp-port" type="text" ref={(r) => this.port = r} defaultValue={port || ''}></input>
<input
className="form-control"
id="smtp-port"
type="text"
ref={r => this.port = r}
defaultValue={port || ''}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="smtp-from">From Email</label>
<input className="form-control" id="smtp-from" placeholder="email@domain.com" type="text" ref={(r) => this.from = r} defaultValue={from || ''}></input>
<input
className="form-control"
id="smtp-from"
placeholder="email@domain.com"
type="text"
ref={r => this.from = r}
defaultValue={from || ''}
/>
</div>
<div className="form-group col-xs-12 col-md-6">
<label htmlFor="smtp-user">User</label>
<input className="form-control" id="smtp-user" type="text" ref={(r) => this.username = r} defaultValue={username || ''}></input>
<input
className="form-control"
id="smtp-user"
type="text"
ref={r => this.username = r}
defaultValue={username || ''}
/>
</div>
<div className="form-group col-xs-12 col-md-6">
<label htmlFor="smtp-password">Password</label>
<input className="form-control" id="smtp-password" type="password" ref={(r) => this.password = r} defaultValue={`${password}`}></input>
<input
className="form-control"
id="smtp-password"
type="password"
ref={r => this.password = r}
defaultValue={`${password}`}
/>
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -29,16 +29,30 @@ const SensuConfig = React.createClass({
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12 col-md-6">
<label htmlFor="source">Source</label>
<input className="form-control" id="source" type="text" ref={(r) => this.source = r} defaultValue={source || ''}></input>
<input
className="form-control"
id="source"
type="text"
ref={r => this.source = r}
defaultValue={source || ''}
/>
</div>
<div className="form-group col-xs-12 col-md-6">
<label htmlFor="address">Address</label>
<input className="form-control" id="address" type="text" ref={(r) => this.addr = r} defaultValue={addr || ''}></input>
<input
className="form-control"
id="address"
type="text"
ref={r => this.addr = r}
defaultValue={addr || ''}
/>
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -1,5 +1,7 @@
import React, {PropTypes} from 'react'
import RedactedInput from './RedactedInput'
const SlackConfig = React.createClass({
propTypes: {
config: PropTypes.shape({
@ -49,18 +51,40 @@ const SlackConfig = React.createClass({
return (
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12">
<label htmlFor="slack-url">Slack Webhook URL (<a href="https://api.slack.com/incoming-webhooks" target="_">see more on Slack webhooks</a>)</label>
<input className="form-control" id="slack-url" type="text" ref={(r) => this.url = r} defaultValue={url || ''}></input>
<label className="form-helper">Note: a value of <code>true</code> indicates that the Slack channel has been set</label>
<label htmlFor="slack-url">
Slack Webhook URL (
<a href="https://api.slack.com/incoming-webhooks" target="_">
see more on Slack webhooks
</a>
)
</label>
<RedactedInput
defaultValue={url}
id="url"
refFunc={r => this.url = r}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="slack-channel">Slack Channel (optional)</label>
<input className="form-control" id="slack-channel" type="text" placeholder="#alerts" ref={(r) => this.channel = r} defaultValue={channel || ''}></input>
<input
className="form-control"
id="slack-channel"
type="text"
placeholder="#alerts"
ref={r => this.channel = r}
defaultValue={channel || ''}
/>
</div>
<div className="form-group form-group-submit col-xs-12 text-center">
<a className="btn btn-warning" onClick={this.handleTest} disabled={!this.state.testEnabled}>Send Test Message</a>
<a
className="btn btn-warning"
onClick={this.handleTest}
disabled={!this.state.testEnabled}
>
Send Test Message
</a>
<button className="btn btn-primary" type="submit">Save</button>
</div>
</form>

View File

@ -1,11 +1,8 @@
import React, {PropTypes} from 'react'
const {
bool,
string,
shape,
func,
} = PropTypes
import RedactedInput from './RedactedInput'
const {bool, string, shape, func} = PropTypes
const TalkConfig = React.createClass({
propTypes: {
@ -36,17 +33,28 @@ const TalkConfig = React.createClass({
<form onSubmit={this.handleSaveAlert}>
<div className="form-group col-xs-12">
<label htmlFor="url">URL</label>
<input className="form-control" id="url" type="text" ref={(r) => this.url = r} defaultValue={url || ''}></input>
<label className="form-helper">Note: a value of <code>true</code> indicates that the Talk URL has been set</label>
<RedactedInput
defaultValue={url}
id="url"
refFunc={r => this.url = r}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="author">Author Name</label>
<input className="form-control" id="author" type="text" ref={(r) => this.author = r} defaultValue={author || ''}></input>
<input
className="form-control"
id="author"
type="text"
ref={r => this.author = r}
defaultValue={author || ''}
/>
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -2,12 +2,9 @@ import React, {PropTypes} from 'react'
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
import {TELEGRAM_CHAT_ID_TIP, TELEGRAM_TOKEN_TIP} from 'src/kapacitor/copy'
const {
bool,
func,
shape,
string,
} = PropTypes
import RedactedInput from './RedactedInput'
const {bool, func, shape, string} = PropTypes
const TelegramConfig = React.createClass({
propTypes: {
@ -56,7 +53,16 @@ const TelegramConfig = React.createClass({
return (
<form onSubmit={this.handleSaveAlert}>
<p className="no-user-select">
You need a <a href="https://docs.influxdata.com/kapacitor/v1.2/guides/event-handler-setup/#telegram-bot" target="_blank">Telegram Bot</a> to use this endpoint
You need a
{' '}
<a
href="https://docs.influxdata.com/kapacitor/v1.2/guides/event-handler-setup/#telegram-bot"
target="_blank"
>
Telegram Bot
</a>
{' '}
to use this endpoint
</p>
<div className="form-group col-xs-12">
<label htmlFor="token">
@ -66,15 +72,11 @@ const TelegramConfig = React.createClass({
tipContent={TELEGRAM_TOKEN_TIP}
/>
</label>
<input
className="form-control"
<RedactedInput
defaultValue={token}
id="token"
type="text"
placeholder="your-telegram-token"
ref={(r) => this.token = r}
defaultValue={token || ''}>
</input>
<label className="form-helper">Note: a value of <code>true</code> indicates the Telegram token has been set</label>
refFunc={r => this.token = r}
/>
</div>
<div className="form-group col-xs-12">
@ -90,20 +92,34 @@ const TelegramConfig = React.createClass({
id="chat-id"
type="text"
placeholder="your-telegram-chat-id"
ref={(r) => this.chatID = r}
defaultValue={chatID || ''}>
</input>
ref={r => this.chatID = r}
defaultValue={chatID || ''}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="parseMode">Select the alert message format</label>
<div className="form-control-static">
<div className="radio">
<input id="parseModeMarkdown" type="radio" name="parseMode" value="markdown" defaultChecked={parseMode !== 'HTML'} ref={(r) => this.parseModeMarkdown = r} />
<input
id="parseModeMarkdown"
type="radio"
name="parseMode"
value="markdown"
defaultChecked={parseMode !== 'HTML'}
ref={r => this.parseModeMarkdown = r}
/>
<label htmlFor="parseModeMarkdown">Markdown</label>
</div>
<div className="radio">
<input id="parseModeHTML" type="radio" name="parseMode" value="html" defaultChecked={parseMode === 'HTML'} ref={(r) => this.parseModeHTML = r} />
<input
id="parseModeHTML"
type="radio"
name="parseMode"
value="html"
defaultChecked={parseMode === 'HTML'}
ref={r => this.parseModeHTML = r}
/>
<label htmlFor="parseModeHTML">HTML</label>
</div>
</div>
@ -111,16 +127,32 @@ const TelegramConfig = React.createClass({
<div className="form-group col-xs-12">
<div className="form-control-static">
<input id="disableWebPagePreview" type="checkbox" defaultChecked={disableWebPagePreview} ref={(r) => this.disableWebPagePreview = r} />
<input
id="disableWebPagePreview"
type="checkbox"
defaultChecked={disableWebPagePreview}
ref={r => this.disableWebPagePreview = r}
/>
<label htmlFor="disableWebPagePreview">
Disable <a href="https://telegram.org/blog/link-preview" target="_blank">link previews</a> in alert messages.
Disable
{' '}
<a href="https://telegram.org/blog/link-preview" target="_blank">
link previews
</a>
{' '}
in alert messages.
</label>
</div>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input id="disableNotification" type="checkbox" defaultChecked={disableNotification} ref={(r) => this.disableNotification = r} />
<input
id="disableNotification"
type="checkbox"
defaultChecked={disableNotification}
ref={r => this.disableNotification = r}
/>
<label htmlFor="disableNotification">
Disable notifications on iOS devices and disable sounds on Android devices. Android users continue to receive notifications.
</label>
@ -128,7 +160,9 @@ const TelegramConfig = React.createClass({
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -1,5 +1,7 @@
import React, {PropTypes} from 'react'
import RedactedInput from './RedactedInput'
const VictorOpsConfig = React.createClass({
propTypes: {
config: PropTypes.shape({
@ -34,22 +36,39 @@ const VictorOpsConfig = React.createClass({
<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 VictorOps API key has been set</label>
<RedactedInput
defaultValue={apiKey}
id="api-key"
refFunc={r => this.apiKey = r}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="routing-key">Routing Key</label>
<input className="form-control" id="routing-key" type="text" ref={(r) => this.routingKey = r} defaultValue={routingKey || ''}></input>
<input
className="form-control"
id="routing-key"
type="text"
ref={r => this.routingKey = r}
defaultValue={routingKey || ''}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="url">VictorOps URL</label>
<input className="form-control" id="url" type="text" ref={(r) => this.url = r} defaultValue={url || ''}></input>
<input
className="form-control"
id="url"
type="text"
ref={r => this.url = r}
defaultValue={url || ''}
/>
</div>
<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>
<button className="btn btn-block btn-primary" type="submit">
Save
</button>
</div>
</form>
)

View File

@ -24,25 +24,26 @@
@import 'layout/sidebar';
// Components
@import 'components/dropdown';
@import 'components/input-tag-list';
@import 'components/page-header-dropdown';
@import 'components/page-header-editable';
@import 'components/multi-select-dropdown';
@import 'components/page-spinner';
@import 'components/flash-messages';
@import 'components/flip-toggle';
@import 'components/dygraphs';
@import 'components/react-tooltips';
@import 'components/search-widget';
@import 'components/tables';
@import 'components/resizer';
@import 'components/source-indicator';
@import 'components/graph-tips';
@import 'components/confirm-buttons';
@import 'components/custom-time-range';
@import 'components/dropdown';
@import 'components/dygraphs';
@import 'components/flash-messages';
@import 'components/flip-toggle';
@import 'components/graph-tips';
@import 'components/graph';
@import 'components/input-tag-list';
@import 'components/multi-select-dropdown';
@import 'components/page-header-dropdown';
@import 'components/page-header-editable';
@import 'components/page-spinner';
@import 'components/query-maker';
@import 'components/react-tooltips';
@import 'components/redacted-input';
@import 'components/resizer';
@import 'components/search-widget';
@import 'components/source-indicator';
@import 'components/tables';
// Pages

View File

@ -0,0 +1,7 @@
.alert-value-set {
padding: 6px 13px 0;
span a {
margin-left: 10px;
}
}