Add quality of service option to subscriptions

Fixes #323, #14, #334
Fixes #132
This commit is contained in:
Thomas Nordquist
2020-04-20 12:18:24 +02:00
parent e87a7115c5
commit b72fc48bdb
19 changed files with 904 additions and 215 deletions

View File

@@ -1,7 +1,6 @@
import * as React from 'react'
import { useState, useCallback, memo } from 'react'
import Add from '@material-ui/icons/Add'
import ClearAdornment from '../helper/ClearAdornment'
import Delete from '@material-ui/icons/Delete'
import Lock from '@material-ui/icons/Lock'
import Undo from '@material-ui/icons/Undo'
import { bindActionCreators } from 'redux'
@@ -9,8 +8,10 @@ import { connect } from 'react-redux'
import { connectionManagerActions } from '../../actions'
import { ConnectionOptions } from '../../model/ConnectionOptions'
import { Theme, withStyles } from '@material-ui/core/styles'
import { Button, Grid, IconButton, TextField, List, ListItem, ListItemText, Tooltip } from '@material-ui/core'
import { Button, Grid, TextField, Tooltip } from '@material-ui/core'
import { QosSelect } from '../QosSelect'
import { QoS } from '../../../../backend/src/DataSource/MqttSource'
import Subscriptions from './Subscriptions'
interface Props {
connection: ConnectionOptions
@@ -18,116 +19,93 @@ interface Props {
managerActions: typeof connectionManagerActions
}
interface State {
subscription: string
}
const ConnectionSettings = memo(function ConnectionSettings(props: Props) {
const [qos, setQos] = useState<QoS>(0)
const [topic, setTopic] = useState('')
const { classes } = props
class ConnectionSettings extends React.Component<Props, State> {
constructor(props: any) {
super(props)
this.state = { subscription: '' }
}
private handleChange = (name: string) => (event: any) => {
this.props.managerActions.updateConnection(this.props.connection.id, {
[name]: event.target.value,
})
}
private renderSubscriptions() {
const connection = this.props.connection
return connection.subscriptions.map(subscription => (
<Subscription
deleteAction={() => this.props.managerActions.deleteSubscription(subscription, connection.id)}
subscription={subscription}
key={subscription}
/>
))
}
public render() {
const { classes } = this.props
return (
<div>
<form className={classes.container} noValidate={true} autoComplete="off">
<Grid container={true} spacing={3}>
<Grid item={true} xs={10} className={classes.gridPadding}>
<TextField
className={classes.fullWidth}
label="Subscription"
margin="normal"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ subscription: event.target.value })
}
/>
</Grid>
<Grid item={true} xs={2} className={classes.gridPadding}>
<Button
className={classes.button}
color="secondary"
onClick={() =>
this.props.managerActions.addSubscription(this.state.subscription, this.props.connection.id)
}
variant="contained"
>
<Add /> Add
</Button>
</Grid>
<Grid item={true} xs={12} style={{ padding: 0 }}>
<List className={`${classes.topicList} advanced-connection-settings-topic-list`} component="nav">
<div className={classes.list}>{this.renderSubscriptions()}</div>
</List>
</Grid>
<Grid item={true} xs={7} className={classes.gridPadding}>
<TextField
className={classes.fullWidth}
label="MQTT Client ID"
margin="normal"
value={this.props.connection.clientId}
onChange={this.handleChange('clientId')}
/>
</Grid>
<Grid item={true} xs={3} className={classes.gridPadding}>
<div>
<Tooltip title="Manage tls connection certificates" placement="top">
<Button
variant="contained"
className={classes.button}
onClick={() => this.props.managerActions.toggleCertificateSettings()}
>
<Lock /> Certificates
</Button>
</Tooltip>
</div>
</Grid>
<Grid item={true} xs={2} className={classes.gridPadding}>
<Button
variant="contained"
className={classes.button}
onClick={this.props.managerActions.toggleAdvancedSettings}
>
<Undo /> Back
</Button>
</Grid>
</Grid>
</form>
</div>
)
}
}
const Subscription = (props: { subscription: string; deleteAction: any }) => {
return (
<ListItem style={{ padding: '0 0 0 8px' }}>
<ListItemText>
<IconButton onClick={props.deleteAction} style={{ padding: '6px' }}>
<Delete />
</IconButton>
{props.subscription}
</ListItemText>
</ListItem>
const updateSubscription = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => setTopic(event.target.value),
[]
)
}
const handleChange = useCallback(
(name: string) => (event: any) => {
props.managerActions.updateConnection(props.connection.id, {
[name]: event.target.value,
})
},
[]
)
return (
<div>
<form className={classes.container} noValidate={true} autoComplete="off">
<Grid container={true} spacing={3}>
<Grid item={true} xs={8} className={classes.gridPadding}>
<TextField
className={classes.fullWidth}
label="Topic"
placeholder="example/topic"
margin="normal"
value={topic}
onChange={updateSubscription}
/>
</Grid>
<Grid item={true} xs={2} className={classes.gridPadding}>
<div className={classes.qos}>
<QosSelect label="QoS" selected={qos} onChange={setQos} />
</div>
</Grid>
<Grid item={true} xs={2} className={classes.gridPadding}>
<Button
className={classes.button}
color="secondary"
onClick={() => props.managerActions.addSubscription({ topic, qos }, props.connection.id)}
variant="contained"
>
<Add /> Add
</Button>
</Grid>
<Grid item={true} xs={12} style={{ padding: 0 }}>
<Subscriptions connection={props.connection} />
</Grid>
<Grid item={true} xs={7} className={classes.gridPadding}>
<TextField
className={classes.fullWidth}
label="MQTT Client ID"
margin="normal"
value={props.connection.clientId}
onChange={handleChange('clientId')}
/>
</Grid>
<Grid item={true} xs={3} className={classes.gridPadding}>
<div>
<Tooltip title="Manage tls connection certificates" placement="top">
<Button
variant="contained"
className={classes.button}
onClick={() => props.managerActions.toggleCertificateSettings()}
>
<Lock /> Certificates
</Button>
</Tooltip>
</div>
</Grid>
<Grid item={true} xs={2} className={classes.gridPadding}>
<Button
variant="contained"
className={classes.button}
onClick={props.managerActions.toggleAdvancedSettings}
>
<Undo /> Back
</Button>
</Grid>
</Grid>
</form>
</div>
)
})
const mapDispatchToProps = (dispatch: any) => {
return {
@@ -142,16 +120,13 @@ const styles = (theme: Theme) => ({
gridPadding: {
padding: '0 12px !important',
},
topicList: {
height: '180px',
overflowY: 'scroll' as 'scroll',
margin: '8px 16px',
backgroundColor: theme.palette.background.default,
},
button: {
marginTop: theme.spacing(3),
float: 'right' as 'right',
},
qos: {
marginTop: theme.spacing(1),
},
})
export default connect(undefined, mapDispatchToProps)(withStyles(styles)(ConnectionSettings))

View File

@@ -1,7 +1,7 @@
import ConnectButton from './ConnectButton'
import Delete from '@material-ui/icons/Delete'
import React, { useCallback, useState } from 'react'
import Save from '@material-ui/icons/Save'
import Delete from '@material-ui/icons/Delete'
import Settings from '@material-ui/icons/Settings'
import Visibility from '@material-ui/icons/Visibility'
import VisibilityOff from '@material-ui/icons/VisibilityOff'

View File

@@ -0,0 +1,90 @@
import React, { useCallback, useState } from 'react'
import Delete from '@material-ui/icons/Delete'
import { connectionManagerActions } from '../../actions'
import { ConnectionOptions } from '../../model/ConnectionOptions'
import {
IconButton,
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Paper,
Theme,
} from '@material-ui/core'
import { bindActionCreators } from 'redux'
import { withStyles } from '@material-ui/styles'
import { connect } from 'react-redux'
function Subscriptions(props: {
classes: any
connection: ConnectionOptions
managerActions: typeof connectionManagerActions
}) {
const { classes, connection, managerActions } = props
return (
<TableContainer component={Paper} className={`${classes.topicList} advanced-connection-settings-topic-list`}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell align="left" padding="checkbox" className={classes.tableTitleCell}></TableCell>
<TableCell className={classes.tableTitleCell}>Topic</TableCell>
<TableCell align="right" className={classes.tableTitleCell}>
QoS
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{connection.subscriptions.map(subscription => (
<TableRow key={subscription.topic + '_qos_' + subscription.qos}>
<TableCell align="right" className={classes.tableCell}>
<IconButton
onClick={() => managerActions.deleteSubscription(subscription, connection.id)}
style={{ padding: '6px' }}
>
<Delete />
</IconButton>
</TableCell>
<TableCell component="th" scope="row" className={classes.tableCell}>
{subscription.topic}
</TableCell>
<TableCell align="right" className={classes.tableCell}>
{subscription.qos}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
const mapDispatchToProps = (dispatch: any) => {
return {
managerActions: bindActionCreators(connectionManagerActions, dispatch),
}
}
const styles = (theme: Theme) => ({
tableCell: {
paddingTop: 0,
paddingBottom: 0,
wordBbreak: 'break-word',
},
tableTitleCell: {
paddingTop: `${theme.spacing(0.5)}px`,
paddingBottom: `${theme.spacing(0.5)}px`,
},
topicList: {
height: '196px',
overflowY: 'scroll' as 'scroll',
margin: `${theme.spacing(1)}px ${theme.spacing(1)}px 0 ${theme.spacing(1)}px`,
backgroundColor: theme.palette.background.default,
width: 'auto',
},
})
export default connect(undefined, mapDispatchToProps)(withStyles(styles)(Subscriptions))

View File

@@ -1,18 +1,8 @@
import * as React from 'react'
import { TextField, MenuItem, Tooltip } from '@material-ui/core'
import { connect } from 'react-redux'
import { AppState } from '../../../reducers'
import { bindActionCreators } from 'redux'
import { publishActions } from '../../../actions'
import { QoS } from '../../../backend/src/DataSource/MqttSource'
interface Props {
qos: 0 | 1 | 2
actions: {
publish: typeof publishActions
}
}
function QosSelect(props: Props) {
export function QosSelect(props: { selected: QoS; onChange: (value: QoS) => void; label?: string }) {
const tooltipStyle = { textAlign: 'center' as 'center', width: '100%' }
const itemStyle = { padding: '0' }
@@ -22,16 +12,16 @@ function QosSelect(props: Props) {
if (value !== 0 && value !== 1 && value !== 2) {
return
}
props.actions.publish.setQoS(value)
props.onChange(value)
},
[props.actions.publish]
[props.onChange]
)
return (
<TextField
select={true}
value={props.qos}
label={props.label}
value={props.selected}
margin="normal"
style={{ margin: '8px 0 8px 8px' }}
onChange={onChangeQos}
@@ -55,18 +45,4 @@ function QosSelect(props: Props) {
)
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: {
publish: bindActionCreators(publishActions, dispatch),
},
}
}
const mapStateToProps = (state: AppState) => {
return {
qos: state.publish.qos,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QosSelect)
export default React.memo(QosSelect)

View File

@@ -0,0 +1,34 @@
import * as React from 'react'
import { connect } from 'react-redux'
import { AppState } from '../../../reducers'
import { bindActionCreators } from 'redux'
import { publishActions } from '../../../actions'
import { QosSelect } from '../../QosSelect'
import { QoS } from '../../../../../backend/src/DataSource/MqttSource'
interface Props {
qos: QoS
actions: {
publish: typeof publishActions
}
}
function QosPublishOption(props: Props) {
return <QosSelect onChange={props.actions.publish.setQoS} selected={props.qos} />
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: {
publish: bindActionCreators(publishActions, dispatch),
},
}
}
const mapStateToProps = (state: AppState) => {
return {
qos: state.publish.qos,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QosPublishOption)

View File

@@ -1,4 +1,4 @@
import QosSelect from './QosSelect'
import QosSelect from './QosPublishOption'
import React from 'react'
import { Checkbox, FormControlLabel, Tooltip } from '@material-ui/core'
import { publishActions } from '../../../actions'