Merge pull request #1205 from influxdata/initial-source-options

add missing options when creating initial source
pull/10616/head
Jade McGough 2017-04-07 16:00:36 -07:00 committed by GitHub
commit 67f9e4020f
8 changed files with 302 additions and 241 deletions

View File

@ -23,6 +23,7 @@
1. [#1209](https://github.com/influxdata/chronograf/pull/1209): HipChat Kapacitor config now uses only the subdomain instead of asking for the entire HipChat URL.
1. [#1219](https://github.com/influxdata/chronograf/pull/1219): Update query for default cell in new dashboard
1. [#1206](https://github.com/influxdata/chronograf/issues/1206): Chronograf now proxies to kapacitors behind proxy using vhost correctly.
1. [#1205](https://github.com/influxdata/chronograf/pull/1205): Allow initial source to be an enterprise source.
### Features
1. [#1112](https://github.com/influxdata/chronograf/pull/1112): Add ability to delete a dashboard

View File

@ -1,18 +1,16 @@
import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import classnames from 'classnames'
import SideNavContainer from 'src/side_nav'
import Notifications from 'shared/components/Notifications'
import {
publishNotification as publishNotificationAction,
dismissNotification as dismissNotificationAction,
dismissAllNotifications as dismissAllNotificationsAction,
} from 'src/shared/actions/notifications'
const {
func,
node,
shape,
string,
func,
} = PropTypes
const App = React.createClass({
@ -24,99 +22,34 @@ const App = React.createClass({
params: shape({
sourceID: string.isRequired,
}).isRequired,
publishNotification: func.isRequired,
dismissNotification: func.isRequired,
dismissAllNotifications: func.isRequired,
notifications: shape({
success: string,
error: string,
warning: string,
}),
notify: func.isRequired,
},
handleNotification({type, text}) {
const validTypes = ['error', 'success', 'warning']
if (!validTypes.includes(type) || text === undefined) {
console.error("handleNotification must have a valid type and text") // eslint-disable-line no-console
}
this.props.publishNotification(type, text)
},
handleAddFlashMessage({type, text}) {
const {notify} = this.props
handleDismissNotification(type) {
this.props.dismissNotification(type)
},
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname !== this.props.location.pathname) {
this.props.dismissAllNotifications()
}
notify(type, text)
},
render() {
const {params: {sourceID}} = this.props
const {params: {sourceID}, location} = this.props
return (
<div className="chronograf-root">
<SideNavContainer
sourceID={sourceID}
addFlashMessage={this.handleNotification}
addFlashMessage={this.handleAddFlashMessage}
currentLocation={this.props.location.pathname}
/>
{this.renderNotifications()}
<Notifications location={location} />
{this.props.children && React.cloneElement(this.props.children, {
addFlashMessage: this.handleNotification,
addFlashMessage: this.handleAddFlashMessage,
})}
</div>
)
},
renderNotifications() {
const {success, error, warning} = this.props.notifications
if (!success && !error && !warning) {
return null
}
return (
<div className="flash-messages">
{this.renderNotification('success', success)}
{this.renderNotification('error', error)}
{this.renderNotification('warning', warning)}
</div>
)
},
renderNotification(type, message) {
if (!message) {
return null
}
const cls = classnames('alert', {
'alert-danger': type === 'error',
'alert-success': type === 'success',
'alert-warning': type === 'warning',
})
return (
<div className={cls} role="alert">
{message}{this.renderDismiss(type)}
</div>
)
},
renderDismiss(type) {
return (
<button className="close" data-dismiss="alert" aria-label="Close" onClick={() => this.handleDismissNotification(type)}>
<span className="icon remove"></span>
</button>
)
},
})
function mapStateToProps(state) {
return {
notifications: state.notifications,
}
}
export default connect(mapStateToProps, {
publishNotification: publishNotificationAction,
dismissNotification: dismissNotificationAction,
dismissAllNotifications: dismissAllNotificationsAction,
export default connect(null, {
notify: publishNotificationAction,
})(App)

View File

@ -1,4 +1,10 @@
export function publishNotification(type, message) {
// this validator is purely for development purposes. It might make sense to move this to a middleware.
const validTypes = ['error', 'success', 'warning']
if (!validTypes.includes(type) || message === undefined) {
console.error("handleNotification must have a valid type and text") // eslint-disable-line no-console
}
return {
type: 'NOTIFICATION_RECEIVED',
payload: {

View File

@ -0,0 +1,98 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {
publishNotification as publishNotificationAction,
dismissNotification as dismissNotificationAction,
dismissAllNotifications as dismissAllNotificationsAction,
} from 'src/shared/actions/notifications'
class Notifications extends Component {
constructor(props) {
super(props)
this.renderNotification = ::this.renderNotification
this.renderDismiss = ::this.renderDismiss
}
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname !== this.props.location.pathname) {
this.props.dismissAllNotifications()
}
}
renderNotification(type, message) {
if (!message) {
return null
}
const cls = classnames('alert', {
'alert-danger': type === 'error',
'alert-success': type === 'success',
'alert-warning': type === 'warning',
})
return (
<div className={cls} role="alert">
{message}{this.renderDismiss(type)}
</div>
)
}
renderDismiss(type) {
const {dismissNotification} = this.props
return (
<button className="close" data-dismiss="alert" aria-label="Close" onClick={() => dismissNotification(type)}>
<span className="icon remove"></span>
</button>
)
}
render() {
const {success, error, warning} = this.props.notifications
if (!success && !error && !warning) {
return null
}
return (
<div className="flash-messages">
{this.renderNotification('success', success)}
{this.renderNotification('error', error)}
{this.renderNotification('warning', warning)}
</div>
)
}
}
const {
func,
shape,
string,
} = PropTypes
Notifications.propTypes = {
location: shape({
pathname: string,
}),
publishNotification: func.isRequired,
dismissNotification: func.isRequired,
dismissAllNotifications: func.isRequired,
notifications: shape({
success: string,
error: string,
warning: string,
}),
}
const mapStateToProps = ({notifications}) => ({
notifications,
})
const mapDispatchToProps = (dispatch) => ({
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
dismissNotification: bindActionCreators(dismissNotificationAction, dispatch),
dismissAllNotifications: bindActionCreators(dismissAllNotificationsAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(Notifications)

View File

@ -11,9 +11,6 @@ const {
export const SourceForm = React.createClass({
propTypes: {
addFlashMessage: func.isRequired,
addSourceAction: func,
updateSourceAction: func,
source: shape({}).isRequired,
editMode: bool.isRequired,
onInputChange: func.isRequired,
@ -56,80 +53,55 @@ export const SourceForm = React.createClass({
render() {
const {source, editMode, onInputChange} = this.props
if (editMode && !source.id) {
return <div className="page-spinner"></div>
}
return (
<div className="page" id="source-form-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1>
{editMode ? "Edit Source" : "Add a New Source"}
</h1>
</div>
</div>
</div>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-8 col-md-offset-2">
<div className="panel panel-minimal">
<div className="panel-body">
<h4 className="text-center">Connection Details</h4>
<br/>
<div className="panel-body">
<h4 className="text-center">Connection Details</h4>
<br/>
<form onSubmit={this.handleSubmitForm}>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="connect-string"> Connection String</label>
<input type="text" name="url" ref={(r) => this.sourceURL = r} className="form-control" id="connect-string" placeholder="http://localhost:8086" onChange={onInputChange} value={source.url || ''} onBlur={this.handleBlurSourceURL}></input>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="name">Name</label>
<input type="text" name="name" ref={(r) => this.sourceName = r} className="form-control" id="name" placeholder="Influx 1" onChange={onInputChange} value={source.name || ''}></input>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="username">Username</label>
<input type="text" name="username" ref={(r) => this.sourceUsername = r} className="form-control" id="username" onChange={onInputChange} value={source.username || ''}></input>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="password">Password</label>
<input type="password" name="password" ref={(r) => this.sourcePassword = r} className="form-control" id="password" onChange={onInputChange} value={source.password || ''}></input>
</div>
{_.get(source, 'type', '').includes("enterprise") ?
<div className="form-group col-xs-12">
<label htmlFor="meta-url">Meta Service Connection URL</label>
<input type="text" name="metaUrl" ref={(r) => this.metaUrl = r} className="form-control" id="meta-url" placeholder="http://localhost:8091" onChange={onInputChange} value={source.metaUrl || ''}></input>
</div> : null}
<div className="form-group col-xs-12">
<label htmlFor="telegraf">Telegraf database</label>
<input type="text" name="telegraf" ref={(r) => this.sourceTelegraf = r} className="form-control" id="telegraf" onChange={onInputChange} value={source.telegraf || 'telegraf'}></input>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input type="checkbox" id="defaultSourceCheckbox" defaultChecked={source.default} ref={(r) => this.sourceDefault = r} />
<label htmlFor="defaultSourceCheckbox">Make this the default source</label>
</div>
</div>
{_.get(source, 'url', '').startsWith("https") ?
<div className="form-group col-xs-12">
<div className="form-control-static">
<input type="checkbox" id="insecureSkipVerifyCheckbox" defaultChecked={source.insecureSkipVerify} ref={(r) => this.sourceInsecureSkipVerify = r} />
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div> : null}
<div className="form-group form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button className={classNames('btn btn-block', {'btn-primary': editMode, 'btn-success': !editMode})} type="submit">{editMode ? "Save Changes" : "Add Source"}</button>
</div>
</form>
</div>
</div>
</div>
<form onSubmit={this.handleSubmitForm}>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="connect-string"> Connection String</label>
<input type="text" name="url" ref={(r) => this.sourceURL = r} className="form-control" id="connect-string" placeholder="http://localhost:8086" onChange={onInputChange} value={source.url || ''} onBlur={this.handleBlurSourceURL}></input>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="name">Name</label>
<input type="text" name="name" ref={(r) => this.sourceName = r} className="form-control" id="name" placeholder="Influx 1" onChange={onInputChange} value={source.name || ''}></input>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="username">Username</label>
<input type="text" name="username" ref={(r) => this.sourceUsername = r} className="form-control" id="username" onChange={onInputChange} value={source.username || ''}></input>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="password">Password</label>
<input type="password" name="password" ref={(r) => this.sourcePassword = r} className="form-control" id="password" onChange={onInputChange} value={source.password || ''}></input>
</div>
{_.get(source, 'type', '').includes("enterprise") ?
<div className="form-group col-xs-12">
<label htmlFor="meta-url">Meta Service Connection URL</label>
<input type="text" name="metaUrl" ref={(r) => this.metaUrl = r} className="form-control" id="meta-url" placeholder="http://localhost:8091" onChange={onInputChange} value={source.metaUrl || ''}></input>
</div> : null}
<div className="form-group col-xs-12">
<label htmlFor="telegraf">Telegraf database</label>
<input type="text" name="telegraf" ref={(r) => this.sourceTelegraf = r} className="form-control" id="telegraf" onChange={onInputChange} value={source.telegraf || 'telegraf'}></input>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input type="checkbox" id="defaultSourceCheckbox" defaultChecked={source.default} ref={(r) => this.sourceDefault = r} />
<label htmlFor="defaultSourceCheckbox">Make this the default source</label>
</div>
</div>
</div>
{_.get(source, 'url', '').startsWith("https") ?
<div className="form-group col-xs-12">
<div className="form-control-static">
<input type="checkbox" id="insecureSkipVerifyCheckbox" defaultChecked={source.insecureSkipVerify} ref={(r) => this.sourceInsecureSkipVerify = r} />
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div> : null}
<div className="form-group form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button className={classNames('btn btn-block', {'btn-primary': editMode, 'btn-success': !editMode})} type="submit">{editMode ? "Save Changes" : "Add Source"}</button>
</div>
</form>
</div>
)
},

View File

@ -1,36 +1,45 @@
import React, {PropTypes} from 'react'
import {withRouter} from 'react-router'
import {addSource as addSourceAction} from 'src/shared/actions/sources'
import {createSource} from 'shared/apis'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {
createSource as createSourceAJAX,
updateSource as updateSourceAJAX,
} from 'shared/apis'
import SourceForm from 'src/sources/components/SourceForm'
import Notifications from 'shared/components/Notifications'
import {
addSource as addSourceAction,
updateSource as updateSourceAction,
} from 'src/shared/actions/sources'
import {publishNotification} from 'src/shared/actions/notifications'
const {
func,
shape,
string,
} = PropTypes
export const CreateSource = React.createClass({
propTypes: {
router: PropTypes.shape({
push: PropTypes.func.isRequired,
router: shape({
push: func.isRequired,
}).isRequired,
location: PropTypes.shape({
query: PropTypes.shape({
redirectPath: PropTypes.string,
location: shape({
query: shape({
redirectPath: string,
}).isRequired,
}).isRequired,
addSourceAction: PropTypes.func,
addSource: func,
updateSource: func,
notify: func,
},
handleNewSource(e) {
e.preventDefault()
const source = {
url: this.sourceURL.value.trim(),
name: this.sourceName.value,
username: this.sourceUser.value,
password: this.sourcePassword.value,
isDefault: true,
telegraf: this.sourceTelegraf.value,
getInitialState() {
return {
source: {},
}
createSource(source).then(({data: sourceFromServer}) => {
this.props.addSourceAction(sourceFromServer)
this.redirectToApp(sourceFromServer)
})
},
redirectToApp(source) {
@ -43,47 +52,77 @@ export const CreateSource = React.createClass({
return this.props.router.push(fixedPath)
},
render() {
return (
<div className="select-source-page" id="select-source-page">
<div className="container-fluid">
<div className="row">
<div className="col-md-8 col-md-offset-2">
<div className="panel panel-minimal">
<div className="panel-heading text-center">
<h2 className="deluxe">Welcome to Chronograf</h2>
</div>
<div className="panel-body">
<h4 className="text-center">Connect to a New Source</h4>
<br/>
handleInputChange(e) {
const val = e.target.value
const name = e.target.name
this.setState((prevState) => {
const newSource = Object.assign({}, prevState.source, {
[name]: val,
})
return Object.assign({}, prevState, {source: newSource})
})
},
<form onSubmit={this.handleNewSource}>
<div>
<div className="form-group col-xs-6 col-sm-4 col-sm-offset-2">
<label htmlFor="connect-string">Connection String</label>
<input ref={(r) => this.sourceURL = r} className="form-control" id="connect-string" defaultValue="http://localhost:8086"></input>
</div>
<div className="form-group col-xs-6 col-sm-4">
<label htmlFor="name">Name</label>
<input ref={(r) => this.sourceName = r} className="form-control" id="name" defaultValue="Influx 1"></input>
</div>
<div className="form-group col-xs-6 col-sm-4 col-sm-offset-2">
<label htmlFor="username">Username</label>
<input ref={(r) => this.sourceUser = r} className="form-control" id="username"></input>
</div>
<div className="form-group col-xs-6 col-sm-4">
<label htmlFor="password">Password</label>
<input ref={(r) => this.sourcePassword = r} className="form-control" id="password" type="password"></input>
</div>
</div>
<div className="form-group col-xs-8 col-xs-offset-2">
<label htmlFor="telegraf">Telegraf Database</label>
<input ref={(r) => this.sourceTelegraf = r} className="form-control" id="telegraf" type="text" defaultValue="telegraf"></input>
</div>
<div className="form-group form-group-submit col-xs-12 text-center">
<button className="btn btn-success" type="submit">Connect New Source</button>
</div>
</form>
handleBlurSourceURL(newSource) {
if (this.state.editMode) {
return
}
if (!newSource.url) {
return
}
// if there is a type on source it has already been created
if (newSource.type) {
return
}
createSourceAJAX(newSource).then(({data: sourceFromServer}) => {
this.props.addSource(sourceFromServer)
this.setState({source: sourceFromServer, error: null})
}).catch(({data: error}) => {
this.setState({error: error.message})
})
},
handleSubmit(newSource) {
const {error} = this.state
const {notify, updateSource} = this.props
if (error) {
return notify('error', error)
}
updateSourceAJAX(newSource).then(({data: sourceFromServer}) => {
updateSource(sourceFromServer)
this.redirectToApp(sourceFromServer)
}).catch(() => {
notify('error', 'There was a problem updating the source. Check the settings')
})
},
render() {
const {location} = this.props
const {source} = this.state
return (
<div>
<Notifications location={location} />
<div className="select-source-page">
<div className="container-fluid">
<div className="row">
<div className="col-md-8 col-md-offset-2">
<div className="panel panel-minimal">
<div className="panel-heading text-center">
<h2 className="deluxe">Welcome to Chronograf</h2>
</div>
<SourceForm
source={source}
editMode={false}
onInputChange={this.handleInputChange}
onSubmit={this.handleSubmit}
onBlurSourceURL={this.handleBlurSourceURL}
/>
</div>
</div>
</div>
@ -94,8 +133,12 @@ export const CreateSource = React.createClass({
},
})
function mapStateToProps(_) {
return {}
function mapDispatchToProps(dispatch) {
return {
addSource: bindActionCreators(addSourceAction, dispatch),
updateSource: bindActionCreators(updateSourceAction, dispatch),
notify: bindActionCreators(publishNotification, dispatch),
}
}
export default connect(mapStateToProps, {addSourceAction})(withRouter(CreateSource))
export default connect(null, mapDispatchToProps)(withRouter(CreateSource))

View File

@ -78,7 +78,7 @@ export const SourcePage = React.createClass({
createSource(newSource).then(({data: sourceFromServer}) => {
this.props.addSourceAction(sourceFromServer)
this.setState({source: sourceFromServer})
this.setState({source: sourceFromServer, error: null})
}).catch(({data: error}) => {
// dont want to flash this until they submit
this.setState({error: error.message})
@ -105,22 +105,40 @@ export const SourcePage = React.createClass({
render() {
const {source, editMode} = this.state
const {addFlashMessage, router, location, params} = this.props
if (editMode && !source.id) {
return <div className="page-spinner"></div>
}
return (
<SourceForm
sourceID={params.sourceID}
router={router}
location={location}
source={source}
editMode={editMode}
addFlashMessage={addFlashMessage}
addSourceAction={addSourceAction}
updateSourceAction={updateSourceAction}
onInputChange={this.handleInputChange}
onSubmit={this.handleSubmit}
onBlurSourceURL={this.handleBlurSourceURL}
/>
<div className="page" id="source-form-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1>
{editMode ? "Edit Source" : "Add a New Source"}
</h1>
</div>
</div>
</div>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-8 col-md-offset-2">
<div className="panel panel-minimal">
<SourceForm
source={source}
editMode={editMode}
onInputChange={this.handleInputChange}
onSubmit={this.handleSubmit}
onBlurSourceURL={this.handleBlurSourceURL}
/>
</div>
</div>
</div>
</div>
</div>
</div>
)
},
})

View File

@ -2,16 +2,6 @@
Unsorted
----------------------------------------------
*/
.select-source-page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: auto;
@include custom-scrollbar($g2-kevlar, $c-pool);
@include gradient-v($g2-kevlar, $g0-obsidian);
}
.text-right .btn {
margin: 0 0 0 4px;
}