Clean up & Add connection setup
This commit is contained in:
27
app/package-lock.json
generated
27
app/package-lock.json
generated
@@ -141,6 +141,14 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/sha1": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sha1/-/sha1-1.1.1.tgz",
|
||||
"integrity": "sha512-Yrz4TPsm/xaw7c39aTISskNirnRJj2W9OVeHv8ooOR9SG8NHEfh4lwvGeN9euzxDyPfBdFkvL/VHIY3kM45OpQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/socket.io-client": {
|
||||
"version": "1.4.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.32.tgz",
|
||||
@@ -924,6 +932,11 @@
|
||||
"resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
|
||||
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
|
||||
},
|
||||
"charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
||||
@@ -1210,6 +1223,11 @@
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
@@ -4475,6 +4493,15 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"sha1": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
|
||||
"integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=",
|
||||
"requires": {
|
||||
"charenc": ">= 0.0.1",
|
||||
"crypt": ">= 0.0.1"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@types/node": "^10.12.18",
|
||||
"@types/react": "^16.7.18",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"@types/sha1": "^1.1.1",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"@types/vis": "^4.21.9",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
@@ -26,6 +27,7 @@
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.3",
|
||||
"react-json-view": "^1.19.1",
|
||||
"sha1": "^1.1.1",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"typescript": "^3.2.2",
|
||||
|
||||
@@ -5,11 +5,13 @@ import { Tree } from './components/Tree/Tree'
|
||||
import TitleBar from './components/TitleBar'
|
||||
import Sidebar from './components/Sidebar/Sidebar'
|
||||
import Connection from './components/ConnectionSetup/Connection'
|
||||
// import { default as EventBus } from '../../events'
|
||||
|
||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||
|
||||
class State {
|
||||
public selectedNode?: q.TreeNode | undefined
|
||||
interface State {
|
||||
selectedNode?: q.TreeNode,
|
||||
connectionId?: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -55,7 +57,7 @@ class App extends React.Component<Props, State> {
|
||||
<TitleBar />
|
||||
<div>
|
||||
<div style={this.getStyles().left}>
|
||||
<Tree didSelectNode={(node: q.TreeNode) => {
|
||||
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
|
||||
this.setState({ selectedNode: node })
|
||||
}} />
|
||||
</div>
|
||||
@@ -63,7 +65,7 @@ class App extends React.Component<Props, State> {
|
||||
<Sidebar node={this.state.selectedNode} />
|
||||
</div>
|
||||
</div>
|
||||
<Connection />
|
||||
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
|
||||
</div >
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { Typography, Toolbar, Modal, MenuItem, Button, Grid, Paper, TextField, Switch, FormControlLabel } from '@material-ui/core'
|
||||
import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles'
|
||||
import { MqttOptions, DataSourceState } from '../../../../backend/src/DataSource'
|
||||
import { addMqttConnectionEvent, makeConnectionStateEvent, rendererEvents } from '../../../../events'
|
||||
import sha1 = require('sha1')
|
||||
|
||||
interface Props {
|
||||
classes: {[s: string]: string}
|
||||
theme: Theme
|
||||
onAbort: () => void
|
||||
onAbort: () => void,
|
||||
onConnection: (connectionId: string) => void
|
||||
}
|
||||
|
||||
const protocols = [
|
||||
@@ -16,7 +19,6 @@ const protocols = [
|
||||
|
||||
interface State {
|
||||
visible: boolean
|
||||
name: string
|
||||
host: string
|
||||
protocol: string
|
||||
port: number
|
||||
@@ -27,13 +29,22 @@ interface State {
|
||||
password: string
|
||||
}
|
||||
|
||||
declare var window: any
|
||||
|
||||
class Connection extends React.Component<Props, State> {
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
this.state = {
|
||||
const storedSettingsString = window.localStorage.getItem('connectionSettings')
|
||||
let storedSettings
|
||||
try {
|
||||
storedSettings = storedSettingsString ? JSON.parse(storedSettingsString) : undefined
|
||||
} catch {
|
||||
window.localStorage.setItem('connectionSettings', undefined)
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
visible: true,
|
||||
name: '',
|
||||
host: '',
|
||||
host: 'nodered',
|
||||
protocol: protocols[0],
|
||||
port: 1883,
|
||||
ssl: false,
|
||||
@@ -42,6 +53,38 @@ class Connection extends React.Component<Props, State> {
|
||||
username: '',
|
||||
password: '',
|
||||
}
|
||||
|
||||
this.state = Object.assign({}, defaultState, storedSettings)
|
||||
}
|
||||
|
||||
private saveConnectionSettings() {
|
||||
window.localStorage.setItem('connectionSettings', JSON.stringify(this.state))
|
||||
}
|
||||
|
||||
private optionsFromState(): MqttOptions {
|
||||
const protocol = this.state.protocol === 'tcp://' ? 'mqtt://' : this.state.protocol
|
||||
const url = `${protocol}${this.state.host}:${this.state.port}`
|
||||
|
||||
return {
|
||||
url,
|
||||
username: this.state.username || undefined,
|
||||
password: this.state.username || undefined,
|
||||
ssl: this.state.ssl,
|
||||
sslValidation: this.state.sslValidation,
|
||||
}
|
||||
}
|
||||
|
||||
private connect() {
|
||||
const options = this.optionsFromState()
|
||||
const connectionId = (sha1(Math.random() + JSON.stringify(options)).slice(0, 8)) as string
|
||||
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
||||
rendererEvents.subscribe(makeConnectionStateEvent(connectionId), (state: DataSourceState) => {
|
||||
console.log(state)
|
||||
if (state.connected) {
|
||||
this.props.onConnection(connectionId)
|
||||
this.setState({ visible: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public static styles: StyleRulesCallback<string> = (theme: Theme) => {
|
||||
@@ -91,46 +134,6 @@ class Connection extends React.Component<Props, State> {
|
||||
<form className={classes.container} noValidate autoComplete="off">
|
||||
|
||||
<Grid container spacing={24}>
|
||||
<Grid item xs={5}>
|
||||
<TextField
|
||||
label="Name"
|
||||
className={classes.textField}
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange('name')}
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={1}></Grid>
|
||||
<Grid item xs={3}>
|
||||
<div className={classes.switch}>
|
||||
<FormControlLabel
|
||||
control={(
|
||||
<Switch
|
||||
checked={this.state.sslValidation}
|
||||
onChange={() => this.setState({ sslValidation: !this.state.sslValidation })}
|
||||
color="primary"
|
||||
/>
|
||||
)}
|
||||
label="Validate certificate"
|
||||
labelPlacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<div className={classes.switch}>
|
||||
<FormControlLabel
|
||||
control={(
|
||||
<Switch
|
||||
checked={this.state.ssl}
|
||||
onChange={() => this.setState({ ssl: !this.state.ssl })}
|
||||
color="primary"
|
||||
/>
|
||||
)}
|
||||
label="Encryption"
|
||||
labelPlacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<TextField
|
||||
select
|
||||
@@ -184,19 +187,49 @@ class Connection extends React.Component<Props, State> {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}></Grid>
|
||||
<Grid item xs={3}>
|
||||
<div className={classes.switch}>
|
||||
<FormControlLabel
|
||||
control={(
|
||||
<Switch
|
||||
checked={this.state.sslValidation}
|
||||
onChange={() => this.setState({ sslValidation: !this.state.sslValidation })}
|
||||
color="primary"
|
||||
/>
|
||||
)}
|
||||
label="Validate certificate"
|
||||
labelPlacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<div className={classes.switch}>
|
||||
<FormControlLabel
|
||||
control={(
|
||||
<Switch
|
||||
checked={this.state.ssl}
|
||||
onChange={() => this.setState({ ssl: !this.state.ssl })}
|
||||
color="primary"
|
||||
/>
|
||||
)}
|
||||
label="Encryption"
|
||||
labelPlacement="bottom"
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={6}></Grid>
|
||||
</Grid>
|
||||
<br />
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<Button variant="contained" className={classes.button}>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.setState({ visible: false })}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" className={classes.button}>
|
||||
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" className={classes.button} onClick={() => this.connect()}>
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Paper>
|
||||
|
||||
@@ -53,6 +53,8 @@ class ValueRenderer extends React.Component<Props, State> {
|
||||
return this.renderRawValue(node.message.value)
|
||||
} else if (typeof json === 'number') {
|
||||
return this.renderRawValue(node.message.value)
|
||||
} else if (typeof json === 'boolean') {
|
||||
return this.renderRawValue(node.message.value)
|
||||
} else {
|
||||
const theme = this.props.theme.palette.type === 'dark' ? 'monokai' : 'bright:inverted'
|
||||
return <ReactJson
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import * as React from 'react'
|
||||
import * as io from 'socket.io-client'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import TreeNode from './TreeNode'
|
||||
import List from '@material-ui/core/List'
|
||||
|
||||
class TreeState {
|
||||
public tree: q.Tree
|
||||
public msg: any
|
||||
constructor(tree: q.Tree, msg: any) {
|
||||
this.tree = tree
|
||||
this.msg = msg
|
||||
}
|
||||
}
|
||||
|
||||
export interface TreeNodeProps {
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
}
|
||||
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
|
||||
import { } from '../../../../events/Events'
|
||||
declare const performance: any
|
||||
|
||||
export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
private socket: SocketIOClient.Socket
|
||||
interface Props{
|
||||
didSelectNode?: (node: q.TreeNode) => void
|
||||
connectionId?: string
|
||||
}
|
||||
|
||||
interface TreeState {
|
||||
tree: q.Tree
|
||||
msg: any
|
||||
}
|
||||
|
||||
export class Tree extends React.Component<Props, TreeState> {
|
||||
private renderDuration: number = 300
|
||||
private updateTimer?: any
|
||||
private lastUpdate: number = 0
|
||||
@@ -28,8 +25,7 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
const tree = new q.Tree()
|
||||
this.state = new TreeState(tree, {})
|
||||
this.socket = io('http://localhost:3000')
|
||||
this.state = { tree, msg: {} }
|
||||
}
|
||||
|
||||
public time(): number {
|
||||
@@ -55,18 +51,37 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
}, Math.max(0, timeUntilNextUpdate))
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.socket.on('message', (msg: any) => {
|
||||
const edges = msg.topic.split('/')
|
||||
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
||||
this.state.tree.updateWithNode(node.firstNode())
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.connectionId) {
|
||||
const event = makeConnectionMessageEvent(this.props.connectionId)
|
||||
rendererEvents.unsubscribeAll(event)
|
||||
}
|
||||
if (nextProps.connectionId) {
|
||||
const event = makeConnectionMessageEvent(nextProps.connectionId)
|
||||
rendererEvents.subscribe(event, this.handleNewData)
|
||||
}
|
||||
}
|
||||
|
||||
this.throttledStateUpdate({ msg, tree: this.state.tree })
|
||||
})
|
||||
public componentDidMount() {
|
||||
if (this.props.connectionId) {
|
||||
const event = makeConnectionMessageEvent(this.props.connectionId)
|
||||
rendererEvents.subscribe(event, this.handleNewData)
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.socket.removeAllListeners()
|
||||
if (this.props.connectionId) {
|
||||
const event = makeConnectionMessageEvent(this.props.connectionId)
|
||||
rendererEvents.unsubscribeAll(event)
|
||||
}
|
||||
}
|
||||
|
||||
private handleNewData = (msg: any) => {
|
||||
const edges = msg.topic.split('/')
|
||||
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
||||
this.state.tree.updateWithNode(node.firstNode())
|
||||
|
||||
this.throttledStateUpdate({ msg, tree: this.state.tree })
|
||||
}
|
||||
|
||||
public render() {
|
||||
@@ -74,7 +89,7 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
||||
<List>
|
||||
<TreeNode
|
||||
animateChages={true}
|
||||
autoExpandLimit={3}
|
||||
autoExpandLimit={0}
|
||||
isRoot={true}
|
||||
didSelectNode={this.props.didSelectNode}
|
||||
treeNode={this.state.tree}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import * as React from 'react'
|
||||
import * as q from '../../../../backend/src/Model'
|
||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||
const throttle = require('lodash.throttle')
|
||||
import { isElementInViewport } from '../helper/isElementInViewport'
|
||||
import TreeNodeTitle from './TreeNodeTitle'
|
||||
import TreeNodeSubnodes from './TreeNodeSubnodes'
|
||||
|
||||
declare var performance: any
|
||||
declare var window: any
|
||||
|
||||
export interface TreeNodeProps {
|
||||
animateChages: boolean
|
||||
@@ -37,7 +35,7 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
private cssAnimationWasSetAt?: number
|
||||
|
||||
private willUpdateTime: number = performance.now()
|
||||
private titleRef = React.createRef<HTMLElement>()
|
||||
private titleRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
private subnodesDidchange = () => {
|
||||
this.dirtySubnodes = true
|
||||
@@ -139,7 +137,7 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
||||
this.dirtyState = this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
|
||||
|
||||
return <div key={this.props.treeNode.hash()} style={ displayBlock }>
|
||||
<div style={animationStyle} ref={this.titleRef}>
|
||||
<div style={animationStyle} ref={this.titleRef} onClick={() => this.toggle()}>
|
||||
<TreeNodeTitle
|
||||
edgeCount={this.state.edgeCount}
|
||||
collapsed={this.collapsed()}
|
||||
|
||||
@@ -29,7 +29,11 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
|
||||
const listItems = edges
|
||||
.map(edge => edge.target)
|
||||
.map(node => (
|
||||
<ListItem key={node.hash()} style={listItemStyle} button>
|
||||
<ListItem
|
||||
key={node.hash()}
|
||||
style={listItemStyle}
|
||||
button
|
||||
>
|
||||
<TreeNode
|
||||
animateChages={this.props.animateChanges}
|
||||
treeNode={node}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"lib": ["es2017"],
|
||||
"lib": ["es2017", "dom"],
|
||||
"outDir": "./build/",
|
||||
"sourceMap": true,
|
||||
"module": "commonjs",
|
||||
@@ -14,6 +14,9 @@
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.d.ts"
|
||||
],
|
||||
"awesomeTypescriptLoaderOptions": {
|
||||
"useCache": true,
|
||||
"transpileModule": true,
|
||||
|
||||
@@ -12,6 +12,8 @@ module.exports = {
|
||||
splitChunks: false,
|
||||
},
|
||||
|
||||
target: 'electron-renderer',
|
||||
|
||||
mode: 'production',
|
||||
|
||||
// Enable sourcemaps for debugging webpack's output.
|
||||
|
||||
Reference in New Issue
Block a user