Add electron

This commit is contained in:
Thomas Nordquist
2019-01-01 15:31:33 +01:00
parent 4e09ea3d30
commit b2badfd43f
37 changed files with 3900 additions and 2578 deletions

6
app/package-lock.json generated
View File

@@ -65,6 +65,12 @@
"indefinite-observable": "^1.0.1" "indefinite-observable": "^1.0.1"
} }
}, },
"@types/node": {
"version": "10.12.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==",
"dev": true
},
"@types/prop-types": { "@types/prop-types": {
"version": "15.5.8", "version": "15.5.8",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.8.tgz",

View File

@@ -4,11 +4,12 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "build": "webpack --mode production"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/node": "^10.12.18",
"awesome-typescript-loader": "^5.2.1", "awesome-typescript-loader": "^5.2.1",
"source-map-loader": "^0.2.4", "source-map-loader": "^0.2.4",
"typescript": "^3.2.2", "typescript": "^3.2.2",

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import * as q from '../../src/Model' import * as q from '../../backend/src/Model'
import { AppBar, Toolbar, Typography, InputBase } from '@material-ui/core'; import { AppBar, Toolbar, Typography, InputBase } from '@material-ui/core';

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import * as q from '../../../src/Model' import * as q from '../../../backend/src/Model'
import Drawer from '@material-ui/core/Drawer'; import Drawer from '@material-ui/core/Drawer';
import TextField from '@material-ui/core/TextField'; import TextField from '@material-ui/core/TextField';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as io from 'socket.io-client'; import * as io from 'socket.io-client';
import * as q from '../../../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';

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as io from 'socket.io-client'; import * as io from 'socket.io-client';
import * as q from '../../../src/Model' import * as q from '../../../backend/src/Model'
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import Collapse from '@material-ui/core/Collapse'; import Collapse from '@material-ui/core/Collapse';

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import * as q from '../../../src/Model' import * as q from '../../../backend/src/Model'
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
interface Props { interface Props {

2644
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

56
backend/package.json Normal file
View File

@@ -0,0 +1,56 @@
{
"name": "mqtt-explorer",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
"scripts": {
"test": "mocha",
"test-inspect": "mocha --inspect-brk",
"coverage": "nyc mocha",
"debug": "ts-node --inspect ./src/index.ts"
},
"author": "",
"license": "ISC",
"nyc": {
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"src/**/spec/*.spec.ts"
],
"extension": [
".ts",
".tsx"
],
"require": [
"ts-node/register"
],
"reporter": [
"text-summary",
"html"
],
"sourceMap": true,
"instrument": true
},
"dependencies": {
"@types/sha1": "^1.1.1",
"@types/socket.io": "^2.1.2",
"mqtt": "^2.18.8",
"sha1": "^1.1.1",
"socket.io": "^2.2.0",
"tslint": "^5.12.0",
"typescript": "^3.2.2"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"source-map-support": "^0.5.9",
"ts-node": "^7.0.1",
"tslint-strict-null-checks": "^1.0.1"
}
}

View File

@@ -0,0 +1,13 @@
import { DataSourceState } from './'
type MessageCallback = (topic: string, payload: Buffer) => void
// A DataSource should automatically reconnect if connection was broken
interface DataSource<DataSourceOptions> {
connect(options: DataSourceOptions): DataSourceState
disconnect(): void
onMessage(messageCallback: MessageCallback): void
topicSeparator: string
}
export { DataSource, MessageCallback }

View File

@@ -0,0 +1,41 @@
interface InternalState {
connecting: boolean
connected: boolean
error?: Error
}
export class DataSourceState {
private state: InternalState = {
error: undefined,
connected: false,
connecting: false
}
public setConnected(connected: boolean) {
this.state = {
error: undefined,
connected: connected,
connecting: false
}
}
public setError(error: Error) {
this.state = {
error: error,
connected: false,
connecting: false
}
}
public setConnecting() {
this.state = {
error: undefined,
connected: false,
connecting: true
}
}
public toJSON() {
return this.state
}
}

View File

@@ -0,0 +1,58 @@
import { Client, connect as mqttConnect } from 'mqtt'
import { DataSource, DataSourceState } from './'
export interface MqttOptions {
url: string
}
export class MqttSource implements DataSource<MqttOptions> {
private client: Client | undefined
private messageCallback?: (topic: string, message: Buffer) => void
private rootSubscription = '#'
public topicSeparator = '/'
public onMessage(messageCallback: (topic: string, message: Buffer) => void) {
this.messageCallback = messageCallback
}
public connect(options: MqttOptions): DataSourceState {
const state = new DataSourceState()
const client = mqttConnect(options.url, {
resubscribe: false
})
this.client = client
client.on('error', (error: Error) => {
state.setError(error)
})
client.on('close', () => {
state.setConnected(false)
})
client.on('reconnect', () => {
state.setConnecting()
})
client.on('connect', () => {
state.setConnected(true)
client.subscribe(this.rootSubscription, (err: Error) => {
if (err) {
throw new Error('mqtt subscription failed')
}
})
})
client.on('message', (topic, message) => {
this.messageCallback && this.messageCallback(topic, message)
})
return state
}
public disconnect() {
this.client && this.client.end()
}
}

View File

@@ -0,0 +1,10 @@
import { DataSource } from './DataSource'
import { DataSourceState } from './DataSourceState'
import { MqttOptions, MqttSource } from './MqttSource'
export {
DataSource,
DataSourceState,
MqttOptions,
MqttSource,
}

View File

@@ -1,7 +1,7 @@
import { Hashable, TreeNode, TopicProperties } from './' import { Hashable, TreeNode } from './'
const sha1 = require('sha1') const sha1 = require('sha1')
export class Edge { export class Edge implements Hashable {
public name: string public name: string
public node!: TreeNode public node!: TreeNode

View File

@@ -12,7 +12,7 @@ export class TreeNode extends EventEmitter {
if (sourceEdge) { if (sourceEdge) {
this.sourceEdge = sourceEdge this.sourceEdge = sourceEdge
sourceEdge.target = this sourceEdge.node = this
} }
this.value = value this.value = value
} }

37
backend/src/index.ts Normal file
View File

@@ -0,0 +1,37 @@
import { TopicProperties, Tree, TreeNodeFactory } from './Model'
import { MqttSource, DataSource } from './DataSource'
import * as socketIO from 'socket.io'
const server = require('http').createServer();
let tree = new Tree()
let options = {url: 'mqtt://nodered'}
let dataSource = new MqttSource()
let count = 200
const a: Array<any> = []
const io = socketIO(server)
io.on('connection', client => {
console.log('connection')
a.forEach(b => {
io.emit('message', b)
})
client.on('event', data => { /* … */ });
client.on('disconnect', () => { /* … */ });
});
server.listen(3000);
let state = dataSource.connect(options)
dataSource.onMessage((topic: string, payload: Buffer) => {
if (payload.length > 10000) {
payload = payload.slice(0, 10000)
}
io.emit('message', { topic, payload: payload.toString('base64') })
})
setTimeout(() => {
dataSource.disconnect()
}, 1000000)

50
electron.js Normal file
View File

@@ -0,0 +1,50 @@
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
// 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.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadFile('app/index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

3254
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,17 @@
{ {
"name": "mqtt-explorer", "name": "mqtt-explorer-electron",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "electron.js",
"scripts": { "scripts": {
"test": "mocha", "start": "electron .",
"test-inspect": "mocha --inspect-brk", "build-app": "cd app && npm run build",
"coverage": "nyc mocha", "build-backend": "cd backend && tsc",
"debug": "ts-node --inspect ./src/index.ts" "build": "npm run build-app && npm run build-backend"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"nyc": {
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"src/**/spec/*.spec.ts"
],
"extension": [
".ts",
".tsx"
],
"require": [
"ts-node/register"
],
"reporter": [
"text-summary",
"html"
],
"sourceMap": true,
"instrument": true
},
"dependencies": {
"@types/sha1": "^1.1.1",
"@types/socket.io": "^2.1.2",
"mqtt": "^2.18.8",
"sha1": "^1.1.1",
"socket.io": "^2.2.0",
"tslint": "^5.12.0",
"typescript": "^3.2.2"
},
"devDependencies": { "devDependencies": {
"@types/chai": "^4.1.7", "electron": "^4.0.0"
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"source-map-support": "^0.5.9",
"ts-node": "^7.0.1",
"tslint-strict-null-checks": "^1.0.1"
} }
} }

View File

@@ -1,10 +0,0 @@
type ReadyCallback = () => void
type MessageCallback = (topic: string, payload: Buffer) => void
interface DataSource {
connect({readyCallback, messageCallback}: { readyCallback: ReadyCallback, messageCallback: MessageCallback }): void
disconnect(): void
topicSeparator: string
}
export { DataSource, ReadyCallback, MessageCallback }

View File

@@ -1,42 +0,0 @@
import { Client, connect as mqttConnect } from 'mqtt'
import { DataSource } from './'
export class MqttSource implements DataSource {
private client: Client | undefined
private url: string
private subscription: string
public topicSeparator = '/'
constructor({url, subscription}: {url: string, subscription: string}) {
this.url = url
this.subscription = subscription
}
public connect({
readyCallback,
messageCallback
}: {
readyCallback: () => void,
messageCallback: (topic: string, message: Buffer) => void
}) {
const client = mqttConnect(this.url)
this.client = client
client.on('connect', () => {
readyCallback()
client.subscribe(this.subscription, (err: Error) => {
if (err) {
throw new Error('mqtt connection failed')
}
})
})
client.on('message', (topic, message) => {
messageCallback(topic, message)
})
}
public disconnect() {
this.client && this.client.end()
}
}

View File

@@ -1,7 +0,0 @@
import { DataSource } from './DataSource'
import { MqttSource } from './MqttSource'
export {
DataSource,
MqttSource,
}

View File

@@ -1,62 +0,0 @@
import { TopicProperties, Tree, TreeNodeFactory } from './Model'
import { MqttSource, DataSource } from './DataSource'
import { DotExport } from './DotExport'
// import { CytoscapeExport } from './CytoscapeExport'
// import { VisExport } from './VisExport'
import { writeFileSync } from 'fs'
import { spawn } from 'child_process'
import * as socketIO from 'socket.io'
const server = require('http').createServer();
let tree = new Tree()
let dataSource: DataSource = new MqttSource({url: 'mqtt://iot.eclipse.org', subscription: '#'})
let count = 200
const a: Array<any> = []
const io = socketIO(server)
io.on('connection', client => {
console.log('connection')
a.forEach(b => {
io.emit('message', b)
})
client.on('event', data => { /* … */ });
client.on('disconnect', () => { /* … */ });
});
server.listen(3000);
dataSource.connect({
readyCallback: () => {
console.log('connected')
},
messageCallback: (topic, payload) => {
// a.push({topic, payload})
if (payload.length > 10000) {
payload = payload.slice(0, 10000)
}
io.emit('message', {topic, payload: payload.toString('base64')})
// console.log(topic)
const edges = topic.split('/')
let value = payload.toString()
let node = TreeNodeFactory.fromEdgesAndValue(edges, value)
tree.updateWithNode(node.firstNode())
}
})
// function writeTree() {
// // writeFileSync('./test.dot', DotExport.toDot(tree))
// // writeFileSync('./test.json', CytoscapeExport.toDot(tree))
// // writeFileSync('./vis.json', VisExport.toDot(tree))
// // let p = spawn('dot', '-Tpng test.dot -o test2.png'.split(' '))
// }
//
// setInterval(() => {
// writeTree()
// }, 2000)
setTimeout(() => {
dataSource.disconnect()
}, 1000000)