Introduce ability to add an IFQL connection
parent
f32af7bb09
commit
e3c75858e5
|
@ -0,0 +1,80 @@
|
|||
import React, {ChangeEvent, PureComponent} from 'react'
|
||||
|
||||
import Input from 'src/kapacitor/components/KapacitorFormInput'
|
||||
|
||||
import {NewService} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
service: NewService
|
||||
exists: boolean
|
||||
onSubmit: (e: ChangeEvent<HTMLFormElement>) => void
|
||||
onInputChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
class IFQLForm extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {service, onSubmit, onInputChange} = this.props
|
||||
|
||||
return (
|
||||
<div className="ifql-overlay">
|
||||
<div className="template-variable-manager--header">
|
||||
<div className="page-header__left">
|
||||
<h1 className="page-header__title">Connect to IFQL</h1>
|
||||
</div>
|
||||
<div className="page-header__right" />
|
||||
</div>
|
||||
<div className="template-variable-manager--body">
|
||||
<form onSubmit={onSubmit} style={{display: 'inline-block'}}>
|
||||
<Input
|
||||
name="url"
|
||||
label="IFQL URL"
|
||||
value={this.url}
|
||||
placeholder={this.url}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
<Input
|
||||
name="name"
|
||||
label="Name"
|
||||
value={service.name}
|
||||
placeholder={service.name}
|
||||
onChange={onInputChange}
|
||||
maxLength={33}
|
||||
/>
|
||||
<div className="form-group form-group-submit col-xs-12 text-center">
|
||||
<button
|
||||
className="btn btn-success"
|
||||
type="submit"
|
||||
data-test="submit-button"
|
||||
>
|
||||
{this.buttonText}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get buttonText(): string {
|
||||
const {exists} = this.props
|
||||
|
||||
if (exists) {
|
||||
return 'Update'
|
||||
}
|
||||
|
||||
return 'Connect'
|
||||
}
|
||||
|
||||
private get url(): string {
|
||||
const {
|
||||
service: {url},
|
||||
} = this.props
|
||||
if (url) {
|
||||
return url
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export default IFQLForm
|
|
@ -0,0 +1,95 @@
|
|||
import React, {PureComponent, ChangeEvent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import IFQLForm from 'src/ifql/components/IFQLForm'
|
||||
|
||||
import {Source, NewService, Notification} from 'src/types'
|
||||
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
import {
|
||||
createServiceAsync,
|
||||
CreateServiceAsync,
|
||||
} from 'src/shared/actions/services'
|
||||
import {ifqlCreated, ifqlNotCreated} from 'src/shared/copy/notifications'
|
||||
|
||||
interface Props {
|
||||
source: Source
|
||||
onDismiss: () => void
|
||||
notify: (message: Notification) => void
|
||||
createService: CreateServiceAsync
|
||||
}
|
||||
|
||||
interface State {
|
||||
service: NewService
|
||||
}
|
||||
|
||||
const port = 8093
|
||||
|
||||
class IFQLOverlay extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
service: this.defaultService,
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<IFQLForm
|
||||
service={this.state.service}
|
||||
onSubmit={this.handleSubmit}
|
||||
onInputChange={this.handleInputChange}
|
||||
exists={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private get defaultService(): NewService {
|
||||
return {
|
||||
name: 'IFQL',
|
||||
url: this.url,
|
||||
username: '',
|
||||
insecureSkipVerify: false,
|
||||
type: 'ifql',
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
const {value, name} = e.target
|
||||
const update = {[name]: value}
|
||||
|
||||
this.setState({service: {...this.state.service, ...update}})
|
||||
}
|
||||
|
||||
private handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
const {notify, source, onDismiss, createService} = this.props
|
||||
|
||||
const {service} = this.state
|
||||
|
||||
try {
|
||||
await createService(source, service)
|
||||
} catch (error) {
|
||||
notify(ifqlNotCreated(error.message))
|
||||
return
|
||||
}
|
||||
|
||||
notify(ifqlCreated)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
private get url(): string {
|
||||
const parser = document.createElement('a')
|
||||
parser.href = this.props.source.url
|
||||
|
||||
return `${parser.protocol}//${parser.hostname}:${port}`
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
notify: notifyAction,
|
||||
createService: createServiceAsync,
|
||||
}
|
||||
|
||||
export default connect(null, mdtp)(IFQLOverlay)
|
|
@ -1,14 +1,21 @@
|
|||
import {PureComponent, ReactChildren} from 'react'
|
||||
import React, {PureComponent, ReactChildren} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {Source} from 'src/types'
|
||||
|
||||
import * as actions from 'src/shared/actions/services'
|
||||
import IFQLOverlay from 'src/ifql/components/IFQLOverlay'
|
||||
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||
import {Source, Service} from 'src/types'
|
||||
import * as a from 'src/shared/actions/overlayTechnology'
|
||||
import * as b from 'src/shared/actions/services'
|
||||
|
||||
const actions = {...a, ...b}
|
||||
|
||||
interface Props {
|
||||
sources: Source[]
|
||||
services: Service[]
|
||||
children: ReactChildren
|
||||
fetchServicesAsync: actions.FetchServicesAsync
|
||||
showOverlay: a.ShowOverlay
|
||||
fetchServicesAsync: b.FetchServicesAsync
|
||||
}
|
||||
|
||||
export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
||||
|
@ -22,17 +29,40 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
|||
}
|
||||
|
||||
await this.props.fetchServicesAsync(source)
|
||||
|
||||
if (!this.props.services.length) {
|
||||
this.overlay()
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return this.props.children
|
||||
}
|
||||
|
||||
private overlay() {
|
||||
const {showOverlay, services, sources, params} = this.props
|
||||
const source = sources.find(s => s.id === params.sourceID)
|
||||
|
||||
if (services.length) {
|
||||
return
|
||||
}
|
||||
|
||||
showOverlay(
|
||||
<OverlayContext.Consumer>
|
||||
{({onDismissOverlay}) => (
|
||||
<IFQLOverlay onDismiss={onDismissOverlay} source={source} />
|
||||
)}
|
||||
</OverlayContext.Consumer>,
|
||||
{}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
fetchServicesAsync: actions.fetchServicesAsync,
|
||||
showOverlay: actions.showOverlay,
|
||||
}
|
||||
|
||||
const mstp = ({sources}) => ({sources})
|
||||
const mstp = ({sources, services}) => ({sources, services})
|
||||
|
||||
export default connect(mstp, mdtp)(withRouter(CheckServices))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import {RouteComponentProps} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
|
@ -7,7 +7,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
import CheckServices from 'src/ifql/containers/CheckServices'
|
||||
import TimeMachine from 'src/ifql/components/TimeMachine'
|
||||
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
|
||||
import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql'
|
||||
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
import {analyzeSuccess} from 'src/shared/copy/notifications'
|
||||
|
||||
|
@ -16,9 +16,16 @@ import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis'
|
|||
import {builder, argTypes} from 'src/ifql/constants'
|
||||
import {funcNames} from 'src/ifql/constants'
|
||||
|
||||
import {Notification} from 'src/types'
|
||||
import {Suggestion, FlatBody, Links} from 'src/types/ifql'
|
||||
import {Service} from 'src/types'
|
||||
import {Source, Service, Notification} from 'src/types'
|
||||
import {
|
||||
Suggestion,
|
||||
FlatBody,
|
||||
Links,
|
||||
InputArg,
|
||||
Handlers,
|
||||
DeleteFuncNodeArgs,
|
||||
Func,
|
||||
} from 'src/types/ifql'
|
||||
|
||||
interface Status {
|
||||
type: string
|
||||
|
@ -28,6 +35,7 @@ interface Status {
|
|||
interface Props {
|
||||
links: Links
|
||||
services: Service[]
|
||||
sources: Source[]
|
||||
notify: (message: Notification) => void
|
||||
}
|
||||
|
||||
|
@ -47,7 +55,10 @@ interface State {
|
|||
export const IFQLContext = React.createContext()
|
||||
|
||||
@ErrorHandling
|
||||
export class IFQLPage extends PureComponent<Props, State> {
|
||||
export class IFQLPage extends PureComponent<
|
||||
Props & RouteComponentProps<any, any>,
|
||||
State
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
|
@ -430,12 +441,12 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({links, services}) => {
|
||||
return {links: links.ifql, services}
|
||||
const mapStateToProps = ({links, services, sources}) => {
|
||||
return {links: links.ifql, services, sources}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
notify: bindActionCreators(notifyAction, dispatch),
|
||||
})
|
||||
const mapDispatchToProps = {
|
||||
notify: notifyAction,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage)
|
||||
|
|
|
@ -5,9 +5,10 @@ interface Props {
|
|||
label: string
|
||||
value: string
|
||||
placeholder: string
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
maxLength?: number
|
||||
inputType?: string
|
||||
customClass?: string
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
const KapacitorFormInput: SFC<Props> = ({
|
||||
|
@ -18,8 +19,9 @@ const KapacitorFormInput: SFC<Props> = ({
|
|||
onChange,
|
||||
maxLength,
|
||||
inputType,
|
||||
customClass,
|
||||
}) => (
|
||||
<div className="form-group">
|
||||
<div className={`form-group ${customClass}`}>
|
||||
<label htmlFor={name}>{label}</label>
|
||||
<input
|
||||
className="form-control"
|
||||
|
@ -37,6 +39,7 @@ const KapacitorFormInput: SFC<Props> = ({
|
|||
|
||||
KapacitorFormInput.defaultProps = {
|
||||
inputType: '',
|
||||
customClass: 'col-sm-6',
|
||||
}
|
||||
|
||||
export default KapacitorFormInput
|
||||
|
|
|
@ -169,6 +169,7 @@ export class KapacitorPage extends PureComponent<Props, State> {
|
|||
return (
|
||||
<KapacitorForm
|
||||
hash={hash}
|
||||
notify={notify}
|
||||
source={source}
|
||||
exists={exists}
|
||||
kapacitor={kapacitor}
|
||||
|
@ -176,7 +177,6 @@ export class KapacitorPage extends PureComponent<Props, State> {
|
|||
onChangeUrl={this.handleChangeUrl}
|
||||
onReset={this.handleResetToDefaults}
|
||||
onInputChange={this.handleInputChange}
|
||||
notify={notify}
|
||||
onCheckboxChange={this.handleCheckboxChange}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -8,6 +8,19 @@ interface Options {
|
|||
transitionTime?: number
|
||||
}
|
||||
|
||||
export type ShowOverlay = (
|
||||
OverlayNode: OverlayNodeType,
|
||||
options: Options
|
||||
) => ActionOverlayNode
|
||||
|
||||
export interface ActionOverlayNode {
|
||||
type: 'SHOW_OVERLAY'
|
||||
payload: {
|
||||
OverlayNode
|
||||
options
|
||||
}
|
||||
}
|
||||
|
||||
export const showOverlay = (
|
||||
OverlayNode: OverlayNodeType,
|
||||
options: Options
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import {Source, Service} from 'src/types'
|
||||
import {getServices as getServicesAJAX} from 'src/shared/apis'
|
||||
import {Source, Service, NewService} from 'src/types'
|
||||
import {
|
||||
getServices as getServicesAJAX,
|
||||
createService as createServiceAJAX,
|
||||
} from 'src/shared/apis'
|
||||
import {notify} from './notifications'
|
||||
import {couldNotGetServices} from 'src/shared/copy/notifications'
|
||||
|
||||
|
@ -99,7 +102,6 @@ export const setActiveService = (
|
|||
})
|
||||
|
||||
export type FetchServicesAsync = (source: Source) => (dispatch) => Promise<void>
|
||||
|
||||
export const fetchServicesAsync = (source: Source) => async (
|
||||
dispatch
|
||||
): Promise<void> => {
|
||||
|
@ -110,3 +112,21 @@ export const fetchServicesAsync = (source: Source) => async (
|
|||
dispatch(notify(couldNotGetServices))
|
||||
}
|
||||
}
|
||||
|
||||
export type CreateServiceAsync = (
|
||||
source: Source,
|
||||
service: NewService
|
||||
) => (dispatch) => Promise<void>
|
||||
|
||||
export const createServiceAsync = (
|
||||
source: Source,
|
||||
service: NewService
|
||||
) => async (dispatch): Promise<void> => {
|
||||
try {
|
||||
const s = await createServiceAJAX(source, service)
|
||||
dispatch(addService(s))
|
||||
} catch (err) {
|
||||
console.error(err.data)
|
||||
throw err.data
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AJAX from 'src/utils/ajax'
|
||||
import {AlertTypes} from 'src/kapacitor/constants'
|
||||
import {Kapacitor, Service} from 'src/types'
|
||||
import {Kapacitor, Source, Service, NewService} from 'src/types'
|
||||
|
||||
export function getSources() {
|
||||
return AJAX({
|
||||
|
@ -311,6 +311,31 @@ export const getServices = async (url: string): Promise<Service[]> => {
|
|||
method: 'GET',
|
||||
})
|
||||
|
||||
return data.services
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const createService = async (
|
||||
source: Source,
|
||||
{
|
||||
url,
|
||||
name = 'My IFQLD',
|
||||
type,
|
||||
username,
|
||||
password,
|
||||
insecureSkipVerify,
|
||||
}: NewService
|
||||
): Promise<Service> => {
|
||||
try {
|
||||
const {data} = await AJAX({
|
||||
url: source.links.services,
|
||||
method: 'POST',
|
||||
data: {url, name, type, username, password, insecureSkipVerify},
|
||||
})
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// All copy for notifications should be stored here for easy editing
|
||||
// and ensuring stylistic consistency
|
||||
|
||||
import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'shared/constants/index'
|
||||
import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index'
|
||||
|
||||
const defaultErrorNotification = {
|
||||
type: 'error',
|
||||
|
@ -620,3 +620,13 @@ export const couldNotGetServices = {
|
|||
...defaultErrorNotification,
|
||||
message: 'We could not get services',
|
||||
}
|
||||
|
||||
export const ifqlCreated = {
|
||||
...defaultSuccessNotification,
|
||||
message: 'IFQL Connection Created. Script your heart out!',
|
||||
}
|
||||
|
||||
export const ifqlNotCreated = (message: string) => ({
|
||||
...defaultErrorNotification,
|
||||
message,
|
||||
})
|
|
@ -0,0 +1,4 @@
|
|||
.ifql-overlay {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@import '../components/time-machine/ifql-overlay';
|
||||
@import '../components/time-machine/ifql-editor';
|
||||
@import '../components/time-machine/ifql-builder';
|
||||
@import '../components/time-machine/ifql-explorer';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Service} from './services'
|
||||
import {Service, NewService} from './services'
|
||||
import {AuthLinks, Organization, Role, User, Me} from './auth'
|
||||
import {Template, Cell, CellQuery, Legend, Axes} from './dashboard'
|
||||
import {
|
||||
|
@ -55,4 +55,5 @@ export {
|
|||
NotificationFunc,
|
||||
Axes,
|
||||
Service,
|
||||
NewService,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
export interface NewService {
|
||||
url: string
|
||||
name: string
|
||||
type: string
|
||||
username?: string
|
||||
password?: string
|
||||
active: boolean
|
||||
insecureSkipVerify: boolean
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id?: string
|
||||
url: string
|
||||
|
|
Loading…
Reference in New Issue