mirror of
https://github.com/thomasnordquist/MQTT-Explorer.git
synced 2026-09-11 17:13:53 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ba31de468 | ||
|
|
ab6938c7da | ||
|
|
1eeaee0fe0 | ||
|
|
0e7ec90c65 | ||
|
|
5eb6f41189 | ||
|
|
830dd31f06 | ||
|
|
0de3ce2c41 | ||
|
|
9c5140c419 | ||
|
|
61b64a5ac2 | ||
|
|
f905ba8e89 | ||
|
|
c6a0a15d95 | ||
|
|
75230ccb40 | ||
|
|
97a1cf72c5 | ||
|
|
71c06b3f74 | ||
|
|
b0fa4a9f7a | ||
|
|
d8c8799406 | ||
|
|
7c72344174 | ||
|
|
7d42ed12b1 | ||
|
|
0df5ff13c1 |
@@ -1,11 +1,15 @@
|
||||
# MQTT-Explorer
|
||||
[](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)
|
||||
|
||||

|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# MQTT-Explorer
|
||||
[](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)
|
||||
|
||||

|
||||
|
||||
## 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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
}))
|
||||
|
||||
|
||||
@@ -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 }) => (
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+117
-8
@@ -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
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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 |
@@ -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()
|
||||
Reference in New Issue
Block a user