Refactor connection
This commit is contained in:
@@ -30,7 +30,6 @@
|
|||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background-color: rgba(140,140,140,0.8);
|
background-color: rgba(140,140,140,0.8);
|
||||||
#-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.8);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface State {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
|
connectionId: string
|
||||||
theme: Theme
|
theme: Theme
|
||||||
settingsVisible: boolean
|
settingsVisible: boolean
|
||||||
}
|
}
|
||||||
@@ -93,17 +94,17 @@ class App extends React.Component<Props, State> {
|
|||||||
<TitleBar />
|
<TitleBar />
|
||||||
<div style={centerContent}>
|
<div style={centerContent}>
|
||||||
<div style={this.getStyles().left}>
|
<div style={this.getStyles().left}>
|
||||||
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
|
<Tree connectionId={this.props.connectionId} didSelectNode={(node: q.TreeNode) => {
|
||||||
this.setState({ selectedNode: node })
|
this.setState({ selectedNode: node })
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
<div style={this.getStyles().right}>
|
<div style={this.getStyles().right}>
|
||||||
<Sidebar connectionId={this.state.connectionId} />
|
<Sidebar connectionId={this.props.connectionId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UpdateNotifier />
|
<UpdateNotifier />
|
||||||
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
|
<Connection />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div >
|
</div >
|
||||||
)
|
)
|
||||||
@@ -113,6 +114,7 @@ class App extends React.Component<Props, State> {
|
|||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
settingsVisible: state.settings.visible,
|
settingsVisible: state.settings.visible,
|
||||||
|
connectionId: state.connectionId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
app/src/actions/Connection.ts
Normal file
40
app/src/actions/Connection.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { ActionTypes, NodeOrder, CustomAction, AppState } from '../reducers'
|
||||||
|
import { MqttOptions } from '../../../backend/src/DataSource'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
import { rendererEvents, addMqttConnectionEvent, makeConnectionStateEvent, removeConnection } from '../../../events'
|
||||||
|
|
||||||
|
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||||
|
dispatch(connecting(connectionId))
|
||||||
|
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
||||||
|
const event = makeConnectionStateEvent(connectionId)
|
||||||
|
rendererEvents.subscribe(event, (dataSourceState) => {
|
||||||
|
if (dataSourceState.connected) {
|
||||||
|
dispatch(connected())
|
||||||
|
} else if (dataSourceState.error) {
|
||||||
|
dispatch(showError(dataSourceState.error))
|
||||||
|
dispatch(disconnect())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const connected: () => CustomAction = () => ({
|
||||||
|
type: ActionTypes.connected,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const connecting: (connectionId: string) => CustomAction = (connectionId: string) => ({
|
||||||
|
connectionId,
|
||||||
|
type: ActionTypes.connecting,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const showError = (error?: string) => ({
|
||||||
|
error,
|
||||||
|
type: ActionTypes.showError,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const disconnect = () => (dispatch: Dispatch<CustomAction>, getState: () => AppState) => {
|
||||||
|
rendererEvents.emit(removeConnection, getState().connectionId)
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.disconnect,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,5 +2,6 @@ import * as settingsActions from './Settings'
|
|||||||
import * as sidebarActions from './Sidebar'
|
import * as sidebarActions from './Sidebar'
|
||||||
import * as treeActions from './Tree'
|
import * as treeActions from './Tree'
|
||||||
import * as updateNotifierActions from './UpdateNotifier'
|
import * as updateNotifierActions from './UpdateNotifier'
|
||||||
|
import * as connectionActions from './Connection'
|
||||||
|
|
||||||
export { settingsActions, treeActions, sidebarActions, updateNotifierActions }
|
export { settingsActions, treeActions, sidebarActions, updateNotifierActions, connectionActions }
|
||||||
|
|||||||
3
app/src/bugtracking.ts
Normal file
3
app/src/bugtracking.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { electronRendererTelementry } from 'electron-telemetry'
|
||||||
|
|
||||||
|
const spareMeFromGc = electronRendererTelementry
|
||||||
@@ -18,21 +18,27 @@ import {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import { DataSourceState, MqttOptions } from '../../../../backend/src/DataSource'
|
import { connect } from 'react-redux'
|
||||||
|
import { MqttOptions } from '../../../../backend/src/DataSource'
|
||||||
import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles'
|
import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../../events'
|
|
||||||
|
|
||||||
import Notification from './Notification'
|
import Notification from './Notification'
|
||||||
import Visibility from '@material-ui/icons/Visibility'
|
import Visibility from '@material-ui/icons/Visibility'
|
||||||
import VisibilityOff from '@material-ui/icons/VisibilityOff'
|
import VisibilityOff from '@material-ui/icons/VisibilityOff'
|
||||||
|
|
||||||
import sha1 = require('sha1')
|
import sha1 = require('sha1')
|
||||||
|
import { AppState } from '../../reducers'
|
||||||
|
import { bindActionCreators } from 'redux'
|
||||||
|
import { connectionActions } from '../../actions'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
classes: {[s: string]: string}
|
classes: {[s: string]: string}
|
||||||
theme: Theme
|
theme: Theme
|
||||||
onAbort: () => void,
|
actions: typeof connectionActions,
|
||||||
onConnection: (connectionId: string) => void
|
visible: boolean
|
||||||
|
connected: boolean
|
||||||
|
connecting: boolean
|
||||||
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocols = [
|
const protocols = [
|
||||||
@@ -41,57 +47,64 @@ const protocols = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
connecting: boolean
|
showPassword: boolean
|
||||||
connectionId?: string
|
connectionSettings: ConnectionSettings
|
||||||
error?: string
|
}
|
||||||
visible: boolean
|
|
||||||
|
interface ConnectionSettings {
|
||||||
host: string
|
host: string
|
||||||
protocol: string
|
protocol: string
|
||||||
port: number
|
port: number
|
||||||
tls: boolean
|
tls: boolean
|
||||||
certValidation: boolean
|
certValidation: boolean
|
||||||
clientId: string
|
clientId: string
|
||||||
|
connectionId?: string
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
showPassword: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare var window: any
|
declare var window: any
|
||||||
|
|
||||||
class Connection extends React.Component<Props, State> {
|
class Connection extends React.Component<Props, State> {
|
||||||
private randomClientId: tring
|
private randomClientId: string
|
||||||
|
private defaultConnectionSettings: ConnectionSettings = {
|
||||||
|
host: 'iot.eclipse.org',
|
||||||
|
protocol: protocols[0],
|
||||||
|
port: 1883,
|
||||||
|
tls: false,
|
||||||
|
certValidation: true,
|
||||||
|
clientId: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
connectionId: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
const clientIdSha = sha1(`${Math.random()}`).slice(0, 8)
|
||||||
|
this.randomClientId = `mqtt-explorer-${clientIdSha}`
|
||||||
|
this.state = {
|
||||||
|
connectionSettings: this.loadConnectionSettings(),
|
||||||
|
showPassword: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadConnectionSettings(): ConnectionSettings {
|
||||||
|
let storedSettings: ConnectionSettings | undefined
|
||||||
|
|
||||||
const storedSettingsString = window.localStorage.getItem('connectionSettings')
|
const storedSettingsString = window.localStorage.getItem('connectionSettings')
|
||||||
let storedSettings
|
|
||||||
try {
|
try {
|
||||||
storedSettings = storedSettingsString ? JSON.parse(storedSettingsString) : undefined
|
storedSettings = storedSettingsString ? JSON.parse(storedSettingsString) : undefined
|
||||||
} catch {
|
} catch {
|
||||||
window.localStorage.setItem('connectionSettings', undefined)
|
window.localStorage.setItem('connectionSettings', undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientIdSha = sha1(`${Math.random()}`).slice(0, 8)
|
return storedSettings || this.defaultConnectionSettings
|
||||||
this.randomClientId = `mqtt-explorer-${clientIdSha}`
|
|
||||||
const defaultState = {
|
|
||||||
visible: true,
|
|
||||||
host: 'iot.eclipse.org',
|
|
||||||
protocol: protocols[0],
|
|
||||||
port: 1883,
|
|
||||||
tls: false,
|
|
||||||
certValidation: true,
|
|
||||||
clientId: '',
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
connecting: false,
|
|
||||||
connectionId: undefined,
|
|
||||||
showPassword: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = Object.assign({}, defaultState, storedSettings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveConnectionSettings() {
|
private saveConnectionSettings() {
|
||||||
window.localStorage.setItem('connectionSettings', JSON.stringify(this.state))
|
window.localStorage.setItem('connectionSettings', JSON.stringify(this.state.connectionSettings))
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClickShowPassword = () => {
|
private handleClickShowPassword = () => {
|
||||||
@@ -99,49 +112,19 @@ class Connection extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private optionsFromState(): MqttOptions {
|
private optionsFromState(): MqttOptions {
|
||||||
const protocol = this.state.protocol === 'tcp://' ? 'mqtt://' : this.state.protocol
|
const protocol = this.state.connectionSettings.protocol === 'tcp://' ? 'mqtt://' : this.state.connectionSettings.protocol
|
||||||
const url = `${protocol}${this.state.host}:${this.state.port}`
|
const url = `${protocol}${this.state.connectionSettings.host}:${this.state.connectionSettings.port}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
username: this.state.username || undefined,
|
username: this.state.connectionSettings.username || undefined,
|
||||||
password: this.state.password || undefined,
|
password: this.state.connectionSettings.password || undefined,
|
||||||
clientId: this.state.clientId || this.randomClientId,
|
clientId: this.state.connectionSettings.clientId || this.randomClientId,
|
||||||
tls: this.state.tls,
|
tls: this.state.connectionSettings.tls,
|
||||||
certValidation: this.state.certValidation,
|
certValidation: this.state.connectionSettings.certValidation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private connect() {
|
|
||||||
this.setState({
|
|
||||||
connecting: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const options = this.optionsFromState()
|
|
||||||
const connectionId = (sha1(Math.random() + JSON.stringify(options)).slice(0, 8)) as string
|
|
||||||
this.setState({ connectionId })
|
|
||||||
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
|
||||||
const event = makeConnectionStateEvent(connectionId)
|
|
||||||
|
|
||||||
rendererEvents.subscribe(event, (state: DataSourceState) => {
|
|
||||||
console.log(state)
|
|
||||||
if (state.connected) {
|
|
||||||
this.props.onConnection(connectionId)
|
|
||||||
this.setState({ visible: false })
|
|
||||||
} else if (state.error) {
|
|
||||||
this.setState({ error: state.error })
|
|
||||||
this.disconnect()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private disconnect() {
|
|
||||||
this.setState({
|
|
||||||
connecting: false,
|
|
||||||
})
|
|
||||||
rendererEvents.emit(removeConnection, this.state.connectionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static styles: StyleRulesCallback<string> = (theme: Theme) => {
|
public static styles: StyleRulesCallback<string> = (theme: Theme) => {
|
||||||
return {
|
return {
|
||||||
root: {
|
root: {
|
||||||
@@ -176,10 +159,12 @@ class Connection extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleChange = (name: string) => (event: any) => {
|
private handleChange = (name: string) => (event: any) => {
|
||||||
const state: any = {
|
this.setState({
|
||||||
[name]: event.target.value,
|
connectionSettings: {
|
||||||
}
|
...this.state.connectionSettings,
|
||||||
this.setState(state)
|
[name]: event.target.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@@ -197,12 +182,11 @@ class Connection extends React.Component<Props, State> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let renderError = null
|
let renderError = null
|
||||||
if (this.state.error) {
|
if (this.props.error) {
|
||||||
renderError = (
|
renderError = (
|
||||||
<Notification
|
<Notification
|
||||||
message={this.state.error}
|
message={this.props.error}
|
||||||
type="error"
|
onClose={() => { this.props.actions.showError(undefined) }}
|
||||||
onClose={() => { this.setState({ error: undefined }) }}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -210,7 +194,7 @@ class Connection extends React.Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{renderError}
|
{renderError}
|
||||||
<Modal open={this.state.visible} disableAutoFocus={true}>
|
<Modal open={this.props.visible} disableAutoFocus={true}>
|
||||||
<Paper className={classes.root}>
|
<Paper className={classes.root}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography>
|
<Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography>
|
||||||
@@ -218,26 +202,13 @@ class Connection extends React.Component<Props, State> {
|
|||||||
<form className={classes.container} noValidate={true} autoComplete="off">
|
<form className={classes.container} noValidate={true} autoComplete="off">
|
||||||
<Grid container={true} spacing={24}>
|
<Grid container={true} spacing={24}>
|
||||||
<Grid item={true} xs={2}>
|
<Grid item={true} xs={2}>
|
||||||
<TextField
|
{this.renderProtocols()}
|
||||||
select={true}
|
|
||||||
label="Protocol"
|
|
||||||
className={classes.textField}
|
|
||||||
value={this.state.protocol}
|
|
||||||
onChange={this.handleChange('protocol')}
|
|
||||||
margin="normal"
|
|
||||||
>
|
|
||||||
{protocols.map((value: string) => (
|
|
||||||
<MenuItem key={value} value={value}>
|
|
||||||
{value}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={7}>
|
<Grid item={true} xs={7}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Host"
|
label="Host"
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
value={this.state.host}
|
value={this.state.connectionSettings.host}
|
||||||
onChange={this.handleChange('host')}
|
onChange={this.handleChange('host')}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
@@ -246,7 +217,7 @@ class Connection extends React.Component<Props, State> {
|
|||||||
<TextField
|
<TextField
|
||||||
label="Port"
|
label="Port"
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
value={this.state.port}
|
value={this.state.connectionSettings.port}
|
||||||
onChange={this.handleChange('port')}
|
onChange={this.handleChange('port')}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
@@ -255,7 +226,7 @@ class Connection extends React.Component<Props, State> {
|
|||||||
<TextField
|
<TextField
|
||||||
label="Username"
|
label="Username"
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
value={this.state.username}
|
value={this.state.connectionSettings.username}
|
||||||
onChange={this.handleChange('username')}
|
onChange={this.handleChange('username')}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
@@ -266,7 +237,7 @@ class Connection extends React.Component<Props, State> {
|
|||||||
<Input
|
<Input
|
||||||
id="adornment-password"
|
id="adornment-password"
|
||||||
type={this.state.showPassword ? 'text' : 'password'}
|
type={this.state.showPassword ? 'text' : 'password'}
|
||||||
value={this.state.password}
|
value={this.state.connectionSettings.password}
|
||||||
onChange={this.handleChange('password')}
|
onChange={this.handleChange('password')}
|
||||||
endAdornment={passwordVisibilityButton}
|
endAdornment={passwordVisibilityButton}
|
||||||
/>
|
/>
|
||||||
@@ -278,41 +249,17 @@ class Connection extends React.Component<Props, State> {
|
|||||||
<Input
|
<Input
|
||||||
placeholder={this.randomClientId}
|
placeholder={this.randomClientId}
|
||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
value={this.state.clientId || ''}
|
value={this.state.connectionSettings.clientId || ''}
|
||||||
onChange={this.handleChange('clientId')}
|
onChange={this.handleChange('clientId')}
|
||||||
startAdornment={<span />}
|
startAdornment={<span />}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item={true} xs={4}>
|
<Grid item={true} xs={4}>
|
||||||
<div className={classes.switch}>
|
{this.renderCertValidationSwitch()}
|
||||||
<FormControlLabel
|
|
||||||
control={(
|
|
||||||
<Switch
|
|
||||||
checked={this.state.certValidation}
|
|
||||||
onChange={() => this.setState({ certValidation: !this.state.certValidation })}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
label="Validate certificate"
|
|
||||||
labelPlacement="bottom"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item={true} xs={3}>
|
<Grid item={true} xs={3}>
|
||||||
<div className={classes.switch}>
|
{this.renderTlsSwitch()}
|
||||||
<FormControlLabel
|
|
||||||
control={(
|
|
||||||
<Switch
|
|
||||||
checked={this.state.tls}
|
|
||||||
onChange={() => this.setState({ tls: !this.state.tls })}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
label="Encryption (tls)"
|
|
||||||
labelPlacement="bottom"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<br />
|
<br />
|
||||||
@@ -329,12 +276,90 @@ class Connection extends React.Component<Props, State> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderConnectButton() {
|
private renderProtocols() {
|
||||||
const { classes } = this.props
|
const { classes } = this.props
|
||||||
|
const protocolItems = protocols.map((value: string) => (
|
||||||
|
<MenuItem key={value} value={value}>
|
||||||
|
{value}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
|
||||||
if (this.state.connecting) {
|
return (
|
||||||
|
<TextField
|
||||||
|
select={true}
|
||||||
|
label="Protocol"
|
||||||
|
className={classes.textField}
|
||||||
|
value={this.state.connectionSettings.protocol}
|
||||||
|
onChange={this.handleChange('protocol')}
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
{protocolItems}
|
||||||
|
</TextField>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderCertValidationSwitch() {
|
||||||
|
const { classes } = this.props
|
||||||
|
const certSwitch = (
|
||||||
|
<Switch
|
||||||
|
checked={this.state.connectionSettings.certValidation}
|
||||||
|
onChange={this.toggleCertValidation}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.switch}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={certSwitch}
|
||||||
|
label="Validate certificate"
|
||||||
|
labelPlacement="bottom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleCertValidation = () => this.setState({
|
||||||
|
connectionSettings: {
|
||||||
|
...this.state.connectionSettings,
|
||||||
|
certValidation: !this.state.connectionSettings.certValidation,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
private renderTlsSwitch() {
|
||||||
|
const { classes } = this.props
|
||||||
|
const tlsSwitch = (
|
||||||
|
<Switch
|
||||||
|
checked={this.state.connectionSettings.tls}
|
||||||
|
onChange={this.toggleTls}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.switch}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={tlsSwitch}
|
||||||
|
label="Encryption (tls)"
|
||||||
|
labelPlacement="bottom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleTls = () => this.setState({
|
||||||
|
connectionSettings: {
|
||||||
|
...this.state.connectionSettings,
|
||||||
|
tls: !this.state.connectionSettings.tls,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
private renderConnectButton() {
|
||||||
|
const { classes, actions } = this.props
|
||||||
|
|
||||||
|
if (this.props.connecting) {
|
||||||
return (
|
return (
|
||||||
<Button variant="contained" color="primary" className={classes.button} onClick={this.onClickAbort}>
|
<Button variant="contained" color="primary" className={classes.button} onClick={actions.disconnect}>
|
||||||
<CircularProgress size={22} style={{ marginRight: '10px' }} color="secondary" /> Abort
|
<CircularProgress size={22} style={{ marginRight: '10px' }} color="secondary" /> Abort
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@@ -347,12 +372,25 @@ class Connection extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onClickConnect = () => {
|
private onClickConnect = () => {
|
||||||
this.connect()
|
const connectionId = String(sha1(String(Math.random())).slice(0, 8))
|
||||||
}
|
const options = this.optionsFromState()
|
||||||
|
this.props.actions.connect(options, connectionId)
|
||||||
private onClickAbort = () => {
|
|
||||||
this.disconnect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(Connection.styles, { withTheme: true })(Connection)
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
return {
|
||||||
|
visible: !state.connected,
|
||||||
|
connected: state.connected,
|
||||||
|
connecting: state.connecting,
|
||||||
|
error: state.error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: any) => {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(connectionActions, dispatch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(Connection.styles, { withTheme: true })(Connection))
|
||||||
|
|||||||
@@ -4,14 +4,8 @@ import { Snackbar, SnackbarContent } from '@material-ui/core'
|
|||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
import { green, red } from '@material-ui/core/colors'
|
import { green, red } from '@material-ui/core/colors'
|
||||||
|
|
||||||
enum MessageType {
|
|
||||||
success = 'success',
|
|
||||||
error = 'error',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message?: string
|
message?: string
|
||||||
type: MessageType
|
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
classes: any
|
classes: any
|
||||||
}
|
}
|
||||||
@@ -33,18 +27,20 @@ class Notification extends React.Component<Props, {}> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
const snackbarAnchor = {
|
||||||
|
vertical: 'bottom' as 'bottom',
|
||||||
|
horizontal: 'left' as 'left',
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Snackbar
|
<Snackbar
|
||||||
anchorOrigin={{
|
anchorOrigin={snackbarAnchor}
|
||||||
vertical: 'bottom',
|
|
||||||
horizontal: 'left',
|
|
||||||
}}
|
|
||||||
open={Boolean(this.props.message)}
|
open={Boolean(this.props.message)}
|
||||||
autoHideDuration={10000}
|
autoHideDuration={10000}
|
||||||
onClose={this.props.onClose}
|
onClose={this.props.onClose}
|
||||||
>
|
>
|
||||||
<SnackbarContent
|
<SnackbarContent
|
||||||
className={this.props.classes[this.props.type]}
|
className={this.props.classes.error}
|
||||||
message={this.props.message}
|
message={this.props.message}
|
||||||
/>
|
/>
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as q from '../../../backend/src/Model'
|
import * as q from '../../../backend/src/Model'
|
||||||
|
|
||||||
import { AppBar, IconButton, InputBase, Toolbar, Typography } from '@material-ui/core'
|
import { AppBar, Button, IconButton, InputBase, Toolbar, Typography } from '@material-ui/core'
|
||||||
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
|
import { StyleRulesCallback, withStyles } from '@material-ui/core/styles'
|
||||||
|
import CloudOff from '@material-ui/icons/CloudOff'
|
||||||
|
|
||||||
import Menu from '@material-ui/icons/Menu'
|
import Menu from '@material-ui/icons/Menu'
|
||||||
import Search from '@material-ui/icons/Search'
|
import Search from '@material-ui/icons/Search'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { fade } from '@material-ui/core/styles/colorManipulator'
|
import { fade } from '@material-ui/core/styles/colorManipulator'
|
||||||
import { settingsActions } from '../actions'
|
import { settingsActions, connectionActions } from '../actions'
|
||||||
|
|
||||||
const styles: StyleRulesCallback = theme => ({
|
const styles: StyleRulesCallback = theme => ({
|
||||||
title: {
|
title: {
|
||||||
@@ -85,7 +86,7 @@ class TitleBar extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { classes } = this.props
|
const { actions, classes } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
@@ -94,6 +95,9 @@ class TitleBar extends React.Component<Props, State> {
|
|||||||
<Menu />
|
<Menu />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography className={classes.title} variant="h6" color="inherit">MQTT-Explorer</Typography>
|
<Typography className={classes.title} variant="h6" color="inherit">MQTT-Explorer</Typography>
|
||||||
|
<Button style={{ margin: 'auto 8px auto auto' }} onClick={actions.disconnect}>
|
||||||
|
Disconnect <CloudOff style={{ marginRight: '8px', paddingLeft: '8px' }}/>
|
||||||
|
</Button>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
)
|
)
|
||||||
@@ -119,8 +123,8 @@ class TitleBar extends React.Component<Props, State> {
|
|||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => {
|
const mapDispatchToProps = (dispatch: any) => {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(settingsActions, dispatch),
|
actions: { ...bindActionCreators(connectionActions, dispatch), ...bindActionCreators(settingsActions, dispatch) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(connect(null, mapDispatchToProps)(TitleBar))
|
export default connect(undefined, mapDispatchToProps)(withStyles(styles)(TitleBar))
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface Props {
|
|||||||
autoExpandLimit: number
|
autoExpandLimit: number
|
||||||
didSelectNode?: (node: q.TreeNode) => void
|
didSelectNode?: (node: q.TreeNode) => void
|
||||||
connectionId?: string
|
connectionId?: string
|
||||||
|
connected: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeState {
|
interface TreeState {
|
||||||
@@ -32,8 +33,7 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
const tree = new q.Tree()
|
this.state = { tree: new q.Tree(), msg: {} }
|
||||||
this.state = { tree, msg: {} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public time(): number {
|
public time(): number {
|
||||||
@@ -67,20 +67,18 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: Props) {
|
public componentWillReceiveProps(nextProps: Props) {
|
||||||
if (this.props.connectionId) {
|
this.registerAndUnregisterEventSubscriptionsForNewProps(nextProps)
|
||||||
const event = makeConnectionMessageEvent(this.props.connectionId)
|
|
||||||
rendererEvents.unsubscribeAll(event)
|
|
||||||
}
|
|
||||||
if (nextProps.connectionId) {
|
|
||||||
const event = makeConnectionMessageEvent(nextProps.connectionId)
|
|
||||||
rendererEvents.subscribe(event, this.handleNewData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
private registerAndUnregisterEventSubscriptionsForNewProps(nextProps: Props) {
|
||||||
if (this.props.connectionId) {
|
if (this.props.connectionId !== nextProps.connectionId) {
|
||||||
const event = makeConnectionMessageEvent(this.props.connectionId)
|
if (this.props.connectionId) {
|
||||||
rendererEvents.subscribe(event, this.handleNewData)
|
this.setState({ tree: new q.Tree() })
|
||||||
|
rendererEvents.unsubscribeAll(makeConnectionMessageEvent(this.props.connectionId))
|
||||||
|
}
|
||||||
|
if (nextProps.connectionId) {
|
||||||
|
rendererEvents.subscribe(makeConnectionMessageEvent(nextProps.connectionId), this.handleNewData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +90,7 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleNewData = (msg: any) => {
|
private handleNewData = (msg: any) => {
|
||||||
|
console.log('new data')
|
||||||
const edges = msg.topic.split('/')
|
const edges = msg.topic.split('/')
|
||||||
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
||||||
this.state.tree.updateWithNode(node.firstNode())
|
this.state.tree.updateWithNode(node.firstNode())
|
||||||
@@ -100,7 +99,9 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
console.log('render called')
|
if (!this.props.connected) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
lineHeight: '1.1',
|
lineHeight: '1.1',
|
||||||
@@ -129,6 +130,7 @@ class Tree extends React.Component<Props, TreeState> {
|
|||||||
const mapStateToProps = (state: AppState) => {
|
const mapStateToProps = (state: AppState) => {
|
||||||
return {
|
return {
|
||||||
autoExpandLimit: state.settings.autoExpandLimit,
|
autoExpandLimit: state.settings.autoExpandLimit,
|
||||||
|
connected: state.connected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,24 @@ import './tracking'
|
|||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as ReactDOM from 'react-dom'
|
import * as ReactDOM from 'react-dom'
|
||||||
|
import reduxThunk from 'redux-thunk'
|
||||||
|
|
||||||
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
|
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
|
||||||
import reducers, { AppState, NodeOrder } from './reducers'
|
import reducers, { AppState, NodeOrder } from './reducers'
|
||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { createStore } from 'redux'
|
import { createStore, applyMiddleware, compose } from 'redux'
|
||||||
|
|
||||||
const initialAppState: AppState = {
|
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||||
settings: {
|
const store = createStore(
|
||||||
autoExpandLimit: 0,
|
reducers,
|
||||||
nodeOrder: NodeOrder.none,
|
composeEnhancers(
|
||||||
visible: false,
|
applyMiddleware(
|
||||||
},
|
reduxThunk,
|
||||||
sidebar: {},
|
),
|
||||||
selectedTopic: undefined,
|
),
|
||||||
showUpdateDetails: false,
|
)
|
||||||
}
|
|
||||||
const store = createStore(reducers, initialAppState)
|
|
||||||
|
|
||||||
const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ import * as q from '../../../backend/src/Model'
|
|||||||
import { Action, Reducer } from 'redux'
|
import { Action, Reducer } from 'redux'
|
||||||
|
|
||||||
import { trackEvent } from '../tracking'
|
import { trackEvent } from '../tracking'
|
||||||
|
import { MqttOptions, DataSourceStateMachine } from '../../../backend/src/DataSource'
|
||||||
|
import { rendererEvents, addMqttConnectionEvent, makeConnectionStateEvent } from '../../../events'
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
|
disconnect = 'DISCONNECT',
|
||||||
|
showError = 'SHOW_ERROR',
|
||||||
setAutoExpandLimit = 'SET_AUTO_EXPAND_LIMIT',
|
setAutoExpandLimit = 'SET_AUTO_EXPAND_LIMIT',
|
||||||
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
|
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
|
||||||
setNodeOrder = 'SET_NODE_ORDER',
|
setNodeOrder = 'SET_NODE_ORDER',
|
||||||
@@ -13,6 +17,8 @@ export enum ActionTypes {
|
|||||||
setPublishPayload = 'SET_PUBLISH_PAYLOAD',
|
setPublishPayload = 'SET_PUBLISH_PAYLOAD',
|
||||||
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
||||||
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
||||||
|
connecting = 'CONNECTING',
|
||||||
|
connected = 'CONNECTED',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomAction extends Action {
|
export interface CustomAction extends Action {
|
||||||
@@ -24,6 +30,9 @@ export interface CustomAction extends Action {
|
|||||||
publishPayload?: string
|
publishPayload?: string
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails?: boolean
|
showUpdateDetails?: boolean
|
||||||
|
connectionOptions?: MqttOptions
|
||||||
|
connectionId?: string
|
||||||
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarState {
|
export interface SidebarState {
|
||||||
@@ -37,6 +46,10 @@ export interface AppState {
|
|||||||
sidebar: SidebarState
|
sidebar: SidebarState
|
||||||
showUpdateNotification?: boolean
|
showUpdateNotification?: boolean
|
||||||
showUpdateDetails: boolean
|
showUpdateDetails: boolean
|
||||||
|
connecting: boolean
|
||||||
|
connected: boolean
|
||||||
|
error?: string
|
||||||
|
connectionId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsState {
|
export interface SettingsState {
|
||||||
@@ -52,7 +65,21 @@ export enum NodeOrder {
|
|||||||
topics = '#topics',
|
topics = '#topics',
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer: Reducer<AppState | undefined, CustomAction> = (state, action) => {
|
const initialAppState: AppState = {
|
||||||
|
settings: {
|
||||||
|
autoExpandLimit: 0,
|
||||||
|
nodeOrder: NodeOrder.none,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
sidebar: {},
|
||||||
|
selectedTopic: undefined,
|
||||||
|
showUpdateDetails: false,
|
||||||
|
connected: false,
|
||||||
|
connecting: false,
|
||||||
|
error: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const reducer: Reducer<AppState | undefined, CustomAction> = (state = initialAppState, action) => {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw Error('No initial state')
|
throw Error('No initial state')
|
||||||
}
|
}
|
||||||
@@ -117,6 +144,40 @@ const reducer: Reducer<AppState | undefined, CustomAction> = (state, action) =>
|
|||||||
...state,
|
...state,
|
||||||
showUpdateDetails: action.showUpdateDetails,
|
showUpdateDetails: action.showUpdateDetails,
|
||||||
}
|
}
|
||||||
|
case ActionTypes.connecting:
|
||||||
|
if (!action.connectionId) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
connected: false,
|
||||||
|
connecting: true,
|
||||||
|
connectionId: action.connectionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
case ActionTypes.disconnect:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
connected: false,
|
||||||
|
connecting: false,
|
||||||
|
connectionId: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
case ActionTypes.connected:
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
connected: true,
|
||||||
|
connecting: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
case ActionTypes.showError:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: action.error,
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -4227,6 +4227,12 @@
|
|||||||
"strip-indent": "^1.0.1"
|
"strip-indent": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux-thunk": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"reflect-metadata": {
|
"reflect-metadata": {
|
||||||
"version": "0.1.12",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"mustache": "^3.0.1",
|
"mustache": "^3.0.1",
|
||||||
"nyc": "^13.1.0",
|
"nyc": "^13.1.0",
|
||||||
|
"redux-thunk": "^2.3.0",
|
||||||
"source-map-support": "^0.5.9",
|
"source-map-support": "^0.5.9",
|
||||||
"ts-node": "^7.0.1",
|
"ts-node": "^7.0.1",
|
||||||
"tslint": "^5.12.0",
|
"tslint": "^5.12.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user