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/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": {
|
"@types/socket.io-client": {
|
||||||
"version": "1.4.32",
|
"version": "1.4.32",
|
||||||
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.32.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
|
||||||
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
|
"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": {
|
"chokidar": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
||||||
@@ -1210,6 +1223,11 @@
|
|||||||
"which": "^1.2.9"
|
"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": {
|
"crypto-browserify": {
|
||||||
"version": "3.12.0",
|
"version": "3.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||||
@@ -4475,6 +4493,15 @@
|
|||||||
"safe-buffer": "^5.0.1"
|
"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": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@types/node": "^10.12.18",
|
"@types/node": "^10.12.18",
|
||||||
"@types/react": "^16.7.18",
|
"@types/react": "^16.7.18",
|
||||||
"@types/react-dom": "^16.0.11",
|
"@types/react-dom": "^16.0.11",
|
||||||
|
"@types/sha1": "^1.1.1",
|
||||||
"@types/socket.io-client": "^1.4.32",
|
"@types/socket.io-client": "^1.4.32",
|
||||||
"@types/vis": "^4.21.9",
|
"@types/vis": "^4.21.9",
|
||||||
"awesome-typescript-loader": "^5.2.1",
|
"awesome-typescript-loader": "^5.2.1",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
"react": "^16.3.2",
|
"react": "^16.3.2",
|
||||||
"react-dom": "^16.3.3",
|
"react-dom": "^16.3.3",
|
||||||
"react-json-view": "^1.19.1",
|
"react-json-view": "^1.19.1",
|
||||||
|
"sha1": "^1.1.1",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
"source-map-loader": "^0.2.4",
|
"source-map-loader": "^0.2.4",
|
||||||
"typescript": "^3.2.2",
|
"typescript": "^3.2.2",
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { Tree } from './components/Tree/Tree'
|
|||||||
import TitleBar from './components/TitleBar'
|
import TitleBar from './components/TitleBar'
|
||||||
import Sidebar from './components/Sidebar/Sidebar'
|
import Sidebar from './components/Sidebar/Sidebar'
|
||||||
import Connection from './components/ConnectionSetup/Connection'
|
import Connection from './components/ConnectionSetup/Connection'
|
||||||
|
// import { default as EventBus } from '../../events'
|
||||||
|
|
||||||
import { withTheme, Theme } from '@material-ui/core/styles'
|
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||||
|
|
||||||
class State {
|
interface State {
|
||||||
public selectedNode?: q.TreeNode | undefined
|
selectedNode?: q.TreeNode,
|
||||||
|
connectionId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -55,7 +57,7 @@ class App extends React.Component<Props, State> {
|
|||||||
<TitleBar />
|
<TitleBar />
|
||||||
<div>
|
<div>
|
||||||
<div style={this.getStyles().left}>
|
<div style={this.getStyles().left}>
|
||||||
<Tree didSelectNode={(node: q.TreeNode) => {
|
<Tree connectionId={this.state.connectionId} didSelectNode={(node: q.TreeNode) => {
|
||||||
this.setState({ selectedNode: node })
|
this.setState({ selectedNode: node })
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +65,7 @@ class App extends React.Component<Props, State> {
|
|||||||
<Sidebar node={this.state.selectedNode} />
|
<Sidebar node={this.state.selectedNode} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Connection />
|
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
|
||||||
</div >
|
</div >
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import { Typography, Toolbar, Modal, MenuItem, Button, Grid, Paper, TextField, Switch, FormControlLabel } from '@material-ui/core'
|
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 { 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 {
|
interface Props {
|
||||||
classes: {[s: string]: string}
|
classes: {[s: string]: string}
|
||||||
theme: Theme
|
theme: Theme
|
||||||
onAbort: () => void
|
onAbort: () => void,
|
||||||
|
onConnection: (connectionId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocols = [
|
const protocols = [
|
||||||
@@ -16,7 +19,6 @@ const protocols = [
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
name: string
|
|
||||||
host: string
|
host: string
|
||||||
protocol: string
|
protocol: string
|
||||||
port: number
|
port: number
|
||||||
@@ -27,13 +29,22 @@ interface State {
|
|||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare var window: any
|
||||||
|
|
||||||
class Connection extends React.Component<Props, State> {
|
class Connection extends React.Component<Props, State> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
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,
|
visible: true,
|
||||||
name: '',
|
host: 'nodered',
|
||||||
host: '',
|
|
||||||
protocol: protocols[0],
|
protocol: protocols[0],
|
||||||
port: 1883,
|
port: 1883,
|
||||||
ssl: false,
|
ssl: false,
|
||||||
@@ -42,6 +53,38 @@ class Connection extends React.Component<Props, State> {
|
|||||||
username: '',
|
username: '',
|
||||||
password: '',
|
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) => {
|
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">
|
<form className={classes.container} noValidate autoComplete="off">
|
||||||
|
|
||||||
<Grid container spacing={24}>
|
<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}>
|
<Grid item xs={2}>
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
@@ -184,19 +187,49 @@ class Connection extends React.Component<Props, State> {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</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>
|
</Grid>
|
||||||
<br />
|
<br />
|
||||||
<div style={{ textAlign: 'right' }}>
|
<div style={{ textAlign: 'right' }}>
|
||||||
<Button variant="contained" className={classes.button}>
|
<Button variant="contained" className={classes.button}>
|
||||||
Test Connection
|
Test Connection
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.setState({ visible: false })}>
|
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="primary" className={classes.button}>
|
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="contained" color="primary" className={classes.button} onClick={() => this.connect()}>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class ValueRenderer extends React.Component<Props, State> {
|
|||||||
return this.renderRawValue(node.message.value)
|
return this.renderRawValue(node.message.value)
|
||||||
} else if (typeof json === 'number') {
|
} else if (typeof json === 'number') {
|
||||||
return this.renderRawValue(node.message.value)
|
return this.renderRawValue(node.message.value)
|
||||||
|
} else if (typeof json === 'boolean') {
|
||||||
|
return this.renderRawValue(node.message.value)
|
||||||
} else {
|
} else {
|
||||||
const theme = this.props.theme.palette.type === 'dark' ? 'monokai' : 'bright:inverted'
|
const theme = this.props.theme.palette.type === 'dark' ? 'monokai' : 'bright:inverted'
|
||||||
return <ReactJson
|
return <ReactJson
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as io from 'socket.io-client'
|
|
||||||
import * as q from '../../../../backend/src/Model'
|
import * as q from '../../../../backend/src/Model'
|
||||||
import TreeNode from './TreeNode'
|
import TreeNode from './TreeNode'
|
||||||
import List from '@material-ui/core/List'
|
import List from '@material-ui/core/List'
|
||||||
|
import { makeConnectionMessageEvent, rendererEvents } from '../../../../events'
|
||||||
class TreeState {
|
import { } from '../../../../events/Events'
|
||||||
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
|
|
||||||
}
|
|
||||||
declare const performance: any
|
declare const performance: any
|
||||||
|
|
||||||
export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
interface Props{
|
||||||
private socket: SocketIOClient.Socket
|
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 renderDuration: number = 300
|
||||||
private updateTimer?: any
|
private updateTimer?: any
|
||||||
private lastUpdate: number = 0
|
private lastUpdate: number = 0
|
||||||
@@ -28,8 +25,7 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
|||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props)
|
super(props)
|
||||||
const tree = new q.Tree()
|
const tree = new q.Tree()
|
||||||
this.state = new TreeState(tree, {})
|
this.state = { tree, msg: {} }
|
||||||
this.socket = io('http://localhost:3000')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public time(): number {
|
public time(): number {
|
||||||
@@ -55,18 +51,37 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
|||||||
}, Math.max(0, timeUntilNextUpdate))
|
}, Math.max(0, timeUntilNextUpdate))
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentWillReceiveProps(nextProps: Props) {
|
||||||
this.socket.on('message', (msg: any) => {
|
if (this.props.connectionId) {
|
||||||
const edges = msg.topic.split('/')
|
const event = makeConnectionMessageEvent(this.props.connectionId)
|
||||||
const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString())
|
rendererEvents.unsubscribeAll(event)
|
||||||
this.state.tree.updateWithNode(node.firstNode())
|
}
|
||||||
|
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() {
|
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() {
|
public render() {
|
||||||
@@ -74,7 +89,7 @@ export class Tree extends React.Component<TreeNodeProps, TreeState> {
|
|||||||
<List>
|
<List>
|
||||||
<TreeNode
|
<TreeNode
|
||||||
animateChages={true}
|
animateChages={true}
|
||||||
autoExpandLimit={3}
|
autoExpandLimit={0}
|
||||||
isRoot={true}
|
isRoot={true}
|
||||||
didSelectNode={this.props.didSelectNode}
|
didSelectNode={this.props.didSelectNode}
|
||||||
treeNode={this.state.tree}
|
treeNode={this.state.tree}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
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 { withTheme, Theme } from '@material-ui/core/styles'
|
import { withTheme, Theme } from '@material-ui/core/styles'
|
||||||
const throttle = require('lodash.throttle')
|
|
||||||
import { isElementInViewport } from '../helper/isElementInViewport'
|
import { isElementInViewport } from '../helper/isElementInViewport'
|
||||||
import TreeNodeTitle from './TreeNodeTitle'
|
import TreeNodeTitle from './TreeNodeTitle'
|
||||||
import TreeNodeSubnodes from './TreeNodeSubnodes'
|
import TreeNodeSubnodes from './TreeNodeSubnodes'
|
||||||
|
|
||||||
declare var performance: any
|
declare var performance: any
|
||||||
declare var window: any
|
|
||||||
|
|
||||||
export interface TreeNodeProps {
|
export interface TreeNodeProps {
|
||||||
animateChages: boolean
|
animateChages: boolean
|
||||||
@@ -37,7 +35,7 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
|||||||
private cssAnimationWasSetAt?: number
|
private cssAnimationWasSetAt?: number
|
||||||
|
|
||||||
private willUpdateTime: number = performance.now()
|
private willUpdateTime: number = performance.now()
|
||||||
private titleRef = React.createRef<HTMLElement>()
|
private titleRef = React.createRef<HTMLDivElement>()
|
||||||
|
|
||||||
private subnodesDidchange = () => {
|
private subnodesDidchange = () => {
|
||||||
this.dirtySubnodes = true
|
this.dirtySubnodes = true
|
||||||
@@ -139,7 +137,7 @@ class TreeNode extends React.Component<TreeNodeProps, TreeNodeState> {
|
|||||||
this.dirtyState = this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
|
this.dirtyState = this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
|
||||||
|
|
||||||
return <div key={this.props.treeNode.hash()} style={ displayBlock }>
|
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
|
<TreeNodeTitle
|
||||||
edgeCount={this.state.edgeCount}
|
edgeCount={this.state.edgeCount}
|
||||||
collapsed={this.collapsed()}
|
collapsed={this.collapsed()}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
|
|||||||
const listItems = edges
|
const listItems = edges
|
||||||
.map(edge => edge.target)
|
.map(edge => edge.target)
|
||||||
.map(node => (
|
.map(node => (
|
||||||
<ListItem key={node.hash()} style={listItemStyle} button>
|
<ListItem
|
||||||
|
key={node.hash()}
|
||||||
|
style={listItemStyle}
|
||||||
|
button
|
||||||
|
>
|
||||||
<TreeNode
|
<TreeNode
|
||||||
animateChages={this.props.animateChanges}
|
animateChages={this.props.animateChanges}
|
||||||
treeNode={node}
|
treeNode={node}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"lib": ["es2017"],
|
"lib": ["es2017", "dom"],
|
||||||
"outDir": "./build/",
|
"outDir": "./build/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"./src/**/*"
|
"./src/**/*"
|
||||||
],
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.d.ts"
|
||||||
|
],
|
||||||
"awesomeTypescriptLoaderOptions": {
|
"awesomeTypescriptLoaderOptions": {
|
||||||
"useCache": true,
|
"useCache": true,
|
||||||
"transpileModule": true,
|
"transpileModule": true,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ module.exports = {
|
|||||||
splitChunks: false,
|
splitChunks: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
target: 'electron-renderer',
|
||||||
|
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
|
|
||||||
// Enable sourcemaps for debugging webpack's output.
|
// Enable sourcemaps for debugging webpack's output.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface DataSource<DataSourceOptions> {
|
|||||||
disconnect(): void
|
disconnect(): void
|
||||||
onMessage(messageCallback: MessageCallback): void
|
onMessage(messageCallback: MessageCallback): void
|
||||||
topicSeparator: string
|
topicSeparator: string
|
||||||
|
stateMachine: DataSourceStateMachine
|
||||||
}
|
}
|
||||||
|
|
||||||
export { DataSource, MessageCallback }
|
export { DataSource, MessageCallback }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EventEmitter } from 'events'
|
import { EventDispatcher } from '../../../events'
|
||||||
|
|
||||||
export interface DataSourceState {
|
export interface DataSourceState {
|
||||||
connecting: boolean
|
connecting: boolean
|
||||||
@@ -6,7 +6,8 @@ export interface DataSourceState {
|
|||||||
error?: Error
|
error?: Error
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataSourceStateMachine extends EventEmitter {
|
export class DataSourceStateMachine {
|
||||||
|
public onUpdate = new EventDispatcher<DataSourceState, DataSourceStateMachine>(this)
|
||||||
private state: DataSourceState = {
|
private state: DataSourceState = {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
connected: false,
|
connected: false,
|
||||||
@@ -19,6 +20,7 @@ export class DataSourceStateMachine extends EventEmitter {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
connecting: false,
|
connecting: false,
|
||||||
}
|
}
|
||||||
|
this.onUpdate.dispatch(this.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
public setError(error: Error) {
|
public setError(error: Error) {
|
||||||
@@ -27,6 +29,7 @@ export class DataSourceStateMachine extends EventEmitter {
|
|||||||
connected: false,
|
connected: false,
|
||||||
connecting: false,
|
connecting: false,
|
||||||
}
|
}
|
||||||
|
this.onUpdate.dispatch(this.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConnecting() {
|
public setConnecting() {
|
||||||
@@ -35,6 +38,7 @@ export class DataSourceStateMachine extends EventEmitter {
|
|||||||
connected: false,
|
connected: false,
|
||||||
connecting: true,
|
connecting: true,
|
||||||
}
|
}
|
||||||
|
this.onUpdate.dispatch(this.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
public toJSON() {
|
public toJSON() {
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import { DataSource, DataSourceStateMachine } from './'
|
|||||||
|
|
||||||
export interface MqttOptions {
|
export interface MqttOptions {
|
||||||
url: string
|
url: string
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
ssl: boolean
|
||||||
|
sslValidation: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MqttSource implements DataSource<MqttOptions> {
|
export class MqttSource implements DataSource<MqttOptions> {
|
||||||
|
public stateMachine: DataSourceStateMachine = new DataSourceStateMachine()
|
||||||
private client: Client | undefined
|
private client: Client | undefined
|
||||||
private messageCallback?: (topic: string, message: Buffer) => void
|
private messageCallback?: (topic: string, message: Buffer) => void
|
||||||
private rootSubscription = '#'
|
private rootSubscription = '#'
|
||||||
@@ -16,8 +21,7 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public connect(options: MqttOptions): DataSourceStateMachine {
|
public connect(options: MqttOptions): DataSourceStateMachine {
|
||||||
const state = new DataSourceStateMachine()
|
this.stateMachine.setConnecting()
|
||||||
|
|
||||||
const client = mqttConnect(options.url, {
|
const client = mqttConnect(options.url, {
|
||||||
resubscribe: false,
|
resubscribe: false,
|
||||||
})
|
})
|
||||||
@@ -25,19 +29,19 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
this.client = client
|
this.client = client
|
||||||
|
|
||||||
client.on('error', (error: Error) => {
|
client.on('error', (error: Error) => {
|
||||||
state.setError(error)
|
this.stateMachine.setError(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('close', () => {
|
client.on('close', () => {
|
||||||
state.setConnected(false)
|
this.stateMachine.setConnected(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('reconnect', () => {
|
client.on('reconnect', () => {
|
||||||
state.setConnecting()
|
this.stateMachine.setConnecting()
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
state.setConnected(true)
|
this.stateMachine.setConnected(true)
|
||||||
client.subscribe(this.rootSubscription, (err: Error) => {
|
client.subscribe(this.rootSubscription, (err: Error) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw new Error('mqtt subscription failed')
|
throw new Error('mqtt subscription failed')
|
||||||
@@ -49,7 +53,7 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
this.messageCallback && this.messageCallback(topic, message)
|
this.messageCallback && this.messageCallback(topic, message)
|
||||||
})
|
})
|
||||||
|
|
||||||
return state
|
return this.stateMachine
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnect() {
|
public disconnect() {
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export class TopicProperties {
|
|
||||||
public topicSeparator: string = '/'
|
|
||||||
public multilevelWildcard: string | null = '#'
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,4 @@ export { TreeNode, TreeNodeUpdateEvents } from './TreeNode'
|
|||||||
export { Message } from './Message'
|
export { Message } from './Message'
|
||||||
export { TreeNodeFactory } from './TreeNodeFactory'
|
export { TreeNodeFactory } from './TreeNodeFactory'
|
||||||
export { Tree } from './Tree'
|
export { Tree } from './Tree'
|
||||||
export { TopicProperties } from './TopicProperties'
|
|
||||||
export { Hashable } from './Hashable'
|
export { Hashable } from './Hashable'
|
||||||
|
|||||||
@@ -1,34 +1,46 @@
|
|||||||
import * as socketIO from 'socket.io'
|
import { addMqttConnectionEvent, backendEvents, makeConnectionStateEvent, makeConnectionMessageEvent, AddMqttConnection } from '../../events'
|
||||||
const http = require('http')
|
|
||||||
|
|
||||||
import { TopicProperties, Tree, TreeNodeFactory } from './Model'
|
|
||||||
import { MqttSource, DataSource } from './DataSource'
|
import { MqttSource, DataSource } from './DataSource'
|
||||||
|
|
||||||
const options = { url: 'mqtt://nodered' }
|
class ConnectionManager {
|
||||||
const dataSource = new MqttSource()
|
private connections: {[s: string]: DataSource<any>} = {}
|
||||||
|
|
||||||
const a: any[] = []
|
public manageConnections() {
|
||||||
|
backendEvents.subscribe(addMqttConnectionEvent, this.handleConnectionRequest)
|
||||||
const server = http.createServer()
|
|
||||||
const io = socketIO(server)
|
|
||||||
io.on('connection', (client) => {
|
|
||||||
console.log('connection')
|
|
||||||
a.forEach((b) => {
|
|
||||||
io.emit('message', b)
|
|
||||||
})
|
|
||||||
client.on('disconnect', () => { /* … */ })
|
|
||||||
})
|
|
||||||
server.listen(3000)
|
|
||||||
|
|
||||||
const state = dataSource.connect(options)
|
|
||||||
dataSource.onMessage((topic: string, payload: Buffer) => {
|
|
||||||
let buffer = payload
|
|
||||||
if (a.length < 30) {
|
|
||||||
a.push({ topic, payload: buffer.toString('base64') })
|
|
||||||
}
|
|
||||||
if (buffer.length > 10000) {
|
|
||||||
buffer = buffer.slice(0, 10000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
io.emit('message', { topic, payload: buffer.toString('base64') })
|
private handleConnectionRequest = (event: AddMqttConnection) => {
|
||||||
})
|
console.log(event)
|
||||||
|
const connectionId = event.id
|
||||||
|
const options = event.options
|
||||||
|
const connection = new MqttSource()
|
||||||
|
this.connections[connectionId] = connection
|
||||||
|
|
||||||
|
connection.stateMachine.onUpdate.subscribe((state) => {
|
||||||
|
backendEvents.emit(makeConnectionStateEvent(connectionId), state)
|
||||||
|
})
|
||||||
|
|
||||||
|
connection.connect(options)
|
||||||
|
this.handleNewMessagesForConnection(connectionId, connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNewMessagesForConnection(connectionId: string, connection: MqttSource) {
|
||||||
|
const messageEvent = makeConnectionMessageEvent(connectionId)
|
||||||
|
connection.onMessage((topic: string, payload: Buffer) => {
|
||||||
|
let buffer = payload
|
||||||
|
if (buffer.length > 10000) {
|
||||||
|
buffer = buffer.slice(0, 10000)
|
||||||
|
}
|
||||||
|
backendEvents.emit(messageEvent, { topic, payload: buffer.toString('base64') })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeConnection(hash: string) {
|
||||||
|
const connection = this.connections[hash]
|
||||||
|
connection.stateMachine
|
||||||
|
connection.disconnect()
|
||||||
|
delete this.connections[hash]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionManager = new ConnectionManager()
|
||||||
|
connectionManager.manageConnections()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"lib": ["es2017"],
|
"lib": ["es2017", "dom"],
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"includes": [
|
"includes": [
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"app",
|
"app",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"src/**/*.spec.ts"
|
"src/**/*.spec.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
|
"typings"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Modules to control application life and create native browser window
|
// Modules to control application life and create native browser window
|
||||||
const {app, BrowserWindow} = require('electron')
|
const {app, BrowserWindow} = require('electron')
|
||||||
require('mqtt-explorer-backend')
|
require('./backend/build/backend/src/index.js')
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
// be closed automatically when the JavaScript object is garbage collected.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let mainWindow
|
let mainWindow
|
||||||
@@ -27,6 +27,7 @@ function createWindow () {
|
|||||||
// in an array if your app supports multi windows, this is the time
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// when you should delete the corresponding element.
|
||||||
mainWindow = null
|
mainWindow = null
|
||||||
|
app.quit()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
55
events/EventBus.ts
Normal file
55
events/EventBus.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Event } from './Events'
|
||||||
|
import { ipcMain, ipcRenderer, IpcRenderer, IpcMain } from 'electron'
|
||||||
|
|
||||||
|
interface EventBusInterface {
|
||||||
|
subscribe<MessageType>(event: Event<MessageType>, callback:(msg: MessageType) => void): void
|
||||||
|
unsubscribeAll<MessageType>(event: Event<MessageType>): void
|
||||||
|
emit<MessageType>(event: Event<MessageType>, msg: MessageType): void
|
||||||
|
}
|
||||||
|
|
||||||
|
class IpcMainEventBus implements EventBusInterface {
|
||||||
|
private ipc: IpcMain
|
||||||
|
private client: any
|
||||||
|
constructor(ipc: IpcMain) {
|
||||||
|
this.ipc = ipc
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe<MessageType>(event: Event<MessageType>, callback:(msg: MessageType) => void) {
|
||||||
|
console.log('subscribing', event.topic)
|
||||||
|
this.ipc.on(event.topic, (event: any, arg: any) => {
|
||||||
|
this.client = event.sender
|
||||||
|
callback(arg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
|
||||||
|
this.ipc.removeAllListeners(event.topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
|
||||||
|
this.client.send(event.topic, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IpcRendererEventBus implements EventBusInterface {
|
||||||
|
private ipc: IpcRenderer
|
||||||
|
constructor(ipc: IpcRenderer) {
|
||||||
|
this.ipc = ipc
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe<MessageType>(event: Event<MessageType>, callback:(msg: MessageType) => void) {
|
||||||
|
this.ipc.on(event.topic, (_event: any, arg: any) => callback(arg))
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
|
||||||
|
this.ipc.removeAllListeners(event.topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
|
||||||
|
console.log(event.topic, msg)
|
||||||
|
this.ipc.send(event.topic, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
|
||||||
|
export const backendEvents = new IpcMainEventBus(ipcMain)
|
||||||
47
events/EventDispatcher.ts
Normal file
47
events/EventDispatcher.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
interface CallbackStore {
|
||||||
|
wrappedCallback: any
|
||||||
|
callback: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventDispatcher<Message, Dispatcher> {
|
||||||
|
private emitter = new EventEmitter()
|
||||||
|
private dispatcher: Dispatcher
|
||||||
|
private callbacks: CallbackStore[] = []
|
||||||
|
|
||||||
|
constructor(dispatcher: Dispatcher) {
|
||||||
|
this.dispatcher = dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispatch(msg: Message) {
|
||||||
|
this.emitter.emit('event', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe(callback: (msg: Message, dispatcher: Dispatcher) => void) {
|
||||||
|
const wrappedCallback = (msg: Message) => {
|
||||||
|
callback(msg, this.dispatcher)
|
||||||
|
}
|
||||||
|
this.emitter.on('event', wrappedCallback)
|
||||||
|
|
||||||
|
this.callbacks.push({
|
||||||
|
callback,
|
||||||
|
wrappedCallback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsubscribe(callback: (msg: Message, dispatcher: Dispatcher) => void) {
|
||||||
|
const item = this.callbacks.find(store => store.callback === callback)
|
||||||
|
if (!item) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emitter.removeListener('event', item.wrappedCallback)
|
||||||
|
this.callbacks = this.callbacks.filter(a => a !== item)
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeAllListeners() {
|
||||||
|
this.emitter.removeAllListeners()
|
||||||
|
this.callbacks = []
|
||||||
|
}
|
||||||
|
}
|
||||||
39
events/Events.ts
Normal file
39
events/Events.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { MqttOptions, DataSourceState } from '../backend/src/DataSource'
|
||||||
|
|
||||||
|
export interface Event<MessageType> {
|
||||||
|
topic: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddMqttConnection {
|
||||||
|
id: string,
|
||||||
|
options: MqttOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addMqttConnectionEvent: Event<AddMqttConnection> = {
|
||||||
|
topic: 'connection/add/mqtt',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoveConnection {
|
||||||
|
connectionId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeConnection: Event<string> = {
|
||||||
|
topic: 'connection/remove',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeConnectionStateEvent(connectionId: string): Event<DataSourceState> {
|
||||||
|
return {
|
||||||
|
topic: `conn/state/${connectionId}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
topic: string,
|
||||||
|
payload: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeConnectionMessageEvent(connectionId: string): Event<Message> {
|
||||||
|
return {
|
||||||
|
topic: `conn/${connectionId}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
0
events/Server/index.ts
Normal file
0
events/Server/index.ts
Normal file
3
events/index.ts
Normal file
3
events/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './Events'
|
||||||
|
export * from './EventDispatcher'
|
||||||
|
export * from './EventBus'
|
||||||
9
events/test.js
Normal file
9
events/test.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const { EventEmitter } = require('events');
|
||||||
|
|
||||||
|
const a = new EventEmitter()
|
||||||
|
a.on('test', () => console.log('test'))
|
||||||
|
a.on('test2', () => console.log('test2'))
|
||||||
|
a.removeAllListeners('test')
|
||||||
|
|
||||||
|
a.emit('test')
|
||||||
|
a.emit('test2')
|
||||||
631
package-lock.json
generated
631
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"test": "npm run test-backend",
|
"test": "npm run test-backend",
|
||||||
|
"build": "cd app; npm run build; cd ..; cd backend; npm run build; cd ..",
|
||||||
"test-backend": "cd backend && npm run test && cd ..",
|
"test-backend": "cd backend && npm run test && cd ..",
|
||||||
"release": "npm run test && ./release.sh"
|
"release": "npm run test && ./release.sh"
|
||||||
},
|
},
|
||||||
@@ -38,6 +39,9 @@
|
|||||||
"typescript": "^3.2.2"
|
"typescript": "^3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mqtt-explorer-backend": "file:backend"
|
"@types/electron": "^1.6.10",
|
||||||
|
"@types/socket.io": "^2.1.2",
|
||||||
|
"mqtt-explorer-backend": "file:backend",
|
||||||
|
"socket.io": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user