Source form and page
parent
36355f18d9
commit
08bf9bcd27
|
@ -450,8 +450,7 @@ export const populateNamespacesAsync = (
|
||||||
export const getSourceAndPopulateNamespacesAsync = (sourceID: string) => async (
|
export const getSourceAndPopulateNamespacesAsync = (sourceID: string) => async (
|
||||||
dispatch
|
dispatch
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const response = await getSource(sourceID)
|
const source = await getSource(sourceID)
|
||||||
const source = response.data
|
|
||||||
|
|
||||||
const proxyLink = getDeep<string | null>(source, 'links.proxy', null)
|
const proxyLink = getDeep<string | null>(source, 'links.proxy', null)
|
||||||
|
|
||||||
|
|
|
@ -9,29 +9,51 @@ export function getSources() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSource(id) {
|
export const getSource = async (id: string): Promise<Source> => {
|
||||||
return AJAX({
|
try {
|
||||||
|
const {data: source} = await AJAX({
|
||||||
url: null,
|
url: null,
|
||||||
resource: 'sources',
|
resource: 'sources',
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return source
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSource(attributes) {
|
export const createSource = async (
|
||||||
return AJAX({
|
attributes: Partial<Source>
|
||||||
|
): Promise<Source> => {
|
||||||
|
try {
|
||||||
|
const {data: source} = await AJAX({
|
||||||
url: null,
|
url: null,
|
||||||
resource: 'sources',
|
resource: 'sources',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: attributes,
|
data: attributes,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return source
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateSource(newSource) {
|
export const updateSource = async (
|
||||||
return AJAX({
|
newSource: Partial<Source>
|
||||||
|
): Promise<Source> => {
|
||||||
|
try {
|
||||||
|
const {data: source} = await AJAX({
|
||||||
url: newSource.links.self,
|
url: newSource.links.self,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
data: newSource,
|
data: newSource,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return source
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteSource(source) {
|
export function deleteSource(source) {
|
||||||
|
|
|
@ -155,8 +155,10 @@ export const notifyUnableToRetrieveSources = () => 'Unable to retrieve sources.'
|
||||||
export const notifyUnableToConnectSource = sourceName =>
|
export const notifyUnableToConnectSource = sourceName =>
|
||||||
`Unable to connect to source ${sourceName}.`
|
`Unable to connect to source ${sourceName}.`
|
||||||
|
|
||||||
export const notifyErrorConnectingToSource = errorMessage =>
|
export const notifyErrorConnectingToSource = errorMessage => ({
|
||||||
`Unable to connect to InfluxDB source: ${errorMessage}`
|
...defaultErrorNotification,
|
||||||
|
message: `Unable to connect to InfluxDB source: ${errorMessage}`,
|
||||||
|
})
|
||||||
|
|
||||||
// Multitenancy User Notifications
|
// Multitenancy User Notifications
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,208 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
import {insecureSkipVerifyText} from 'shared/copy/tooltipText'
|
|
||||||
|
|
||||||
import {SUPERADMIN_ROLE} from 'src/auth/Authorized'
|
|
||||||
|
|
||||||
export const SourceForm = ({
|
|
||||||
source,
|
|
||||||
editMode,
|
|
||||||
onSubmit,
|
|
||||||
onInputChange,
|
|
||||||
onBlurSourceURL,
|
|
||||||
isUsingAuth,
|
|
||||||
gotoPurgatory,
|
|
||||||
isInitialSource,
|
|
||||||
me,
|
|
||||||
}) => (
|
|
||||||
<div className="panel-body">
|
|
||||||
{isUsingAuth && isInitialSource ? (
|
|
||||||
<div className="text-center">
|
|
||||||
{me.role === SUPERADMIN_ROLE ? (
|
|
||||||
<h3>
|
|
||||||
<strong>{me.currentOrganization.name}</strong> has no connections
|
|
||||||
</h3>
|
|
||||||
) : (
|
|
||||||
<h3>
|
|
||||||
<strong>{me.currentOrganization.name}</strong> has no connections
|
|
||||||
available to <em>{me.role}s</em>
|
|
||||||
</h3>
|
|
||||||
)}
|
|
||||||
<h6>Add a Connection below:</h6>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<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"
|
|
||||||
className="form-control"
|
|
||||||
id="connect-string"
|
|
||||||
placeholder="Address of InfluxDB"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.url}
|
|
||||||
onBlur={onBlurSourceURL}
|
|
||||||
required={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-group col-xs-12 col-sm-6">
|
|
||||||
<label htmlFor="name">Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
className="form-control"
|
|
||||||
id="name"
|
|
||||||
placeholder="Name this source"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.name}
|
|
||||||
required={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-group col-xs-12 col-sm-6">
|
|
||||||
<label htmlFor="username">Username</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
className="form-control"
|
|
||||||
id="username"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.username}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-group col-xs-12 col-sm-6">
|
|
||||||
<label htmlFor="password">Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
className="form-control"
|
|
||||||
id="password"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.password}
|
|
||||||
/>
|
|
||||||
</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"
|
|
||||||
className="form-control"
|
|
||||||
id="meta-url"
|
|
||||||
placeholder="http://localhost:8091"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.metaUrl}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="form-group col-xs-12 col-sm-6">
|
|
||||||
<label htmlFor="telegraf">Telegraf Database</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="telegraf"
|
|
||||||
className="form-control"
|
|
||||||
id="telegraf"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.telegraf}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-group col-xs-12 col-sm-6">
|
|
||||||
<label htmlFor="defaultRP">Default Retention Policy</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="defaultRP"
|
|
||||||
className="form-control"
|
|
||||||
id="defaultRP"
|
|
||||||
onChange={onInputChange}
|
|
||||||
value={source.defaultRP}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-group col-xs-12">
|
|
||||||
<div className="form-control-static">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="defaultConnectionCheckbox"
|
|
||||||
name="default"
|
|
||||||
checked={source.default}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
<label htmlFor="defaultConnectionCheckbox">
|
|
||||||
Make this the default connection
|
|
||||||
</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"
|
|
||||||
name="insecureSkipVerify"
|
|
||||||
checked={source.insecureSkipVerify}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
|
|
||||||
</div>
|
|
||||||
<label className="form-helper">{insecureSkipVerifyText}</label>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className="form-group form-group-submit text-center col-xs-12 col-sm-6 col-sm-offset-3">
|
|
||||||
<button
|
|
||||||
className={classnames('btn btn-block', {
|
|
||||||
'btn-primary': editMode,
|
|
||||||
'btn-success': !editMode,
|
|
||||||
})}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<span className={`icon ${editMode ? 'checkmark' : 'plus'}`} />
|
|
||||||
{editMode ? 'Save Changes' : 'Add Connection'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
{isUsingAuth ? (
|
|
||||||
<button className="btn btn-link btn-sm" onClick={gotoPurgatory}>
|
|
||||||
<span className="icon shuffle" /> Switch Orgs
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const {bool, func, shape, string} = PropTypes
|
|
||||||
|
|
||||||
SourceForm.propTypes = {
|
|
||||||
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,
|
|
||||||
onBlurSourceURL: func.isRequired,
|
|
||||||
me: shape({
|
|
||||||
role: string,
|
|
||||||
currentOrganization: shape({
|
|
||||||
id: string.isRequired,
|
|
||||||
name: string.isRequired,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
isUsingAuth: bool,
|
|
||||||
isInitialSource: bool,
|
|
||||||
gotoPurgatory: func,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me})
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(SourceForm)
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
import React, {PureComponent, FocusEvent, MouseEvent, ChangeEvent} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText'
|
||||||
|
|
||||||
|
import {SUPERADMIN_ROLE} from 'src/auth/Authorized'
|
||||||
|
import {Source, Me} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
me: Me
|
||||||
|
source: Partial<Source>
|
||||||
|
editMode: boolean
|
||||||
|
isUsingAuth: boolean
|
||||||
|
gotoPurgatory: () => void
|
||||||
|
isInitialSource: boolean
|
||||||
|
onSubmit: (e: MouseEvent<HTMLFormElement>) => void
|
||||||
|
onInputChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
|
onBlurSourceURL: (e: FocusEvent<HTMLInputElement>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SourceForm extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
onSubmit,
|
||||||
|
isUsingAuth,
|
||||||
|
onInputChange,
|
||||||
|
gotoPurgatory,
|
||||||
|
onBlurSourceURL,
|
||||||
|
isInitialSource,
|
||||||
|
} = this.props
|
||||||
|
return (
|
||||||
|
<div className="panel-body">
|
||||||
|
{isUsingAuth && isInitialSource && this.authIndicatior}
|
||||||
|
<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"
|
||||||
|
className="form-control"
|
||||||
|
id="connect-string"
|
||||||
|
placeholder="Address of InfluxDB"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.url}
|
||||||
|
onBlur={onBlurSourceURL}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-xs-12 col-sm-6">
|
||||||
|
<label htmlFor="name">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
className="form-control"
|
||||||
|
id="name"
|
||||||
|
placeholder="Name this source"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.name}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-xs-12 col-sm-6">
|
||||||
|
<label htmlFor="username">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
className="form-control"
|
||||||
|
id="username"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.username}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-xs-12 col-sm-6">
|
||||||
|
<label htmlFor="password">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
className="form-control"
|
||||||
|
id="password"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.password}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{this.isEnterprise && (
|
||||||
|
<div className="form-group col-xs-12">
|
||||||
|
<label htmlFor="meta-url">Meta Service Connection URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="metaUrl"
|
||||||
|
className="form-control"
|
||||||
|
id="meta-url"
|
||||||
|
placeholder="http://localhost:8091"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.metaUrl}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="form-group col-xs-12 col-sm-6">
|
||||||
|
<label htmlFor="telegraf">Telegraf Database</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="telegraf"
|
||||||
|
className="form-control"
|
||||||
|
id="telegraf"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.telegraf}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-xs-12 col-sm-6">
|
||||||
|
<label htmlFor="defaultRP">Default Retention Policy</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="defaultRP"
|
||||||
|
className="form-control"
|
||||||
|
id="defaultRP"
|
||||||
|
onChange={onInputChange}
|
||||||
|
value={source.defaultRP}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-xs-12">
|
||||||
|
<div className="form-control-static">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="defaultConnectionCheckbox"
|
||||||
|
name="default"
|
||||||
|
checked={source.default}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="defaultConnectionCheckbox">
|
||||||
|
Make this the default connection
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.isHTTPS && (
|
||||||
|
<div className="form-group col-xs-12">
|
||||||
|
<div className="form-control-static">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="insecureSkipVerifyCheckbox"
|
||||||
|
name="insecureSkipVerify"
|
||||||
|
checked={source.insecureSkipVerify}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
|
||||||
|
</div>
|
||||||
|
<label className="form-helper">{insecureSkipVerifyText}</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="form-group form-group-submit text-center col-xs-12 col-sm-6 col-sm-offset-3">
|
||||||
|
<button className={this.submitClass} type="submit">
|
||||||
|
<span className={this.submitIconClass} />
|
||||||
|
{this.submitText}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
{isUsingAuth && (
|
||||||
|
<button className="btn btn-link btn-sm" onClick={gotoPurgatory}>
|
||||||
|
<span className="icon shuffle" /> Switch Orgs
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get authIndicatior(): JSX.Element {
|
||||||
|
const {me} = this.props
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
{me.role.name === SUPERADMIN_ROLE ? (
|
||||||
|
<h3>
|
||||||
|
<strong>{me.currentOrganization.name}</strong> has no connections
|
||||||
|
</h3>
|
||||||
|
) : (
|
||||||
|
<h3>
|
||||||
|
<strong>{me.currentOrganization.name}</strong> has no connections
|
||||||
|
available to <em>{me.role}s</em>
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
|
<h6>Add a Connection below:</h6>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get submitText(): string {
|
||||||
|
const {editMode} = this.props
|
||||||
|
if (editMode) {
|
||||||
|
return 'Save Changes'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Add Connection'
|
||||||
|
}
|
||||||
|
|
||||||
|
private get submitIconClass(): string {
|
||||||
|
const {editMode} = this.props
|
||||||
|
return `icon ${editMode ? 'checkmark' : 'plus'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private get submitClass(): string {
|
||||||
|
const {editMode} = this.props
|
||||||
|
return classnames('btn btn-block', {
|
||||||
|
'btn-primary': editMode,
|
||||||
|
'btn-success': !editMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isEnterprise(): boolean {
|
||||||
|
const {source} = this.props
|
||||||
|
return _.get(source, 'type', '').includes('enterprise')
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isHTTPS(): boolean {
|
||||||
|
const {source} = this.props
|
||||||
|
return _.get(source, 'url', '').startsWith('https')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me})
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(SourceForm)
|
|
@ -1,47 +1,67 @@
|
||||||
import React, {Component} from 'react'
|
import React, {PureComponent, MouseEvent, ChangeEvent} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import {withRouter, WithRouterProps} from 'react-router'
|
||||||
import {withRouter} from 'react-router'
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import {getSource} from 'shared/apis'
|
import {getSource} from 'src/shared/apis'
|
||||||
import {createSource, updateSource} from 'shared/apis'
|
import {createSource, updateSource} from 'src/shared/apis'
|
||||||
import {
|
import {
|
||||||
addSource as addSourceAction,
|
addSource as addSourceAction,
|
||||||
updateSource as updateSourceAction,
|
updateSource as updateSourceAction,
|
||||||
} from 'shared/actions/sources'
|
AddSource,
|
||||||
import {notify as notifyAction} from 'shared/actions/notifications'
|
UpdateSource,
|
||||||
|
} from 'src/shared/actions/sources'
|
||||||
|
import {
|
||||||
|
notify as notifyAction,
|
||||||
|
PubishNotification,
|
||||||
|
} from 'src/shared/actions/notifications'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {bindActionCreators} from 'redux'
|
|
||||||
|
|
||||||
import Notifications from 'shared/components/Notifications'
|
import Notifications from 'src/shared/components/Notifications'
|
||||||
import SourceForm from 'src/sources/components/SourceForm'
|
import SourceForm from 'src/sources/components/SourceForm'
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||||
import SourceIndicator from 'shared/components/SourceIndicator'
|
import SourceIndicator from 'src/shared/components/SourceIndicator'
|
||||||
import {DEFAULT_SOURCE} from 'shared/constants'
|
import {DEFAULT_SOURCE} from 'src/shared/constants'
|
||||||
const initialPath = '/sources/new'
|
import {Source} from 'src/types'
|
||||||
|
|
||||||
|
const INITIAL_PATH = '/sources/new'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
notifyErrorConnectingToSource,
|
|
||||||
notifySourceCreationSucceeded,
|
|
||||||
notifySourceCreationFailed,
|
|
||||||
notifySourceUdpated,
|
notifySourceUdpated,
|
||||||
notifySourceUdpateFailed,
|
notifySourceUdpateFailed,
|
||||||
} from 'shared/copy/notifications'
|
notifySourceCreationFailed,
|
||||||
|
notifyErrorConnectingToSource,
|
||||||
|
notifySourceCreationSucceeded,
|
||||||
|
} from 'src/shared/copy/notifications'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
|
interface Props extends WithRouterProps {
|
||||||
|
notify: PubishNotification
|
||||||
|
addSource: AddSource
|
||||||
|
updateSource: UpdateSource
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isCreated: boolean
|
||||||
|
isLoading: boolean
|
||||||
|
source: Partial<Source>
|
||||||
|
editMode: boolean
|
||||||
|
isInitialSource: boolean
|
||||||
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class SourcePage extends Component {
|
class SourcePage extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
isCreated: false,
|
||||||
source: DEFAULT_SOURCE,
|
source: DEFAULT_SOURCE,
|
||||||
editMode: props.params.id !== undefined,
|
editMode: props.params.id !== undefined,
|
||||||
isInitialSource: props.router.location.pathname === initialPath,
|
isInitialSource: props.router.location.pathname === INITIAL_PATH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public async componentDidMount() {
|
||||||
const {editMode} = this.state
|
const {editMode} = this.state
|
||||||
const {params, notify} = this.props
|
const {params, notify} = this.props
|
||||||
|
|
||||||
|
@ -49,156 +69,19 @@ class SourcePage extends Component {
|
||||||
return this.setState({isLoading: false})
|
return this.setState({isLoading: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
getSource(params.id)
|
try {
|
||||||
.then(({data: source}) => {
|
const source = await getSource(params.id)
|
||||||
this.setState({
|
this.setState({
|
||||||
source: {...DEFAULT_SOURCE, ...source},
|
source: {...DEFAULT_SOURCE, ...source},
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
notify(notifyErrorConnectingToSource(this.parseError(error)))
|
||||||
notify(notifyErrorConnectingToSource(this._parseError(error)))
|
|
||||||
this.setState({isLoading: false})
|
this.setState({isLoading: false})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange = e => {
|
public render() {
|
||||||
let val = e.target.value
|
|
||||||
const name = e.target.name
|
|
||||||
|
|
||||||
if (e.target.type === 'checkbox') {
|
|
||||||
val = e.target.checked
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(prevState => {
|
|
||||||
const source = {
|
|
||||||
...prevState.source,
|
|
||||||
[name]: val,
|
|
||||||
}
|
|
||||||
|
|
||||||
return {...prevState, source}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlurSourceURL = () => {
|
|
||||||
const {source, editMode} = this.state
|
|
||||||
if (editMode) {
|
|
||||||
this.setState(this._normalizeSource)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!source.url) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(this._normalizeSource, this._createSourceOnBlur)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
e.preventDefault()
|
|
||||||
const {isCreated, editMode} = this.state
|
|
||||||
const isNewSource = !editMode
|
|
||||||
|
|
||||||
if (!isCreated && isNewSource) {
|
|
||||||
return this.setState(this._normalizeSource, this._createSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(this._normalizeSource, this._updateSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
gotoPurgatory = () => {
|
|
||||||
const {router} = this.props
|
|
||||||
router.push('/purgatory')
|
|
||||||
}
|
|
||||||
|
|
||||||
_normalizeSource({source}) {
|
|
||||||
const url = source.url.trim()
|
|
||||||
if (source.url.startsWith('http')) {
|
|
||||||
return {source: {...source, url}}
|
|
||||||
}
|
|
||||||
return {source: {...source, url: `http://${url}`}}
|
|
||||||
}
|
|
||||||
|
|
||||||
_createSourceOnBlur = () => {
|
|
||||||
const {source} = this.state
|
|
||||||
// if there is a type on source it has already been created
|
|
||||||
if (source.type) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
createSource(source)
|
|
||||||
.then(({data: sourceFromServer}) => {
|
|
||||||
this.props.addSource(sourceFromServer)
|
|
||||||
this.setState({
|
|
||||||
source: {...DEFAULT_SOURCE, ...sourceFromServer},
|
|
||||||
isCreated: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
// dont want to flash this until they submit
|
|
||||||
const error = this._parseError(err)
|
|
||||||
console.error('Error creating InfluxDB connection: ', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_createSource = () => {
|
|
||||||
const {source} = this.state
|
|
||||||
const {notify} = this.props
|
|
||||||
createSource(source)
|
|
||||||
.then(({data: sourceFromServer}) => {
|
|
||||||
this.props.addSource(sourceFromServer)
|
|
||||||
this._redirect(sourceFromServer)
|
|
||||||
notify(notifySourceCreationSucceeded(source.name))
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
notify(notifySourceCreationFailed(source.name, this._parseError(error)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateSource = () => {
|
|
||||||
const {source} = this.state
|
|
||||||
const {notify} = this.props
|
|
||||||
updateSource(source)
|
|
||||||
.then(({data: sourceFromServer}) => {
|
|
||||||
this.props.updateSource(sourceFromServer)
|
|
||||||
this._redirect(sourceFromServer)
|
|
||||||
notify(notifySourceUdpated(source.name))
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
notify(notifySourceUdpateFailed(source.name, this._parseError(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`)
|
|
||||||
}
|
|
||||||
|
|
||||||
_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 {isLoading, source, editMode, isInitialSource} = this.state
|
const {isLoading, source, editMode, isInitialSource} = this.state
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
@ -248,31 +131,147 @@ class SourcePage extends Component {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleSubmit = (e: MouseEvent<HTMLFormElement>): void => {
|
||||||
|
e.preventDefault()
|
||||||
|
const {isCreated, editMode} = this.state
|
||||||
|
const isNewSource = !editMode
|
||||||
|
|
||||||
|
if (!isCreated && isNewSource) {
|
||||||
|
return this.setState(this.normalizeSource, this.createSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(this.normalizeSource, this.updateSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
private gotoPurgatory = (): void => {
|
||||||
|
const {router} = this.props
|
||||||
|
router.push('/purgatory')
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeSource({source}) {
|
||||||
|
const url = source.url.trim()
|
||||||
|
if (source.url.startsWith('http')) {
|
||||||
|
return {source: {...source, url}}
|
||||||
|
}
|
||||||
|
return {source: {...source, url: `http://${url}`}}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSourceOnBlur = async () => {
|
||||||
|
const {source} = this.state
|
||||||
|
// if there is a type on source it has already been created
|
||||||
|
if (source.type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sourceFromServer = await createSource(source)
|
||||||
|
this.props.addSource(sourceFromServer)
|
||||||
|
this.setState({
|
||||||
|
source: {...DEFAULT_SOURCE, ...sourceFromServer},
|
||||||
|
isCreated: true,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
// dont want to flash this until they submit
|
||||||
|
const error = this.parseError(err)
|
||||||
|
console.error('Error creating InfluxDB connection: ', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSource = async () => {
|
||||||
|
const {source} = this.state
|
||||||
|
const {notify} = this.props
|
||||||
|
try {
|
||||||
|
const sourceFromServer = await createSource(source)
|
||||||
|
this.props.addSource(sourceFromServer)
|
||||||
|
this.redirect(sourceFromServer)
|
||||||
|
notify(notifySourceCreationSucceeded(source.name))
|
||||||
|
} catch (err) {
|
||||||
|
// dont want to flash this until they submit
|
||||||
|
notify(notifySourceCreationFailed(source.name, this.parseError(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSource = async () => {
|
||||||
|
const {source} = this.state
|
||||||
|
const {notify} = this.props
|
||||||
|
try {
|
||||||
|
const sourceFromServer = await updateSource(source)
|
||||||
|
this.props.updateSource(sourceFromServer)
|
||||||
|
this.redirect(sourceFromServer)
|
||||||
|
notify(notifySourceUdpated(source.name))
|
||||||
|
} catch (error) {
|
||||||
|
notify(notifySourceUdpateFailed(source.name, this.parseError(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private redirect = source => {
|
||||||
|
const {isInitialSource} = this.state
|
||||||
|
const {params, router} = this.props
|
||||||
|
|
||||||
|
if (isInitialSource) {
|
||||||
|
return this.redirectToApp(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(`/sources/${params.sourceID}/manage-sources`)
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseError = (error): string => {
|
||||||
|
return _.get(error, ['data', 'message'], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
let val = e.target.value
|
||||||
|
const name = e.target.name
|
||||||
|
|
||||||
|
if (e.target.type === 'checkbox') {
|
||||||
|
val = e.target.checked as any
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(prevState => {
|
||||||
|
const source = {
|
||||||
|
...prevState.source,
|
||||||
|
[name]: val,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...prevState, source}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleBlurSourceURL = () => {
|
||||||
|
const {source, editMode} = this.state
|
||||||
|
if (editMode) {
|
||||||
|
this.setState(this.normalizeSource)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source.url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(this.normalizeSource, this.createSourceOnBlur)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {func, shape, string} = PropTypes
|
const mdtp = {
|
||||||
|
notify: notifyAction,
|
||||||
SourcePage.propTypes = {
|
addSource: addSourceAction,
|
||||||
params: shape({
|
updateSource: updateSourceAction,
|
||||||
id: string,
|
|
||||||
sourceID: string,
|
|
||||||
}),
|
|
||||||
router: shape({
|
|
||||||
push: func.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
location: shape({
|
|
||||||
query: shape({
|
|
||||||
redirectPath: string,
|
|
||||||
}).isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
notify: func.isRequired,
|
|
||||||
addSource: func.isRequired,
|
|
||||||
updateSource: func.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
export default connect(null, mdtp)(withRouter(SourcePage))
|
||||||
notify: bindActionCreators(notifyAction, dispatch),
|
|
||||||
addSource: bindActionCreators(addSourceAction, dispatch),
|
|
||||||
updateSource: bindActionCreators(updateSourceAction, dispatch),
|
|
||||||
})
|
|
||||||
export default connect(null, mapDispatchToProps)(withRouter(SourcePage))
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
TagValues,
|
TagValues,
|
||||||
} from './query'
|
} from './query'
|
||||||
import {AlertRule, Kapacitor, Task, RuleValues} from './kapacitor'
|
import {AlertRule, Kapacitor, Task, RuleValues} from './kapacitor'
|
||||||
import {Source, SourceLinks} from './sources'
|
import {NewSource, Source, SourceLinks} from './sources'
|
||||||
import {DropdownAction, DropdownItem, Constructable} from './shared'
|
import {DropdownAction, DropdownItem, Constructable} from './shared'
|
||||||
import {
|
import {
|
||||||
Notification,
|
Notification,
|
||||||
|
@ -70,6 +70,7 @@ export {
|
||||||
TagValues,
|
TagValues,
|
||||||
AlertRule,
|
AlertRule,
|
||||||
Kapacitor,
|
Kapacitor,
|
||||||
|
NewSource,
|
||||||
Source,
|
Source,
|
||||||
SourceLinks,
|
SourceLinks,
|
||||||
DropdownAction,
|
DropdownAction,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import {Kapacitor, Service} from './'
|
import {Kapacitor, Service} from './'
|
||||||
|
|
||||||
|
export type NewSource = Pick<Source, Exclude<keyof Source, 'id'>>
|
||||||
|
|
||||||
export interface Source {
|
export interface Source {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||||
import {shallow} from 'enzyme'
|
import {shallow} from 'enzyme'
|
||||||
|
|
||||||
import {SourceForm} from 'src/sources/components/SourceForm'
|
import {SourceForm} from 'src/sources/components/SourceForm'
|
||||||
|
import {me} from 'test/resources'
|
||||||
|
|
||||||
const setup = (override = {}) => {
|
const setup = (override = {}) => {
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
|
@ -23,7 +24,7 @@ const setup = (override = {}) => {
|
||||||
isUsingAuth: false,
|
isUsingAuth: false,
|
||||||
gotoPurgatory: noop,
|
gotoPurgatory: noop,
|
||||||
isInitialSource: false,
|
isInitialSource: false,
|
||||||
me: {},
|
me,
|
||||||
...override,
|
...override,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue