Merge pull request #2122 from influxdata/bugfix/unsafe-ssl

BUGFIX:  Unsafe SSL
pull/10616/head
Andrew Watkins 2017-10-18 09:38:46 -07:00 committed by GitHub
commit bf283aa3bf
9 changed files with 240 additions and 301 deletions

View File

@ -1,6 +1,7 @@
## v1.3.10.0 [unreleased]
### Bug Fixes
1. [#2095](https://github.com/influxdata/chronograf/pull/2095): Improve the copy in the retention policy edit page
1. [#2122](https://github.com/influxdata/chronograf/pull/2122): Fix 'could not connect to source' bug on source creation with unsafe-ssl
1. [#2093](https://github.com/influxdata/chronograf/pull/2093): Fix when exporting `SHOW DATABASES` CSV has bad data
1. [#2098](https://github.com/influxdata/chronograf/pull/2098): Fix not-equal-to highlighting in Kapacitor Rule Builder

View File

@ -26,7 +26,7 @@ import {
TickscriptPage,
} from 'src/kapacitor'
import {AdminPage} from 'src/admin'
import {CreateSource, SourcePage, ManageSources} from 'src/sources'
import {SourcePage, ManageSources} from 'src/sources'
import NotFound from 'shared/components/NotFound'
import {getMe} from 'shared/apis'
@ -127,7 +127,7 @@ const Root = React.createClass({
<Route path="/login" component={UserIsNotAuthenticated(Login)} />
<Route
path="/sources/new"
component={UserIsAuthenticated(CreateSource)}
component={UserIsAuthenticated(SourcePage)}
/>
<Route path="/sources/:sourceID" component={UserIsAuthenticated(App)}>
<Route component={CheckSources}>

View File

@ -411,3 +411,14 @@ export const PAGE_HEADER_HEIGHT = 60 // TODO: get this dynamically to ensure lon
export const PAGE_CONTAINER_MARGIN = 30 // TODO: get this dynamically to ensure longevity
export const LAYOUT_MARGIN = 4
export const DASHBOARD_LAYOUT_ROW_HEIGHT = 83.5
export const DEFAULT_SOURCE = {
url: 'http://localhost:8086',
name: 'Influx 1',
username: '',
password: '',
default: true,
telegraf: 'telegraf',
insecureSkipVerify: false,
metaUrl: '',
}

View File

@ -8,32 +8,8 @@ class SourceForm extends Component {
super(props)
}
handleSubmitForm = e => {
e.preventDefault()
const newSource = {
...this.props.source,
url: this.sourceURL.value.trim(),
name: this.sourceName.value,
username: this.sourceUsername.value,
password: this.sourcePassword.value,
default: this.sourceDefault.checked,
telegraf: this.sourceTelegraf.value,
insecureSkipVerify: this.sourceInsecureSkipVerify
? this.sourceInsecureSkipVerify.checked
: false,
metaUrl: this.metaUrl && this.metaUrl.value.trim(),
}
this.props.onSubmit(newSource)
}
handleUseDefaultValues = () => {
this.sourceURL.value = 'http://localhost:8086'
this.sourceName.value = 'My InfluxDB'
}
handleBlurSourceURL = () => {
const url = this.sourceURL.value.trim()
const url = this.props.source.url.trim()
if (!url) {
return
@ -41,32 +17,31 @@ class SourceForm extends Component {
const newSource = {
...this.props.source,
url: this.sourceURL.value.trim(),
url,
}
this.props.onBlurSourceURL(newSource)
}
render() {
const {source, editMode, onInputChange} = this.props
const {source, editMode, onSubmit, onInputChange} = this.props
return (
<div className="panel-body">
<h4 className="text-center">Connection Details</h4>
<br />
<form onSubmit={this.handleSubmitForm}>
<form onSubmit={onSubmit}>
<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="Address of InfluxDB"
onChange={onInputChange}
value={source.url || ''}
value={source.url}
onBlur={this.handleBlurSourceURL}
required={true}
/>
@ -76,12 +51,11 @@ class SourceForm extends Component {
<input
type="text"
name="name"
ref={r => (this.sourceName = r)}
className="form-control"
id="name"
placeholder="Name this source"
onChange={onInputChange}
value={source.name || ''}
value={source.name}
required={true}
/>
</div>
@ -90,11 +64,10 @@ class SourceForm extends Component {
<input
type="text"
name="username"
ref={r => (this.sourceUsername = r)}
className="form-control"
id="username"
onChange={onInputChange}
value={source.username || ''}
value={source.username}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
@ -102,11 +75,10 @@ class SourceForm extends Component {
<input
type="password"
name="password"
ref={r => (this.sourcePassword = r)}
className="form-control"
id="password"
onChange={onInputChange}
value={source.password || ''}
value={source.password}
/>
</div>
{_.get(source, 'type', '').includes('enterprise')
@ -115,12 +87,11 @@ class SourceForm extends Component {
<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 || ''}
value={source.metaUrl}
/>
</div>
: null}
@ -129,11 +100,10 @@ class SourceForm extends Component {
<input
type="text"
name="telegraf"
ref={r => (this.sourceTelegraf = r)}
className="form-control"
id="telegraf"
onChange={onInputChange}
value={source.telegraf || 'telegraf'}
value={source.telegraf}
/>
</div>
<div className="form-group col-xs-12">
@ -141,8 +111,9 @@ class SourceForm extends Component {
<input
type="checkbox"
id="defaultSourceCheckbox"
defaultChecked={source.default}
ref={r => (this.sourceDefault = r)}
name="default"
checked={source.default}
onChange={onInputChange}
/>
<label htmlFor="defaultSourceCheckbox">
Make this the default source
@ -155,8 +126,9 @@ class SourceForm extends Component {
<input
type="checkbox"
id="insecureSkipVerifyCheckbox"
defaultChecked={source.insecureSkipVerify}
ref={r => (this.sourceInsecureSkipVerify = r)}
name="insecureSkipVerify"
checked={source.insecureSkipVerify}
onChange={onInputChange}
/>
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
@ -176,13 +148,6 @@ class SourceForm extends Component {
{editMode ? 'Save Changes' : 'Add Source'}
</button>
<br />
<a
href="#"
className="btn btn-link btn-sm"
onClick={this.handleUseDefaultValues}
>
Use Default Values
</a>
</div>
</form>
</div>
@ -190,10 +155,19 @@ class SourceForm extends Component {
}
}
const {bool, func, shape} = PropTypes
const {bool, func, shape, string} = PropTypes
SourceForm.propTypes = {
source: shape({}).isRequired,
source: shape({
url: string.isRequired,
name: string.isRequired,
username: string.isRequired,
password: string.isRequired,
telegraf: string.isRequired,
insecureSkipVerify: bool.isRequired,
default: bool.isRequired,
metaUrl: string.isRequired,
}).isRequired,
editMode: bool.isRequired,
onInputChange: func.isRequired,
onSubmit: func.isRequired,

View File

@ -1,149 +0,0 @@
import React, {PropTypes} from 'react'
import {withRouter} from 'react-router'
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 'shared/actions/sources'
import {publishNotification} from 'shared/actions/notifications'
const {func, shape, string} = PropTypes
export const CreateSource = React.createClass({
propTypes: {
router: shape({
push: func.isRequired,
}).isRequired,
location: shape({
query: shape({
redirectPath: string,
}).isRequired,
}).isRequired,
addSource: func,
updateSource: func,
notify: func,
},
getInitialState() {
return {
source: {},
}
},
redirectToApp(source) {
const {redirectPath} = this.props.location.query
if (!redirectPath) {
return this.props.router.push(`/sources/${source.id}/hosts`)
}
const fixedPath = redirectPath.replace(
/\/sources\/[^/]*/,
`/sources/${source.id}`
)
return this.props.router.push(fixedPath)
},
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})
})
},
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 {source} = this.state
return (
<div>
<Notifications />
<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>
</div>
</div>
</div>
)
},
})
function mapDispatchToProps(dispatch) {
return {
addSource: bindActionCreators(addSourceAction, dispatch),
updateSource: bindActionCreators(updateSourceAction, dispatch),
notify: bindActionCreators(publishNotification, dispatch),
}
}
export default connect(null, mapDispatchToProps)(withRouter(CreateSource))

View File

@ -1,67 +1,73 @@
import React, {PropTypes} from 'react'
import React, {PropTypes, Component} from 'react'
import {withRouter} from 'react-router'
import _ from 'lodash'
import {getSource} from 'shared/apis'
import {createSource, updateSource} from 'shared/apis'
import {
addSource as addSourceAction,
updateSource as updateSourceAction,
} from 'shared/actions/sources'
import {publishNotification} from 'shared/actions/notifications'
import {connect} from 'react-redux'
import SourceForm from 'src/sources/components/SourceForm'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import SourceIndicator from 'shared/components/SourceIndicator'
import {DEFAULT_SOURCE} from 'shared/constants'
const initialPath = '/sources/new'
const {func, shape, string} = PropTypes
class SourcePage extends Component {
constructor(props) {
super(props)
export const SourcePage = React.createClass({
propTypes: {
params: shape({
id: string,
sourceID: string,
}),
router: shape({
push: func.isRequired,
}).isRequired,
location: shape({
query: shape({
redirectPath: string,
}).isRequired,
}).isRequired,
addFlashMessage: func.isRequired,
addSourceAction: func,
updateSourceAction: func,
},
getInitialState() {
return {
source: {},
editMode: this.props.params.id !== undefined,
error: '',
this.state = {
isLoading: true,
source: DEFAULT_SOURCE,
editMode: props.params.id !== undefined,
isInitialSource: props.router.location.pathname === initialPath,
}
}
},
componentDidMount() {
if (!this.state.editMode) {
return
const {editMode} = this.state
const {params} = this.props
if (!editMode) {
return this.setState({isLoading: false})
}
getSource(this.props.params.id).then(({data: source}) => {
this.setState({source})
})
},
handleInputChange(e) {
const val = e.target.value
getSource(params.id)
.then(({data: source}) => {
this.setState({
source: {...DEFAULT_SOURCE, ...source},
isLoading: false,
})
})
.catch(error => {
this.handleError('Could not connect to source', error)
this.setState({isLoading: false})
})
}
handleInputChange = e => {
let 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})
})
},
handleBlurSourceURL(newSource) {
if (e.target.type === 'checkbox') {
val = e.target.checked
}
this.setState(prevState => {
const source = {
...prevState.source,
[name]: val,
}
return {...prevState, source}
})
}
handleBlurSourceURL = newSource => {
if (this.state.editMode) {
return
}
@ -78,47 +84,97 @@ export const SourcePage = React.createClass({
createSource(newSource)
.then(({data: sourceFromServer}) => {
this.props.addSourceAction(sourceFromServer)
this.setState({source: sourceFromServer, error: null})
this.setState({
source: {...DEFAULT_SOURCE, ...sourceFromServer},
isCreated: true,
})
.catch(({data: error}) => {
})
.catch(err => {
// dont want to flash this until they submit
this.setState({error: error.message})
const error = this.parseError(err)
console.error('Error on source creation: ', error)
})
},
handleSubmit(newSource) {
const {router, params, addFlashMessage} = this.props
const {error} = this.state
if (error) {
return addFlashMessage({type: 'error', text: error})
}
updateSource(newSource)
handleSubmit = e => {
e.preventDefault()
const {notify} = this.props
const {isCreated, source, editMode} = this.state
const isNewSource = !editMode
if (!isCreated && isNewSource) {
return createSource(source)
.then(({data: sourceFromServer}) => {
this.props.addSourceAction(sourceFromServer)
this._redirect(sourceFromServer)
})
.catch(error => {
this.handleError('Unable to create source', error)
})
}
updateSource(source)
.then(({data: sourceFromServer}) => {
this.props.updateSourceAction(sourceFromServer)
this._redirect(sourceFromServer)
notify('success', `New source ${source.name} added`)
})
.catch(error => {
this.handleError('Unable to update source', error)
})
}
handleError = (bannerText, err) => {
const {notify} = this.props
const error = this.parseError(err)
console.error('Error: ', error)
notify('error', `${bannerText}: ${error}`)
}
_redirect = source => {
const {isInitialSource} = this.state
const {params, router} = this.props
if (isInitialSource) {
return this._redirectToApp(source)
}
router.push(`/sources/${params.sourceID}/manage-sources`)
addFlashMessage({type: 'success', text: 'The source info saved'})
})
.catch(() => {
addFlashMessage({
type: 'error',
text: 'There was a problem updating the source. Check the settings',
})
})
},
}
_redirectToApp = source => {
const {location, router} = this.props
const {redirectPath} = location.query
if (!redirectPath) {
return router.push(`/sources/${source.id}/hosts`)
}
const fixedPath = redirectPath.replace(
/\/sources\/[^/]*/,
`/sources/${source.id}`
)
return router.push(fixedPath)
}
parseError = error => {
return _.get(error, ['data', 'message'], error)
}
render() {
const {source, editMode} = this.state
const {isLoading, source, editMode, isInitialSource} = this.state
if (editMode && !source.id) {
if (isLoading) {
return <div className="page-spinner" />
}
return (
<div className="page" id="source-form-page">
<div className="page-header">
<div className="page-header__container">
<div className={`${isInitialSource ? '' : 'page'}`}>
{isInitialSource
? null
: <div className="page-header">
<div className="page-header__container page-header__source-page">
<div className="page-header__col-md-8">
<div className="page-header__left">
<h1 className="page-header__title">
{editMode ? 'Edit Source' : 'Add a New Source'}
@ -129,6 +185,7 @@ export const SourcePage = React.createClass({
</div>
</div>
</div>
</div>}
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
@ -148,13 +205,33 @@ export const SourcePage = React.createClass({
</FancyScrollbar>
</div>
)
},
})
function mapStateToProps(_) {
return {}
}
}
export default connect(mapStateToProps, {addSourceAction, updateSourceAction})(
withRouter(SourcePage)
)
const {func, shape, string} = PropTypes
SourcePage.propTypes = {
params: shape({
id: string,
sourceID: string,
}),
router: shape({
push: func.isRequired,
}).isRequired,
location: shape({
query: shape({
redirectPath: string,
}).isRequired,
}).isRequired,
notify: func,
addSourceAction: func,
updateSourceAction: func,
}
const mapStateToProps = () => ({})
export default connect(mapStateToProps, {
notify: publishNotification,
addSourceAction,
updateSourceAction,
})(withRouter(SourcePage))

View File

@ -1,4 +1,3 @@
import CreateSource from './containers/CreateSource'
import SourcePage from './containers/SourcePage'
import ManageSources from './containers/ManageSources'
export {CreateSource, SourcePage, ManageSources}
export {SourcePage, ManageSources}

View File

@ -23,6 +23,7 @@ $page-header-weight: 400 !important;
max-width: 100%;
}
.page-header__container {
position: relative;
width: 100%;
display: flex;
align-items: center;
@ -31,6 +32,26 @@ $page-header-weight: 400 !important;
width: 100%;
max-width: ($page-wrapper-max-width - $page-wrapper-padding - $page-wrapper-padding);
}
.page-header__container.page-header__source-page {
justify-content: center;
}
.page-header__col-md-8 {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
@media screen and (min-width: 992px) {
/*
NOTE:
Breakpoint and % width are based on the bootstrap grid
If the source form column sizing is ever changed, this
will have to be manually updated
*/
.page-header__col-md-8 {
width: 66.66667%;
}
}
.page-header__left,
.page-header__right {
display: flex;

View File

@ -22,6 +22,11 @@
width: 100%;
height: calc(100% - #{$chronograf-page-header-height}) !important;
@include gradient-v($g2-kevlar,$g0-obsidian);
&:only-child {
top: 0;
height: 100%;
}
}
.container-fluid {
padding: ($chronograf-page-header-height / 2) $page-wrapper-padding;