This commit is contained in:
proddy
2021-05-14 12:45:57 +02:00
parent 15df0c0552
commit fec5ff3132
108 changed files with 3508 additions and 2455 deletions

View File

@@ -1,9 +1,13 @@
import React, { Component } from 'react';
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'
import { Component } from 'react';
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom';
import { Tabs, Tab } from '@material-ui/core';
import { AuthenticatedContextProps, withAuthenticatedContext, AuthenticatedRoute } from '../authentication';
import {
AuthenticatedContextProps,
withAuthenticatedContext,
AuthenticatedRoute
} from '../authentication';
import { MenuAppBar } from '../components';
import MqttStatusController from './MqttStatusController';
import MqttSettingsController from './MqttSettingsController';
@@ -11,8 +15,7 @@ import MqttSettingsController from './MqttSettingsController';
type MqttProps = AuthenticatedContextProps & RouteComponentProps;
class Mqtt extends Component<MqttProps> {
handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
handleTabChange = (path: string) => {
this.props.history.push(path);
};
@@ -20,17 +23,33 @@ class Mqtt extends Component<MqttProps> {
const { authenticatedContext } = this.props;
return (
<MenuAppBar sectionTitle="MQTT">
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
<Tabs
value={this.props.match.url}
onChange={(e, path) => this.handleTabChange(path)}
variant="fullWidth"
>
<Tab value="/mqtt/status" label="MQTT Status" />
<Tab value="/mqtt/settings" label="MQTT Settings" disabled={!authenticatedContext.me.admin} />
<Tab
value="/mqtt/settings"
label="MQTT Settings"
disabled={!authenticatedContext.me.admin}
/>
</Tabs>
<Switch>
<AuthenticatedRoute exact path="/mqtt/status" component={MqttStatusController} />
<AuthenticatedRoute exact path="/mqtt/settings" component={MqttSettingsController} />
<AuthenticatedRoute
exact
path="/mqtt/status"
component={MqttStatusController}
/>
<AuthenticatedRoute
exact
path="/mqtt/settings"
component={MqttSettingsController}
/>
<Redirect to="/mqtt/status" />
</Switch>
</MenuAppBar>
)
);
}
}

View File

@@ -1,6 +1,11 @@
import React, { Component } from 'react';
import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import { MQTT_SETTINGS_ENDPOINT } from '../api';
import MqttSettingsForm from './MqttSettingsForm';
@@ -9,7 +14,6 @@ import { MqttSettings } from './types';
type MqttSettingsControllerProps = RestControllerProps<MqttSettings>;
class MqttSettingsController extends Component<MqttSettingsControllerProps> {
componentDidMount() {
this.props.loadData();
}
@@ -19,12 +23,11 @@ class MqttSettingsController extends Component<MqttSettingsControllerProps> {
<SectionContent title="MQTT Settings" titleGutter>
<RestFormLoader
{...this.props}
render={formProps => <MqttSettingsForm {...formProps} />}
render={(formProps) => <MqttSettingsForm {...formProps} />}
/>
</SectionContent>
)
);
}
}
export default restController(MQTT_SETTINGS_ENDPOINT, MqttSettingsController);

View File

@@ -1,31 +1,31 @@
import React from "react";
import React from 'react';
import {
TextValidator,
ValidatorForm,
SelectValidator,
} from "react-material-ui-form-validator";
SelectValidator
} from 'react-material-ui-form-validator';
import { Checkbox, TextField, Typography } from "@material-ui/core";
import SaveIcon from "@material-ui/icons/Save";
import MenuItem from "@material-ui/core/MenuItem";
import { Checkbox, TextField, Typography } from '@material-ui/core';
import SaveIcon from '@material-ui/icons/Save';
import MenuItem from '@material-ui/core/MenuItem';
import {
RestFormProps,
FormActions,
FormButton,
BlockFormControlLabel,
PasswordValidator,
} from "../components";
import { isIP, isHostname, or, isPath } from "../validators";
PasswordValidator
} from '../components';
import { isIP, isHostname, or, isPath } from '../validators';
import { MqttSettings } from "./types";
import { MqttSettings } from './types';
type MqttSettingsFormProps = RestFormProps<MqttSettings>;
class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
componentDidMount() {
ValidatorForm.addValidationRule("isIPOrHostname", or(isIP, isHostname));
ValidatorForm.addValidationRule("isPath", isPath);
ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname));
ValidatorForm.addValidationRule('isPath', isPath);
}
render() {
@@ -36,38 +36,38 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={
<Checkbox
checked={data.enabled}
onChange={handleValueChange("enabled")}
onChange={handleValueChange('enabled')}
value="enabled"
/>
}
label="Enable MQTT"
/>
<TextValidator
validators={["required", "isIPOrHostname"]}
validators={['required', 'isIPOrHostname']}
errorMessages={[
"Host is required",
"Not a valid IP address or hostname",
'Host is required',
'Not a valid IP address or hostname'
]}
name="host"
label="Host"
fullWidth
variant="outlined"
value={data.host}
onChange={handleValueChange("host")}
onChange={handleValueChange('host')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Port is required",
"Must be a number",
"Must be greater than 0 ",
"Max value is 65535",
'Port is required',
'Must be a number',
'Must be greater than 0 ',
'Max value is 65535'
]}
name="port"
label="Port"
@@ -75,18 +75,18 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.port}
type="number"
onChange={handleValueChange("port")}
onChange={handleValueChange('port')}
margin="normal"
/>
<TextValidator
validators={["required", "isPath"]}
errorMessages={["Base is required", "Not a valid Path"]}
validators={['required', 'isPath']}
errorMessages={['Base is required', 'Not a valid Path']}
name="base"
label="Base"
fullWidth
variant="outlined"
value={data.base}
onChange={handleValueChange("base")}
onChange={handleValueChange('base')}
margin="normal"
/>
<TextField
@@ -95,7 +95,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
fullWidth
variant="outlined"
value={data.username}
onChange={handleValueChange("username")}
onChange={handleValueChange('username')}
margin="normal"
/>
<PasswordValidator
@@ -104,7 +104,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
fullWidth
variant="outlined"
value={data.password}
onChange={handleValueChange("password")}
onChange={handleValueChange('password')}
margin="normal"
/>
<TextField
@@ -113,21 +113,21 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
fullWidth
variant="outlined"
value={data.client_id}
onChange={handleValueChange("client_id")}
onChange={handleValueChange('client_id')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:1",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:1',
'maxNumber:65535'
]}
errorMessages={[
"Keep alive is required",
"Must be a number",
"Must be greater than 0",
"Max value is 65535",
'Keep alive is required',
'Must be a number',
'Must be greater than 0',
'Max value is 65535'
]}
name="keep_alive"
label="Keep Alive (seconds)"
@@ -135,7 +135,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.keep_alive}
type="number"
onChange={handleValueChange("keep_alive")}
onChange={handleValueChange('keep_alive')}
margin="normal"
/>
<SelectValidator
@@ -144,7 +144,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
value={data.mqtt_qos}
fullWidth
variant="outlined"
onChange={handleValueChange("mqtt_qos")}
onChange={handleValueChange('mqtt_qos')}
margin="normal"
>
<MenuItem value={0}>0 (default)</MenuItem>
@@ -155,7 +155,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={
<Checkbox
checked={data.clean_session}
onChange={handleValueChange("clean_session")}
onChange={handleValueChange('clean_session')}
value="clean_session"
/>
}
@@ -165,7 +165,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={
<Checkbox
checked={data.mqtt_retain}
onChange={handleValueChange("mqtt_retain")}
onChange={handleValueChange('mqtt_retain')}
value="mqtt_retain"
/>
}
@@ -181,7 +181,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
value={data.nested_format}
fullWidth
variant="outlined"
onChange={handleValueChange("nested_format")}
onChange={handleValueChange('nested_format')}
margin="normal"
>
<MenuItem value={1}>nested on a single topic</MenuItem>
@@ -193,7 +193,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
value={data.dallas_format}
fullWidth
variant="outlined"
onChange={handleValueChange("dallas_format")}
onChange={handleValueChange('dallas_format')}
margin="normal"
>
<MenuItem value={1}>by Sensor ID</MenuItem>
@@ -205,7 +205,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
value={data.bool_format}
fullWidth
variant="outlined"
onChange={handleValueChange("bool_format")}
onChange={handleValueChange('bool_format')}
margin="normal"
>
<MenuItem value={1}>"on"/"off"</MenuItem>
@@ -219,7 +219,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
value={data.subscribe_format}
fullWidth
variant="outlined"
onChange={handleValueChange("subscribe_format")}
onChange={handleValueChange('subscribe_format')}
margin="normal"
>
<MenuItem value={0}>general device topic</MenuItem>
@@ -230,7 +230,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
control={
<Checkbox
checked={data.ha_enabled}
onChange={handleValueChange("ha_enabled")}
onChange={handleValueChange('ha_enabled')}
value="ha_enabled"
/>
}
@@ -243,7 +243,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
value={data.ha_climate_format}
fullWidth
variant="outlined"
onChange={handleValueChange("ha_climate_format")}
onChange={handleValueChange('ha_climate_format')}
margin="normal"
>
<MenuItem value={1}>use Current temperature (default)</MenuItem>
@@ -257,16 +257,16 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
</Typography>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Publish time is required",
"Must be a number",
"Must be 0 or greater",
"Max value is 65535",
'Publish time is required',
'Must be a number',
'Must be 0 or greater',
'Max value is 65535'
]}
name="publish_time_boiler"
label="Boiler Publish Interval (seconds, 0=on change)"
@@ -274,21 +274,21 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.publish_time_boiler}
type="number"
onChange={handleValueChange("publish_time_boiler")}
onChange={handleValueChange('publish_time_boiler')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Publish time is required",
"Must be a number",
"Must be 0 or greater",
"Max value is 65535",
'Publish time is required',
'Must be a number',
'Must be 0 or greater',
'Max value is 65535'
]}
name="publish_time_thermostat"
label="Thermostat Publish Interval (seconds, 0=on change)"
@@ -296,21 +296,21 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.publish_time_thermostat}
type="number"
onChange={handleValueChange("publish_time_thermostat")}
onChange={handleValueChange('publish_time_thermostat')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Publish time is required",
"Must be a number",
"Must be 0 or greater",
"Max value is 65535",
'Publish time is required',
'Must be a number',
'Must be 0 or greater',
'Max value is 65535'
]}
name="publish_time_solar"
label="Solar Publish Interval (seconds, 0=on change)"
@@ -318,21 +318,21 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.publish_time_solar}
type="number"
onChange={handleValueChange("publish_time_solar")}
onChange={handleValueChange('publish_time_solar')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Publish time is required",
"Must be a number",
"Must be 0 or greater",
"Max value is 65535",
'Publish time is required',
'Must be a number',
'Must be 0 or greater',
'Max value is 65535'
]}
name="publish_time_mixer"
label="Mixer Publish Interval (seconds, 0=on change)"
@@ -340,21 +340,21 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.publish_time_mixer}
type="number"
onChange={handleValueChange("publish_time_mixer")}
onChange={handleValueChange('publish_time_mixer')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Publish time is required",
"Must be a number",
"Must be 0 or greater",
"Max value is 65535",
'Publish time is required',
'Must be a number',
'Must be 0 or greater',
'Max value is 65535'
]}
name="publish_time_sensor"
label="Sensors Publish Interval (seconds, 0=on change)"
@@ -362,21 +362,21 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.publish_time_sensor}
type="number"
onChange={handleValueChange("publish_time_sensor")}
onChange={handleValueChange('publish_time_sensor')}
margin="normal"
/>
<TextValidator
validators={[
"required",
"isNumber",
"minNumber:0",
"maxNumber:65535",
'required',
'isNumber',
'minNumber:0',
'maxNumber:65535'
]}
errorMessages={[
"Publish time is required",
"Must be a number",
"Must be 0 or greater",
"Max value is 65535",
'Publish time is required',
'Must be a number',
'Must be 0 or greater',
'Max value is 65535'
]}
name="publish_time_other"
label="All other Modules Publish Interval (seconds, 0=on change)"
@@ -384,7 +384,7 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined"
value={data.publish_time_other}
type="number"
onChange={handleValueChange("publish_time_other")}
onChange={handleValueChange('publish_time_other')}
margin="normal"
/>
<FormActions>

View File

@@ -1,59 +1,59 @@
import { Theme } from '@material-ui/core'
import { MqttStatus, MqttDisconnectReason } from './types'
import { Theme } from '@material-ui/core';
import { MqttStatus, MqttDisconnectReason } from './types';
export const mqttStatusHighlight = (
{ enabled, connected }: MqttStatus,
theme: Theme,
theme: Theme
) => {
if (!enabled) {
return theme.palette.info.main
return theme.palette.info.main;
}
if (connected) {
return theme.palette.success.main
return theme.palette.success.main;
}
return theme.palette.error.main
}
return theme.palette.error.main;
};
export const mqttStatus = ({ enabled, connected }: MqttStatus) => {
if (!enabled) {
return 'Not enabled'
return 'Not enabled';
}
if (connected) {
return 'Connected'
return 'Connected';
}
return 'Disconnected'
}
return 'Disconnected';
};
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
switch (disconnect_reason) {
case MqttDisconnectReason.TCP_DISCONNECTED:
return 'TCP disconnected'
return 'TCP disconnected';
case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
return 'Unacceptable protocol version'
return 'Unacceptable protocol version';
case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED:
return 'Client ID rejected'
return 'Client ID rejected';
case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE:
return 'Server unavailable'
return 'Server unavailable';
case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS:
return 'Malformed credentials'
return 'Malformed credentials';
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
return 'Not authorized'
return 'Not authorized';
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
return 'Device out of memory'
return 'Device out of memory';
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
return 'Server fingerprint invalid'
return 'Server fingerprint invalid';
default:
return 'Unknown'
return 'Unknown';
}
}
};
export const mqttPublishHighlight = (
{ mqtt_fails }: MqttStatus,
theme: Theme,
theme: Theme
) => {
if (mqtt_fails === 0) return theme.palette.success.main
if (mqtt_fails === 0) return theme.palette.success.main;
if (mqtt_fails < 10) return theme.palette.warning.main
if (mqtt_fails < 10) return theme.palette.warning.main;
return theme.palette.error.main
}
return theme.palette.error.main;
};

View File

@@ -1,6 +1,11 @@
import React, { Component } from 'react';
import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components';
import {
restController,
RestControllerProps,
RestFormLoader,
SectionContent
} from '../components';
import { MQTT_STATUS_ENDPOINT } from '../api';
import MqttStatusForm from './MqttStatusForm';
@@ -9,7 +14,6 @@ import { MqttStatus } from './types';
type MqttStatusControllerProps = RestControllerProps<MqttStatus>;
class MqttStatusController extends Component<MqttStatusControllerProps> {
componentDidMount() {
this.props.loadData();
}
@@ -19,10 +23,10 @@ class MqttStatusController extends Component<MqttStatusControllerProps> {
<SectionContent title="MQTT Status">
<RestFormLoader
{...this.props}
render={formProps => <MqttStatusForm {...formProps} />}
render={(formProps) => <MqttStatusForm {...formProps} />}
/>
</SectionContent>
)
);
}
}

View File

@@ -1,23 +1,39 @@
import React, { Component, Fragment } from 'react';
import { Component, Fragment } from 'react';
import { WithTheme, withTheme } from '@material-ui/core/styles';
import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
import {
Avatar,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText
} from '@material-ui/core';
import DeviceHubIcon from '@material-ui/icons/DeviceHub';
import RefreshIcon from '@material-ui/icons/Refresh';
import ReportIcon from '@material-ui/icons/Report';
import SpeakerNotesOffIcon from "@material-ui/icons/SpeakerNotesOff";
import SpeakerNotesOffIcon from '@material-ui/icons/SpeakerNotesOff';
import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components';
import { mqttStatusHighlight, mqttStatus, mqttPublishHighlight, disconnectReason } from './MqttStatus';
import {
RestFormProps,
FormActions,
FormButton,
HighlightAvatar
} from '../components';
import {
mqttStatusHighlight,
mqttStatus,
mqttPublishHighlight,
disconnectReason
} from './MqttStatus';
import { MqttStatus } from './types';
type MqttStatusFormProps = RestFormProps<MqttStatus> & WithTheme;
class MqttStatusForm extends Component<MqttStatusFormProps> {
renderConnectionStatus() {
const { data, theme } = this.props
const { data, theme } = this.props;
if (data.connected) {
return (
<Fragment>
@@ -50,7 +66,10 @@ class MqttStatusForm extends Component<MqttStatusFormProps> {
<ReportIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary="Disconnect Reason" secondary={disconnectReason(data)} />
<ListItemText
primary="Disconnect Reason"
secondary={disconnectReason(data)}
/>
</ListItem>
<Divider variant="inset" component="li" />
</Fragment>
@@ -58,7 +77,7 @@ class MqttStatusForm extends Component<MqttStatusFormProps> {
}
createListItems() {
const { data, theme } = this.props
const { data, theme } = this.props;
return (
<Fragment>
<ListItem>
@@ -78,18 +97,20 @@ class MqttStatusForm extends Component<MqttStatusFormProps> {
render() {
return (
<Fragment>
<List>
{this.createListItems()}
</List>
<List>{this.createListItems()}</List>
<FormActions>
<FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
<FormButton
startIcon={<RefreshIcon />}
variant="contained"
color="secondary"
onClick={this.props.loadData}
>
Refresh
</FormButton>
</FormActions>
</Fragment>
);
}
}
export default withTheme(MqttStatusForm);

View File

@@ -6,40 +6,40 @@ export enum MqttDisconnectReason {
MQTT_MALFORMED_CREDENTIALS = 4,
MQTT_NOT_AUTHORIZED = 5,
ESP8266_NOT_ENOUGH_SPACE = 6,
TLS_BAD_FINGERPRINT = 7,
TLS_BAD_FINGERPRINT = 7
}
export interface MqttStatus {
enabled: boolean
connected: boolean
client_id: string
disconnect_reason: MqttDisconnectReason
mqtt_fails: number
enabled: boolean;
connected: boolean;
client_id: string;
disconnect_reason: MqttDisconnectReason;
mqtt_fails: number;
}
export interface MqttSettings {
enabled: boolean
host: string
port: number
base: string
username: string
password: string
client_id: string
keep_alive: number
clean_session: boolean
max_topic_length: number
publish_time_boiler: number
publish_time_thermostat: number
publish_time_solar: number
publish_time_mixer: number
publish_time_other: number
publish_time_sensor: number
dallas_format: number
bool_format: number
mqtt_qos: number
mqtt_retain: boolean
ha_enabled: boolean
ha_climate_format: number
nested_format: number
subscribe_format: number
enabled: boolean;
host: string;
port: number;
base: string;
username: string;
password: string;
client_id: string;
keep_alive: number;
clean_session: boolean;
max_topic_length: number;
publish_time_boiler: number;
publish_time_thermostat: number;
publish_time_solar: number;
publish_time_mixer: number;
publish_time_other: number;
publish_time_sensor: number;
dallas_format: number;
bool_format: number;
mqtt_qos: number;
mqtt_retain: boolean;
ha_enabled: boolean;
ha_climate_format: number;
nested_format: number;
subscribe_format: number;
}