Compare commits

..
19 Commits
Author SHA1 Message Date
Thomas Nordquist 3ba31de468 Add last message time to sidebar 2019-01-16 00:53:18 +01:00
Thomas Nordquist ab6938c7da Fix debugging 2019-01-16 00:33:03 +01:00
Thomas Nordquist 1eeaee0fe0 Fix update url 2019-01-16 00:29:55 +01:00
Thomas Nordquist 0e7ec90c65 Add electron-debug 2019-01-16 00:08:46 +01:00
Thomas Nordquist 5eb6f41189 Add debug option --debug 2019-01-15 23:54:03 +01:00
Thomas Nordquist 830dd31f06 Update readme 2019-01-15 22:26:16 +01:00
Thomas Nordquist 0de3ce2c41 Update build targets 2019-01-15 22:01:54 +01:00
Thomas Nordquist 9c5140c419 Lazy load js 2019-01-15 22:00:47 +01:00
Thomas Nordquist 61b64a5ac2 Fix authentication 2019-01-15 22:00:35 +01:00
Thomas Nordquist f905ba8e89 Clarify mouse interaction 2019-01-15 19:00:37 +01:00
Thomas Nordquist c6a0a15d95 Print update info 2019-01-15 15:10:29 +01:00
Thomas Nordquist 75230ccb40 Add snap package target 2019-01-15 13:55:06 +01:00
Thomas Nordquist 97a1cf72c5 Fix linux icon 2019-01-15 12:51:21 +01:00
Thomas Nordquist 71c06b3f74 Try fix linux app icon 2019-01-14 18:20:29 +01:00
Thomas Nordquist b0fa4a9f7a Update readme 2019-01-14 17:38:00 +01:00
Thomas Nordquist d8c8799406 Don't send events after window is gone 2019-01-14 17:12:46 +01:00
Thomas Nordquist 7c72344174 Update readme 2019-01-14 17:00:14 +01:00
Thomas Nordquist 7d42ed12b1 Add readme template 2019-01-14 16:48:01 +01:00
Thomas Nordquist 0df5ff13c1 Update publish settings 2019-01-14 15:28:24 +01:00
22 changed files with 543 additions and 166 deletions
+6 -2
View File
@@ -1,11 +1,15 @@
# MQTT-Explorer
[![Build Status](https://travis-ci.org/thomasnordquist/MQTT-Explorer.svg?branch=master)](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
### Version 0.0.6
See the whole picture of your message queue.
The perfect tool to integrate new services, IoT devices in your network.
This application subscribes to all topics on your MQTT-Server and displays your message queue hierarchy, allowing you to drill-down to the topics that are of interest.
## Download
The app is prebuilt for Windows, Linux and Mac. [Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
The app is prebuilt for Windows ([portable](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.0.6/MQTT-Explorer-0.0.6.exe), [installer](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.0.6/MQTT-Explorer-Setup-0.0.6.exe)), Linux ([AppImage](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.0.6/MQTT-Explorer-0.0.6-x86_64.AppImage)) and Mac ([dmg](https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v0.0.6/MQTT-Explorer-0.0.6.dmg)).
More architectures and package types: [Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
![screen2](https://user-images.githubusercontent.com/7721625/51109225-dbe22200-17f4-11e9-8f6b-c6a27c07c90e.png)
@@ -19,4 +23,4 @@ Start with `npm run start`
The `app` directory contains all the rendering logic, the `backend` directory currently contains the models, tests, connection management.
## License
Not yet decided which licens exactly, but the basic idea is: "You may do whatever you want with this tool, except sell it."
Not yet decided which license exactly, but the basic idea is: "You may do whatever you want with this tool, except sell it."
+26
View File
@@ -0,0 +1,26 @@
# MQTT-Explorer
[![Build Status](https://travis-ci.org/thomasnordquist/MQTT-Explorer.svg?branch=master)](https://travis-ci.org/thomasnordquist/MQTT-Explorer)
### Version {{ version }}
See the whole picture of your message queue.
The perfect tool to integrate new services, IoT devices in your network.
This application subscribes to all topics on your MQTT-Server and displays your message queue hierarchy, allowing you to drill-down to the topics that are of interest.
## Download
The app is prebuilt for Windows ({{windowsTargets}}), Linux ({{linuxTargets}}) and Mac ({{macTargets}}).
More architectures and package types: [Downloads](https://github.com/thomasnordquist/MQTT-Explorer/releases)
![screen2](https://user-images.githubusercontent.com/7721625/51109225-dbe22200-17f4-11e9-8f6b-c6a27c07c90e.png)
## Develop
PRs and issues are welcome
Install with `npm run install`, build with `npm run build`
Start with `npm run start`
The `app` directory contains all the rendering logic, the `backend` directory currently contains the models, tests, connection management.
## License
Not yet decided which license exactly, but the basic idea is: "You may do whatever you want with this tool, except sell it."
+10 -9
View File
@@ -59,17 +59,18 @@
}
</style>
<script src="http://localhost:35729/livereload.js"></script>
</head>
<body>
<div id="splash"><div id="splash1"></div>
<!-- <div id="splash1">
<div id="splash2" style="color: #ddd" class="la-square-jelly-box la-3x">
<div>a</div>
<div>ads</div>
</div> -->
</div>
</div>
<div id="splash"><div id="splash1"></div></div>
<div id="app" style="font:-webkit-control"></div>
<script src="./build/bundle.js"></script>
<script>
function onLoad() {
var script = document.createElement("script");
script.src = "./build/bundle.js"
document.head.appendChild(script);
}
document.addEventListener('DOMContentLoaded', onLoad(), false);
</script>
</body>
</html>
+8 -1
View File
@@ -42,6 +42,13 @@ class UpdateNotifier extends React.Component<Props, {}> {
rendererEvents.subscribe(updateAvailable, this.handleUpdate)
}
private fixUrl(url: string, version: string) {
if (!/^http/.test(url)) {
return `https://github.com/thomasnordquist/MQTT-Explorer/releases/download/v${version}/${url}`
}
return url
}
public componentWillUnmount() {
rendererEvents.unsubscribeAll(updateAvailable)
}
@@ -156,7 +163,7 @@ class UpdateNotifier extends React.Component<Props, {}> {
<div key={index}>
<Button
className={this.props.classes.download}
href={file.url}
href={this.fixUrl(file.url, updateInfo.version)}
>
<IconButton><CloudDownload /></IconButton>{this.urlToFilename(file.url)}
</Button>
+149 -102
View File
@@ -3,8 +3,13 @@ import * as React from 'react'
import {
Button,
CircularProgress,
FormControl,
FormControlLabel,
Grid,
IconButton,
Input,
InputAdornment,
InputLabel,
MenuItem,
Modal,
Paper,
@@ -17,6 +22,10 @@ import { DataSourceState, MqttOptions } from '../../../../backend/src/DataSource
import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles'
import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../../events'
import Notification from './Notification'
import Visibility from '@material-ui/icons/Visibility'
import VisibilityOff from '@material-ui/icons/VisibilityOff'
import sha1 = require('sha1')
interface Props {
@@ -34,7 +43,7 @@ const protocols = [
interface State {
connecting: boolean
connectionId?: string
error?: Error
error?: string
visible: boolean
host: string
protocol: string
@@ -44,6 +53,7 @@ interface State {
clientId: string
username: string
password: string
showPassword: boolean
}
declare var window: any
@@ -71,6 +81,7 @@ class Connection extends React.Component<Props, State> {
password: '',
connecting: false,
connectionId: undefined,
showPassword: false,
}
this.state = Object.assign({}, defaultState, storedSettings)
@@ -80,6 +91,10 @@ class Connection extends React.Component<Props, State> {
window.localStorage.setItem('connectionSettings', JSON.stringify(this.state))
}
private handleClickShowPassword = () => {
this.setState({ showPassword: !this.state.showPassword })
}
private optionsFromState(): MqttOptions {
const protocol = this.state.protocol === 'tcp://' ? 'mqtt://' : this.state.protocol
const url = `${protocol}${this.state.host}:${this.state.port}`
@@ -87,7 +102,7 @@ class Connection extends React.Component<Props, State> {
return {
url,
username: this.state.username || undefined,
password: this.state.username || undefined,
password: this.state.password || undefined,
tls: this.state.tls,
certValidation: this.state.certValidation,
}
@@ -110,8 +125,8 @@ class Connection extends React.Component<Props, State> {
this.props.onConnection(connectionId)
this.setState({ visible: false })
} else if (state.error) {
console.log('error', state.error)
this.setState({ error: state.error })
this.disconnect()
}
})
}
@@ -150,6 +165,9 @@ class Connection extends React.Component<Props, State> {
button: {
margin: theme.spacing.unit,
},
passwordFormControl: {
marginTop: '16px',
},
}
}
@@ -163,108 +181,137 @@ class Connection extends React.Component<Props, State> {
public render() {
const { classes } = this.props
return <Modal open={this.state.visible} disableAutoFocus={true} onClose={() => { console.log('close') }}>
<Paper className={classes.root}>
<Toolbar>
<Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography>
</Toolbar>
<form className={classes.container} noValidate autoComplete="off">
<Grid container spacing={24}>
<Grid item xs={2}>
<TextField
select
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 item xs={7}>
<TextField
label="Host"
className={classes.textField}
value={this.state.host}
onChange={this.handleChange('host')}
margin="normal"
/>
</Grid>
<Grid item xs={3}>
<TextField
label="Port"
className={classes.textField}
value={this.state.port}
onChange={this.handleChange('port')}
margin="normal"
/>
</Grid>
<Grid item xs={5}>
<TextField
label="Username"
className={classes.textField}
value={this.state.username}
onChange={this.handleChange('username')}
margin="normal"
/>
</Grid>
<Grid item xs={5}>
<TextField
label="Password"
type="type"
className={classes.textField}
value={this.state.password}
onChange={this.handleChange('password')}
margin="normal"
/>
</Grid>
<Grid item xs={4}>
<div className={classes.switch}>
<FormControlLabel
control={(
<Switch
checked={this.state.certValidation}
onChange={() => this.setState({ certValidation: !this.state.certValidation })}
color="primary"
const passwordVisibilityButton = (
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
>
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
)
let renderError = null
if (this.state.error) {
renderError = (
<Notification
message={this.state.error}
type="error"
onClose={() => { this.setState({ error: undefined }) }}
/>
)
}
return (
<div>
{renderError}
<Modal open={this.state.visible} disableAutoFocus={true}>
<Paper className={classes.root}>
<Toolbar>
<Typography className={classes.title} variant="h6" color="inherit">MQTT Connection</Typography>
</Toolbar>
<form className={classes.container} noValidate={true} autoComplete="off">
<Grid container={true} spacing={24}>
<Grid item={true} xs={2}>
<TextField
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 item xs={7}>
<TextField
label="Host"
className={classes.textField}
value={this.state.host}
onChange={this.handleChange('host')}
margin="normal"
/>
</Grid>
<Grid item={true} xs={3}>
<TextField
label="Port"
className={classes.textField}
value={this.state.port}
onChange={this.handleChange('port')}
margin="normal"
/>
</Grid>
<Grid item={true} xs={5}>
<TextField
label="Username"
className={classes.textField}
value={this.state.username}
onChange={this.handleChange('username')}
margin="normal"
/>
</Grid>
<Grid item={true} xs={5}>
<FormControl className={`${classes.textField} ${classes.passwordFormControl}`}>
<InputLabel htmlFor="adornment-password">Password</InputLabel>
<Input
id="adornment-password"
type={this.state.showPassword ? 'text' : 'password'}
value={this.state.password}
onChange={this.handleChange('password')}
endAdornment={passwordVisibilityButton}
/>
)}
label="Validate certificate"
labelPlacement="bottom"
/>
</div>
</Grid>
<Grid item xs={4}>
<div className={classes.switch}>
<FormControlLabel
control={(
<Switch
checked={this.state.tls}
onChange={() => this.setState({ tls: !this.state.tls })}
color="primary"
</FormControl>
</Grid>
<Grid item={true} xs={4}>
<div className={classes.switch}>
<FormControlLabel
control={(
<Switch
checked={this.state.certValidation}
onChange={() => this.setState({ certValidation: !this.state.certValidation })}
color="primary"
/>
)}
label="Validate certificate"
labelPlacement="bottom"
/>
)}
label="Encryption (tls)"
labelPlacement="bottom"
/>
</div>
</Grid>
<Grid item={true} xs={4}>
<div className={classes.switch}>
<FormControlLabel
control={(
<Switch
checked={this.state.tls}
onChange={() => this.setState({ tls: !this.state.tls })}
color="primary"
/>
)}
label="Encryption (tls)"
labelPlacement="bottom"
/>
</div>
</Grid>
<Grid item={true} xs={4} />
</Grid>
<br />
<div style={{ textAlign: 'right' }}>
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
Save
</Button>
{this.renderConnectButton()}
</div>
</Grid>
<Grid item xs={4}></Grid>
</Grid>
<br />
<div style={{ textAlign: 'right' }}>
<Button variant="contained" color="secondary" className={classes.button} onClick={() => this.saveConnectionSettings()}>
Save
</Button>
{ this.renderConnectButton() }
</div>
</form>
</Paper>
</Modal>
</form>
</Paper>
</Modal>
</div>
)
}
private renderConnectButton() {
@@ -0,0 +1,55 @@
import * as React from 'react'
import { Snackbar, SnackbarContent } from '@material-ui/core'
import { Theme, withStyles } from '@material-ui/core/styles'
import { green, red } from '@material-ui/core/colors'
enum MessageType {
success = 'success',
error = 'error',
}
interface Props {
message?: string
type: MessageType
onClose: () => void
classes: any
}
class Notification extends React.Component<Props, {}> {
constructor(props: Props) {
super(props)
}
public static styles = (theme: Theme) => ({
success: {
backgroundColor: green[600],
color: theme.typography.button.color,
},
error: {
backgroundColor: red[600],
color: theme.typography.button.color,
},
})
public render() {
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={Boolean(this.props.message)}
autoHideDuration={10000}
onClose={this.props.onClose}
>
<SnackbarContent
className={this.props.classes[this.props.type]}
message={this.props.message}
/>
</Snackbar>
)
}
}
export default withStyles(Notification.styles)(Notification)
+14
View File
@@ -0,0 +1,14 @@
import * as React from 'react'
interface Props {
date: Date
}
class DateFormatter extends React.Component<Props, {}> {
public render() {
return <span>{this.props.date.toLocaleDateString()} {this.props.date.toLocaleTimeString()}</span>
}
}
export default DateFormatter
@@ -3,11 +3,8 @@ import * as q from '../../../../backend/src/Model'
import { Theme, withTheme } from '@material-ui/core/styles'
import Fade from '@material-ui/core/Fade'
import DateFormatter from '../DateFormatter'
import History from './History'
import Paper from '@material-ui/core/Paper'
import Popper from '@material-ui/core/Popper'
import ValueRenderer from './ValueRenderer'
const throttle = require('lodash.throttle')
@@ -53,7 +50,7 @@ class MessageHistory extends React.Component<Props, State> {
const history = node.messageHistory.toArray()
const historyElements = history.map(message => ({
title: message.received.toGMTString(),
title: <DateFormatter date={message.received} />,
value: message.value,
}))
+7 -1
View File
@@ -14,6 +14,7 @@ import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles'
import { AppState } from '../../reducers'
import Copy from '../Copy'
import DateFormatter from '../DateFormatter'
import ExpandMore from '@material-ui/icons/ExpandMore'
import MessageHistory from './MessageHistory'
import NodeStats from './NodeStats'
@@ -115,7 +116,12 @@ class Sidebar extends React.Component<Props, State> {
<Typography className={classes.heading}>Value {copyValue}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails style={this.detailsStyle}>
<div ref={this.valueRef}><ValueRenderer message={this.props.node && this.props.node.message} /></div>
<div style={{ width: '100%', textAlign:'right' }}>
{this.props.node && this.props.node.message && <i><DateFormatter date={this.props.node.message.received} /></i>}
</div>
<div ref={this.valueRef}>
<ValueRenderer message={this.props.node && this.props.node.message} />
</div>
<div><MessageHistory onSelect={this.handleMessageHistorySelect} node={this.props.node} /></div>
<Popper open={Boolean(this.state.compareMessage)} anchorEl={this.valueRef.current} placement="left" transition={true}>
{({ TransitionProps }) => (
+39
View File
@@ -3,6 +3,7 @@ import * as q from '../../../../backend/src/Model'
import { Theme, withStyles } from '@material-ui/core/styles'
import LabelImportant from '@material-ui/icons/LabelImportant'
import TreeNodeSubnodes from './TreeNodeSubnodes'
import TreeNodeTitle from './TreeNodeTitle'
import { bindActionCreators } from 'redux'
@@ -29,6 +30,12 @@ const styles = (theme: Theme) => {
backgroundColor: 'rgba(80, 80, 80, 0.35)',
},
},
topicSelect: {
float: 'right' as 'right',
opacity: 0,
cursor: 'pointer',
marginTop: '-1px',
},
}
}
@@ -61,6 +68,7 @@ class TreeNode extends React.Component<Props, State> {
private willUpdateTime: number = performance.now()
private titleRef = React.createRef<HTMLDivElement>()
private topicSelectRef = React.createRef<HTMLDivElement>()
private subnodesDidchange = () => {
this.dirtySubnodes = true
@@ -155,6 +163,8 @@ class TreeNode extends React.Component<Props, State> {
className={`${classes.node} ${!this.props.isRoot ? classes.hover : ''}`}
onClick={this.didClickNode}
style={this.props.style}
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
>
<span ref={this.titleRef} style={animation}>
<TreeNodeTitle
@@ -164,11 +174,40 @@ class TreeNode extends React.Component<Props, State> {
lastUpdate={this.props.treeNode.lastUpdate}
/>
</span>
<div
className={this.props.classes.topicSelect}
ref={this.topicSelectRef}
onClick={this.didSelectNode}
title="Select topic"
>
<LabelImportant style={{ fontSize: '14px' }} />
</div>
{this.renderNodes()}
</div>
)
}
private mouseOver = (event: React.MouseEvent) => {
event.stopPropagation()
if (this.topicSelectRef.current) {
this.topicSelectRef.current.style.opacity = '1'
}
}
private mouseOut = (event: React.MouseEvent) => {
event.stopPropagation()
if (this.topicSelectRef.current) {
this.topicSelectRef.current.style.opacity = '0'
}
}
private didSelectNode = (event: React.MouseEvent) => {
event.stopPropagation()
if (this.topicSelectRef.current) {
this.topicSelectRef.current.style.opacity = '1'
}
this.props.actions.selectTopic(this.props.treeNode)
}
private didClickNode = (event: React.MouseEvent) => {
event.stopPropagation()
this.toggle()
+2 -2
View File
@@ -3,7 +3,7 @@ import { EventDispatcher } from '../../../events'
export interface DataSourceState {
connecting: boolean
connected: boolean
error?: Error
error?: string
}
export class DataSourceStateMachine {
@@ -25,7 +25,7 @@ export class DataSourceStateMachine {
public setError(error: Error) {
this.state = {
error,
error: error.message,
connected: false,
connecting: false,
}
+4 -1
View File
@@ -1,6 +1,7 @@
import * as Url from 'url'
import { Client, connect as mqttConnect } from 'mqtt'
import { DataSource, DataSourceStateMachine } from './'
import * as Url from 'url'
export interface MqttOptions {
url: string
@@ -36,6 +37,8 @@ export class MqttSource implements DataSource<MqttOptions> {
const client = mqttConnect(url, {
resubscribe: false,
rejectUnauthorized: !options.certValidation,
username: options.username,
password: options.password,
})
this.client = client
+10 -4
View File
@@ -2,6 +2,9 @@ const { app, BrowserWindow } = require('electron')
const { autoUpdater } = require("electron-updater")
const log = require('electron-log');
const { ConnectionManager, updateNotifier } = require('./backend/build/backend/src/index.js')
const fs = require('fs')
const path = require('path')
require('electron-debug')({enabled: process.argv[2] === '--debug'});
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
@@ -15,21 +18,21 @@ connectionManager.manageConnections()
let mainWindow
function createWindow () {
const icon = path.join(__dirname, 'icon.png')
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1024,
height: 700,
webPreferences: {
nodeIntegration: true
}
},
icon
})
console.log(icon)
// 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('close', function () {
connectionManager.closeAllConnections()
@@ -53,10 +56,13 @@ app.on('ready', () => {
let updateInfo
autoUpdater.on('update-available', (info) => {
console.log('there is an update')
updateInfo = info
})
autoUpdater.on('error', () => {
console.log('could not update due to error')
if (updateInfo) {
updateNotifier.notify(updateInfo)
}
+3 -1
View File
@@ -28,7 +28,9 @@ class IpcMainEventBus implements EventBusInterface {
}
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
this.client.send(event.topic, msg)
if (!this.client.isDestroyed()) {
this.client.send(event.topic, msg)
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+117 -8
View File
@@ -1,6 +1,6 @@
{
"name": "MQTT-Explorer",
"version": "0.0.5",
"version": "0.0.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -196,6 +196,12 @@
"integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==",
"dev": true
},
"@types/mustache": {
"version": "0.8.32",
"resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-0.8.32.tgz",
"integrity": "sha512-RTVWV485OOf4+nO2+feurk0chzHkSjkjALiejpHltyuMf/13fGymbbNNFrSKdSSUg1TIwzszXdWsVirxgqYiFA==",
"dev": true
},
"@types/node": {
"version": "10.12.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
@@ -414,6 +420,16 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"axios": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"dev": true,
"requires": {
"follow-redirects": "^1.3.0",
"is-buffer": "^1.1.5"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -1183,6 +1199,15 @@
"yargs": "^12.0.5"
}
},
"electron-debug": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/electron-debug/-/electron-debug-2.0.0.tgz",
"integrity": "sha512-orGlw9uErUztdD7cgdKz78txq3czpOnKG/zvvsINkUsugqL+dn77UFrbwRGVgPwuLJ7Ejbjjk9EcxIcgTivMbA==",
"requires": {
"electron-is-dev": "^0.3.0",
"electron-localshortcut": "^3.0.0"
}
},
"electron-download": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz",
@@ -1200,6 +1225,42 @@
"sumchecker": "^2.0.2"
}
},
"electron-is-accelerator": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz",
"integrity": "sha1-UJ5RDCala1Xhf4Y6SwThEYRqsns="
},
"electron-is-dev": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz",
"integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4="
},
"electron-localshortcut": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/electron-localshortcut/-/electron-localshortcut-3.1.0.tgz",
"integrity": "sha512-MgL/j5jdjW7iA0R6cI7S045B0GlKXWM1FjjujVPjlrmyXRa6yH0bGSaIAfxXAF9tpJm3pLEiQzerYHkRh9JG/A==",
"requires": {
"debug": "^2.6.8",
"electron-is-accelerator": "^0.1.0",
"keyboardevent-from-electron-accelerator": "^1.1.0",
"keyboardevents-areequal": "^0.2.1"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"electron-log": {
"version": "2.2.17",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-2.2.17.tgz",
@@ -1467,6 +1528,32 @@
}
}
},
"follow-redirects": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.6.1.tgz",
"integrity": "sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==",
"dev": true,
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -1541,7 +1628,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
@@ -1639,7 +1726,7 @@
},
"got": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
"resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz",
"integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"dev": true,
"requires": {
@@ -1852,6 +1939,12 @@
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
},
"is-builtin-module": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
@@ -1924,7 +2017,7 @@
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
@@ -2114,6 +2207,16 @@
"verror": "1.10.0"
}
},
"keyboardevent-from-electron-accelerator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-1.1.0.tgz",
"integrity": "sha512-VDC4vKWGrR3VgIKCE4CsXnvObGgP8C2idnTKEMUkuEuvDGE1GEBX9FtNdJzrD00iQlhI3xFxRaeItsUmlERVng=="
},
"keyboardevents-areequal": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz",
"integrity": "sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw=="
},
"latest-version": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
@@ -2462,6 +2565,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"mustache": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.0.1.tgz",
"integrity": "sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA==",
"dev": true
},
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@@ -3798,7 +3907,7 @@
},
"p-is-promise": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
"dev": true
},
@@ -4469,7 +4578,7 @@
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
@@ -5085,7 +5194,7 @@
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
@@ -5117,7 +5226,7 @@
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
"dev": true
},
+2 -1
View File
@@ -1,8 +1,9 @@
#!/bin/bash
docker run --rm -ti \
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \
--env ELECTRON_CACHE="/root/.cache/electron" \
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
--env GH_TOKEN="$GH_TOKEN" \
-v ${PWD}:/project \
-v ~/.cache/electron:/root/.cache/electron \
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
+16 -12
View File
@@ -1,6 +1,6 @@
{
"name": "MQTT-Explorer",
"version": "0.0.5",
"version": "0.0.7",
"description": "Explore your message queues",
"main": "electron.js",
"scripts": {
@@ -41,25 +41,29 @@
"homepage": "https://github.com",
"license": "ISC",
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/mustache": "^0.8.32",
"@types/node": "^10.12.18",
"@types/sha1": "^1.1.1",
"@types/socket.io": "^2.1.2",
"axios": "^0.18.0",
"chai": "^4.2.0",
"electron": "^4.0.0",
"electron-builder": "^20.38.4",
"mocha": "^5.2.0",
"mustache": "^3.0.1",
"nyc": "^13.1.0",
"source-map-support": "^0.5.9",
"ts-node": "^7.0.1",
"tslint": "^5.12.0",
"tslint-config-airbnb": "^5.11.1",
"tslint-react": "^3.6.0",
"typescript": "^3.2.2",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"@types/sha1": "^1.1.1",
"@types/socket.io": "^2.1.2",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"source-map-support": "^0.5.9",
"tslint-strict-null-checks": "^1.0.1"
"tslint-strict-null-checks": "^1.0.1",
"typescript": "^3.2.2"
},
"dependencies": {
"electron-debug": "^2.0.0",
"electron-log": "^2.2.17",
"electron-updater": "^4.0.6",
"mqtt": "^2.18.8",
+9 -7
View File
@@ -5,19 +5,19 @@ const linux: builder.CliOptions = {
ia32: true,
armv7l: true,
arm64: true,
linux: ['AppImage', 'deb', 'rpm', 'pacman', 'tar.gz'],
linux: ['AppImage'],
projectDir: './build/clean',
publish: 'onTag',
publish: 'always',
}
const linuxSnap: builder.CliOptions = {
x64: false,
x64: true,
ia32: false,
armv7l: false,
arm64: true,
arm64: false,
linux: ['snap'],
projectDir: './build/clean',
publish: 'onTag',
publish: 'always',
}
const win: builder.CliOptions = {
@@ -27,7 +27,7 @@ const win: builder.CliOptions = {
arm64: false,
win: ['portable', 'nsis'],
projectDir: './build/clean',
publish: 'onTag',
publish: 'always',
}
const mac: builder.CliOptions = {
@@ -37,7 +37,7 @@ const mac: builder.CliOptions = {
arm64: false,
mac: ['dmg'],
projectDir: './build/clean',
publish: 'onTag',
publish: 'always',
}
async function executeBuild() {
@@ -48,6 +48,8 @@ async function executeBuild() {
break
case 'linux':
await builder.build(linux)
break
case 'snap':
try {
await builder.build(linuxSnap)
} catch {
-10
View File
@@ -26,13 +26,3 @@ cd ..
npm run build
rm -rf app/node_modules
cd "$ORIGINAL_DIR"
exit 0
docker run --rm -ti \
--env ELECTRON_CACHE="/root/.cache/electron" \
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
--env GH_TOKEN="$GH_TOKEN" \
-v ${PWD}:/project \
-v ~/.cache/electron:/root/.cache/electron \
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
electronuserland/builder:wine node_modules/.bin/ts-node build.ts
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+64
View File
@@ -0,0 +1,64 @@
import * as mustache from 'mustache'
import { readFileSync, writeFileSync } from 'fs'
import axios from 'axios'
interface Release {
name: string
assets: Asset[]
}
interface Asset {
name: string
browser_download_url: string
}
function createUrl(title: string, path: string) {
return `[${title}](${path})`
}
function fileExtension(file: string): string {
const match = file.match(/\.([a-zA-Z]+)$/)
return (match && match[1]) || ''
}
async function createReadme(): Promise<void> {
const release: Release = (await axios.get('https://api.github.com/repos/thomasnordquist/mqtt-explorer/releases/latest')).data
const linux64 = release.assets.filter(asset => /(x86_64|amd64)\.(AppImage|deb|rpm)$/.test(asset.name))
const windowsInstaller = release.assets.find(asset => /Setup-.+\.exe$/.test(asset.name))
const windowsPortable = release.assets.find(asset => /-(?!Setup).+\.exe$/.test(asset.name))
const macDmg = release.assets.find(asset => /\.dmg$/.test(asset.name))
if (linux64.length === 0 || !windowsInstaller || !windowsPortable || !macDmg) {
console.error('failed retrieving')
process.exit(1)
return
}
const linuxTargets = linux64
.map(item => createUrl(fileExtension(item.browser_download_url), item.browser_download_url))
.join(', ')
const windowsTargets = [
createUrl('portable', windowsPortable.browser_download_url),
createUrl('installer', windowsInstaller.browser_download_url),
].join(', ')
const macTargets = [
createUrl('dmg', macDmg.browser_download_url),
].join(', ')
const template = readFileSync('./Readme.tpl.md').toString()
const rendered = mustache.render(template, {
linuxTargets,
windowsTargets,
macTargets,
version: release.name,
})
writeFileSync('./Readme.md', rendered)
}
createReadme()