diff --git a/app/src/actions/Connection.ts b/app/src/actions/Connection.ts index 66efa44..4d30d5a 100644 --- a/app/src/actions/Connection.ts +++ b/app/src/actions/Connection.ts @@ -9,7 +9,7 @@ import { } from '../../../events' import { AppState } from '../reducers' import { Dispatch } from 'redux' -import { MqttOptions } from '../../../backend/src/DataSource' +import { MqttOptions, DataSourceState } from '../../../backend/src/DataSource' import { showTree } from './Tree' import { TopicViewModel } from '../TopicViewModel' import { showError } from './Global' @@ -31,6 +31,27 @@ export const connect = (options: MqttOptions, connectionId: string) => (dispatch dispatch(showError(dataSourceState.error)) dispatch(disconnect()) } + dispatch(updateHealth(dataSourceState)) + }) +} + +const updateHealth = (dataSourceState: DataSourceState) => (dispatch: Dispatch, getState: () => AppState) => { + let state + if (dataSourceState.connecting) { + state = 'connecting' + } else if (!dataSourceState.connected) { + state = 'offline' + } else if (dataSourceState.connected) { + state = 'online' + } else { + state = undefined + } + + console.log(state) + + dispatch({ + type: ActionTypes.CONNECTION_SET_HEALTH, + health: state, }) } diff --git a/app/src/components/ConnectionHealthIndicator.tsx b/app/src/components/ConnectionHealthIndicator.tsx new file mode 100644 index 0000000..2cdda59 --- /dev/null +++ b/app/src/components/ConnectionHealthIndicator.tsx @@ -0,0 +1,54 @@ +import * as React from 'react' +import DeviceHubOutlined from '@material-ui/icons/DeviceHubOutlined' +import { AppState } from '../reducers' +import { connect } from 'react-redux' +import { ConnectionHealth } from '../reducers/Connection' +import { green, orange, red } from '@material-ui/core/colors' +import { StyleRulesCallback, withStyles } from '@material-ui/core/styles' +import { Tooltip } from '@material-ui/core'; + +const styles: StyleRulesCallback = theme => ({ + offline: { + color: red[700], + }, + online: { + color: green[500], + }, + connecting: { + color: orange[600], + }, +}) + +interface Props { + classes: any + connected: boolean + health?: ConnectionHealth +} + +class ConnectionHealthIndicator extends React.Component { + constructor(props: any) { + super(props) + } + + public render() { + const { classes, health, connected } = this.props + if (!health || !connected) { + return null + } + + return ( + + + + ) + } +} + +const mapStateToProps = (state: AppState) => { + return { + health: state.connection.health, + connected: state.connection.connected || state.connection.connecting, + } +} + +export default connect(mapStateToProps)(withStyles(styles)(ConnectionHealthIndicator)) diff --git a/app/src/components/ConnectionSetup/ConnectionSettings.tsx b/app/src/components/ConnectionSetup/ConnectionSettings.tsx index e9bd715..ba53641 100644 --- a/app/src/components/ConnectionSetup/ConnectionSettings.tsx +++ b/app/src/components/ConnectionSetup/ConnectionSettings.tsx @@ -14,7 +14,6 @@ import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles' import { Button, - CircularProgress, FormControl, FormControlLabel, Grid, @@ -26,6 +25,7 @@ import { Switch, TextField, } from '@material-ui/core' +import ConnectionHealthIndicator from '../ConnectionHealthIndicator' interface Props { connection: ConnectionOptions @@ -284,7 +284,7 @@ class ConnectionSettings extends React.Component { if (this.props.connecting) { return ( ) } diff --git a/app/src/components/TitleBar.tsx b/app/src/components/TitleBar.tsx index 7dd2013..de11e55 100644 --- a/app/src/components/TitleBar.tsx +++ b/app/src/components/TitleBar.tsx @@ -17,6 +17,7 @@ import { connect } from 'react-redux' import { connectionActions, settingsActions } from '../actions' import { fade } from '@material-ui/core/styles/colorManipulator' import { StyleRulesCallback, withStyles } from '@material-ui/core/styles' +import ConnectionHealthIndicator from './ConnectionHealthIndicator'; const styles: StyleRulesCallback = theme => ({ title: { @@ -99,6 +100,7 @@ class TitleBar extends React.Component { + ) diff --git a/app/src/reducers/Connection.ts b/app/src/reducers/Connection.ts index b7bcc91..d568616 100644 --- a/app/src/reducers/Connection.ts +++ b/app/src/reducers/Connection.ts @@ -4,6 +4,7 @@ import * as q from '../../../backend/src/Model' import { MqttOptions } from '../../../backend/src/DataSource' import { TopicViewModel } from '../TopicViewModel' +export type ConnectionHealth = 'offline' | 'online' | 'connecting' export interface ConnectionState { host?: string tree?: q.Tree @@ -12,6 +13,7 @@ export interface ConnectionState { error?: string connected: boolean connecting: boolean + health?: ConnectionHealth } export type Action = SetConnecting | SetConnected | SetDisconnected | ShowError @@ -21,6 +23,7 @@ export enum ActionTypes { CONNECTION_SET_CONNECTED = 'CONNECTION_SET_CONNECTED', CONNECTION_SET_DISCONNECTED = 'CONNECTION_SET_DISCONNECTED', CONNECTION_SET_SHOW_ERROR = 'CONNECTION_SET_SHOW_ERROR', + CONNECTION_SET_HEALTH = 'CONNECTION_SET_HEALTH', } export interface SetConnecting { @@ -38,6 +41,11 @@ export interface SetDisconnected { type: ActionTypes.CONNECTION_SET_DISCONNECTED } +export interface SetHealth { + health: ConnectionHealth + type: ActionTypes.CONNECTION_SET_DISCONNECTED +} + export interface ShowError { type: ActionTypes.CONNECTION_SET_SHOW_ERROR error?: Error @@ -46,6 +54,7 @@ export interface ShowError { const initialState: ConnectionState = { connected: false, connecting: false, + health: 'disconnected', } export const connectionReducer = createReducer(initialState, { @@ -53,6 +62,7 @@ export const connectionReducer = createReducer(initialState, { CONNECTION_SET_CONNECTED: setConnected, CONNECTION_SET_DISCONNECTED: setDisconnected, CONNECTION_SET_SHOW_ERROR: showError, + CONNECTION_SET_HEALTH: setHealth, }) function setConnecting(state: ConnectionState, action: SetConnecting): ConnectionState { @@ -64,6 +74,13 @@ function setConnecting(state: ConnectionState, action: SetConnecting): Connectio } } +function setHealth(state: ConnectionState, action: SetHealth): ConnectionState { + return { + ...state, + health: action.health, + } +} + function setConnected(state: ConnectionState, action: SetConnected): ConnectionState { return { ...state, diff --git a/backend/src/DataSource/DataSourceState.ts b/backend/src/DataSource/DataSourceState.ts index 1be3fc0..861304c 100644 --- a/backend/src/DataSource/DataSourceState.ts +++ b/backend/src/DataSource/DataSourceState.ts @@ -25,16 +25,17 @@ export class DataSourceStateMachine { public setError(error: Error) { this.state = { + ...this.state, error: error.message, - connected: false, - connecting: false, + // connected: false, + // connecting: false, } this.onUpdate.dispatch(this.state) } public setConnecting() { this.state = { - error: undefined, + ...this.state, connected: false, connecting: true, } diff --git a/backend/src/DataSource/MqttSource.ts b/backend/src/DataSource/MqttSource.ts index 4926480..6d55a44 100644 --- a/backend/src/DataSource/MqttSource.ts +++ b/backend/src/DataSource/MqttSource.ts @@ -55,6 +55,10 @@ export class MqttSource implements DataSource { this.stateMachine.setConnected(false) }) + client.on('end', () => { + this.stateMachine.setConnected(false) + }) + client.on('reconnect', () => { this.stateMachine.setConnecting() }) diff --git a/backend/src/index.ts b/backend/src/index.ts index fd8dfdd..ac82d6c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -61,6 +61,7 @@ export class ConnectionManager { if (connection) { connection.disconnect() delete this.connections[hash] + connection.stateMachine.onUpdate.removeAllListeners() } }