Browse Source

start reducing boilerplate by introducing rest HOC

master
rjwats@gmail.com 6 years ago
parent
commit
eb4bcf708e
  1. 96
      interface/src/components/RestComponent.js
  2. 56
      interface/src/containers/APStatus.js
  3. 56
      interface/src/containers/NTPStatus.js
  4. 55
      interface/src/containers/WiFiStatus.js

96
interface/src/components/RestComponent.js

@ -0,0 +1,96 @@
import React from 'react';
import {withNotifier} from '../components/SnackbarNotification';
export const restComponent = (endpointUrl, FormComponent) => {
return withNotifier(
class extends React.Component {
constructor(props) {
super(props);
this.setState = this.setState.bind(this);
this.loadData = this.loadData.bind(this);
this.saveData = this.saveData.bind(this);
this.setData = this.setData.bind(this);
}
setData(data) {
this.setState({
data:data,
fetched: true,
errorMessage:null
});
}
loadData() {
this.setState({
data:null,
fetched: false,
errorMessage:null
});
fetch(endpointUrl)
.then(response => {
if (response.status === 200) {
return response.json();
}
throw Error("Invalid status code: " + response.status);
})
.then(json => {this.setState({data: json, fetched:true})})
.catch(error =>{
this.props.raiseNotification("Problem fetching: " + error.message);
this.setState({data: null, fetched:true, errorMessage:error.message});
});
}
saveData(e) {
this.setState({fetched: false});
fetch(endpointUrl, {
method: 'POST',
body: JSON.stringify(this.statedata),
headers: new Headers({
'Content-Type': 'application/json'
})
})
.then(response => {
if (response.status === 200) {
return response.json();
}
throw Error("Invalid status code: " + response.status);
})
.then(json => {
this.props.raiseNotification("Changes successfully applied.");
this.setState({data: json, fetched:true});
}).catch(error => {
this.props.raiseNotification("Problem saving: " + error.message);
this.setState({data: null, fetched:true, errorMessage:error.message});
});
}
valueChange = name => event => {
const { data } = this.state;
data[name] = event.target.value;
this.setState({data});
};
checkboxChange = name => event => {
const { data } = this.state;
data[name] = event.target.checked;
this.setState({data});
}
render() {
return <FormComponent
valueChange={this.valueChange}
checkboxChange={this.checkboxChange}
setData={this.setData}
saveData={this.saveData}
loadData={this.loadData}
{...this.state}
{...this.props}
/>;
}
}
);
}

56
interface/src/containers/APStatus.js

@ -11,7 +11,7 @@ import SettingsInputAntennaIcon from 'material-ui-icons/SettingsInputAntenna';
import DeviceHubIcon from 'material-ui-icons/DeviceHub';
import ComputerIcon from 'material-ui-icons/Computer';
import {withNotifier} from '../components/SnackbarNotification';
import {restComponent} from '../components/RestComponent';
import SectionContent from '../components/SectionContent'
import * as Highlight from '../constants/Highlight';
@ -37,50 +37,29 @@ const styles = theme => ({
class APStatus extends Component {
constructor(props) {
super(props);
this.state = {
status:null,
fetched: false,
errorMessage:null
};
this.setState = this.setState.bind(this);
this.loadAPStatus = this.loadAPStatus.bind(this);
}
componentDidMount() {
this.loadAPStatus();
}
loadAPStatus() {
simpleGet(
AP_STATUS_ENDPOINT,
this.setState,
this.props.raiseNotification
);
this.props.loadData();
}
apStatusHighlight(status){
return status.active ? Highlight.SUCCESS : Highlight.IDLE;
apStatusHighlight(data){
return data.active ? Highlight.SUCCESS : Highlight.IDLE;
}
apStatus(status){
return status.active ? "Active" : "Inactive";
apStatus(data){
return data.active ? "Active" : "Inactive";
}
// active, ip_address, mac_address, station_num
renderAPStatus(status, fullDetails, classes){
renderAPStatus(data, fullDetails, classes){
const listItems = [];
listItems.push(
<ListItem key="ap_status">
<Avatar className={classes["apStatus_" + this.apStatusHighlight(status)]}>
<Avatar className={classes["apStatus_" + this.apStatusHighlight(data)]}>
<SettingsInputAntennaIcon />
</Avatar>
<ListItemText primary="Status" secondary={this.apStatus(status)} />
<ListItemText primary="Status" secondary={this.apStatus(data)} />
</ListItem>
);
listItems.push(<Divider key="ap_status_divider" inset component="li" />);
@ -88,7 +67,7 @@ class APStatus extends Component {
listItems.push(
<ListItem key="ip_address">
<Avatar>IP</Avatar>
<ListItemText primary="IP Address" secondary={status.ip_address} />
<ListItemText primary="IP Address" secondary={data.ip_address} />
</ListItem>
);
listItems.push(<Divider key="ip_address_divider" inset component="li" />);
@ -98,7 +77,7 @@ class APStatus extends Component {
<Avatar>
<DeviceHubIcon />
</Avatar>
<ListItemText primary="MAC Address" secondary={status.mac_address} />
<ListItemText primary="MAC Address" secondary={data.mac_address} />
</ListItem>
);
listItems.push(<Divider key="mac_address_divider" inset component="li" />);
@ -108,7 +87,7 @@ class APStatus extends Component {
<Avatar>
<ComputerIcon />
</Avatar>
<ListItemText primary="AP Clients" secondary={status.station_num} />
<ListItemText primary="AP Clients" secondary={data.station_num} />
</ListItem>
);
listItems.push(<Divider key="station_num_divider" inset component="li" />);
@ -118,7 +97,7 @@ class APStatus extends Component {
<List>
{listItems}
</List>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadAPStatus}>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
@ -126,8 +105,7 @@ class APStatus extends Component {
}
render() {
const { status, fetched, errorMessage } = this.state;
const { classes, fullDetails } = this.props;
const { data, fetched, errorMessage, classes, fullDetails } = this.props;
return (
<SectionContent title="AP Status">
@ -140,13 +118,13 @@ class APStatus extends Component {
</Typography>
</div>
:
status ? this.renderAPStatus(status, fullDetails, classes)
data ? this.renderAPStatus(data, fullDetails, classes)
:
<div>
<Typography variant="display1" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadAPStatus}>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
@ -156,4 +134,4 @@ class APStatus extends Component {
}
}
export default withNotifier(withStyles(styles)(APStatus));
export default restComponent(AP_STATUS_ENDPOINT, withStyles(styles)(APStatus));

56
interface/src/containers/NTPStatus.js

@ -19,11 +19,9 @@ import * as Highlight from '../constants/Highlight';
import { unixTimeToTimeAndDate } from '../constants/TimeFormat';
import { NTP_STATUS_ENDPOINT } from '../constants/Endpoints';
import {withNotifier} from '../components/SnackbarNotification';
import { restComponent } from '../components/RestComponent';
import SectionContent from '../components/SectionContent';
import { simpleGet } from '../helpers/SimpleGet';
import moment from 'moment';
const styles = theme => ({
@ -48,51 +46,30 @@ const styles = theme => ({
class NTPStatus extends Component {
constructor(props) {
super(props);
this.state = {
status:null,
fetched: false,
errorMessage:null
};
this.setState = this.setState.bind(this);
this.loadNTPStatus = this.loadNTPStatus.bind(this);
}
componentDidMount() {
this.loadNTPStatus();
}
loadNTPStatus() {
simpleGet(
NTP_STATUS_ENDPOINT,
this.setState,
this.props.raiseNotification
);
this.props.loadData();
}
renderNTPStatus(status, fullDetails, classes){
renderNTPStatus(data, fullDetails, classes){
const listItems = [];
listItems.push(
<ListItem key="ntp_status">
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(status)]}>
<Avatar className={classes["ntpStatus_" + ntpStatusHighlight(data)]}>
<UpdateIcon />
</Avatar>
<ListItemText primary="Status" secondary={ntpStatus(status)} />
<ListItemText primary="Status" secondary={ntpStatus(data)} />
</ListItem>
);
listItems.push(<Divider key="ntp_status_divider" inset component="li" />);
if (isSynchronized(status)) {
if (isSynchronized(data)) {
listItems.push(
<ListItem key="time_now">
<Avatar>
<AccessTimeIcon />
</Avatar>
<ListItemText primary="Time Now" secondary={unixTimeToTimeAndDate(status.now)} />
<ListItemText primary="Time Now" secondary={unixTimeToTimeAndDate(data.now)} />
</ListItem>
);
listItems.push(<Divider key="time_now_divider" inset component="li" />);
@ -103,7 +80,7 @@ class NTPStatus extends Component {
<Avatar>
<SwapVerticalCircleIcon />
</Avatar>
<ListItemText primary="Last Sync" secondary={status.last_sync > 0 ? unixTimeToTimeAndDate(status.last_sync) : "never" } />
<ListItemText primary="Last Sync" secondary={data.last_sync > 0 ? unixTimeToTimeAndDate(data.last_sync) : "never" } />
</ListItem>
);
listItems.push(<Divider key="last_sync_divider" inset component="li" />);
@ -113,7 +90,7 @@ class NTPStatus extends Component {
<Avatar>
<DNSIcon />
</Avatar>
<ListItemText primary="NTP Server" secondary={status.server} />
<ListItemText primary="NTP Server" secondary={data.server} />
</ListItem>
);
listItems.push(<Divider key="ntp_server_divider" inset component="li" />);
@ -123,7 +100,7 @@ class NTPStatus extends Component {
<Avatar>
<TimerIcon />
</Avatar>
<ListItemText primary="Sync Interval" secondary={moment.duration(status.interval, 'seconds').humanize()} />
<ListItemText primary="Sync Interval" secondary={moment.duration(data.interval, 'seconds').humanize()} />
</ListItem>
);
listItems.push(<Divider key="sync_interval_divider" inset component="li" />);
@ -133,7 +110,7 @@ class NTPStatus extends Component {
<Avatar>
<AvTimerIcon />
</Avatar>
<ListItemText primary="Uptime" secondary={moment.duration(status.uptime, 'seconds').humanize()} />
<ListItemText primary="Uptime" secondary={moment.duration(data.uptime, 'seconds').humanize()} />
</ListItem>
);
@ -142,7 +119,7 @@ class NTPStatus extends Component {
<List>
{listItems}
</List>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadNTPStatus}>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
@ -150,8 +127,7 @@ class NTPStatus extends Component {
}
render() {
const { status, fetched, errorMessage } = this.state;
const { classes, fullDetails } = this.props;
const { data, fetched, errorMessage, classes, fullDetails } = this.props;
return (
<SectionContent title="NTP Status">
@ -164,13 +140,13 @@ class NTPStatus extends Component {
</Typography>
</div>
:
status ? this.renderNTPStatus(status, fullDetails, classes)
data ? this.renderNTPStatus(data, fullDetails, classes)
:
<div>
<Typography variant="display1" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadNTPStatus}>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
@ -180,4 +156,4 @@ class NTPStatus extends Component {
}
}
export default withNotifier(withStyles(styles)(NTPStatus));
export default restComponent(NTP_STATUS_ENDPOINT, withStyles(styles)(NTPStatus));

55
interface/src/containers/WiFiStatus.js

@ -5,14 +5,13 @@ import Button from 'material-ui/Button';
import { LinearProgress } from 'material-ui/Progress';
import Typography from 'material-ui/Typography';
import {withNotifier} from '../components/SnackbarNotification';
import SectionContent from '../components/SectionContent';
import { WIFI_STATUS_ENDPOINT } from '../constants/Endpoints';
import { isConnected, connectionStatus, connectionStatusHighlight } from '../constants/WiFiConnectionStatus';
import * as Highlight from '../constants/Highlight';
import { simpleGet } from '../helpers/SimpleGet';
import { restComponent } from '../components/RestComponent';
import List, { ListItem, ListItemText } from 'material-ui/List';
import Avatar from 'material-ui/Avatar';
@ -47,21 +46,8 @@ const styles = theme => ({
class WiFiStatus extends Component {
constructor(props) {
super(props);
this.state = {
status:null,
fetched: false,
errorMessage:null
};
this.setState = this.setState.bind(this);
this.loadWiFiStatus = this.loadWiFiStatus.bind(this);
}
componentDidMount() {
this.loadWiFiStatus();
this.props.loadData();
}
dnsServers(status) {
@ -71,27 +57,19 @@ class WiFiStatus extends Component {
return status.dns_ip_1 + (status.dns_ip_2 ? ','+status.dns_ip_2 : '');
}
loadWiFiStatus() {
simpleGet(
WIFI_STATUS_ENDPOINT,
this.setState,
this.props.raiseNotification
);
}
renderWiFiStatus(status, fullDetails, classes) {
renderWiFiStatus(data, fullDetails, classes) {
const listItems = [];
listItems.push(
<ListItem key="connection_status">
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(status)]}>
<Avatar className={classes["wifiStatus_" + connectionStatusHighlight(data)]}>
<WifiIcon />
</Avatar>
<ListItemText primary="Connection Status" secondary={connectionStatus(status)} />
<ListItemText primary="Connection Status" secondary={connectionStatus(data)} />
</ListItem>
);
if (fullDetails && isConnected(status)) {
if (fullDetails && isConnected(data)) {
listItems.push(<Divider key="connection_status_divider" inset component="li" />);
listItems.push(
@ -99,7 +77,7 @@ class WiFiStatus extends Component {
<Avatar>
<SettingsInputAntennaIcon />
</Avatar>
<ListItemText primary="SSID" secondary={status.ssid} />
<ListItemText primary="SSID" secondary={data.ssid} />
</ListItem>
);
listItems.push(<Divider key="ssid_divider" inset component="li" />);
@ -107,7 +85,7 @@ class WiFiStatus extends Component {
listItems.push(
<ListItem key="ip_address">
<Avatar>IP</Avatar>
<ListItemText primary="IP Address" secondary={status.local_ip} />
<ListItemText primary="IP Address" secondary={data.local_ip} />
</ListItem>
);
listItems.push(<Divider key="ip_address_divider" inset component="li" />);
@ -115,7 +93,7 @@ class WiFiStatus extends Component {
listItems.push(
<ListItem key="subnet_mask">
<Avatar>#</Avatar>
<ListItemText primary="Subnet Mask" secondary={status.subnet_mask} />
<ListItemText primary="Subnet Mask" secondary={data.subnet_mask} />
</ListItem>
);
listItems.push(<Divider key="subnet_mask_divider" inset component="li" />);
@ -125,7 +103,7 @@ class WiFiStatus extends Component {
<Avatar>
<SettingsInputComponentIcon />
</Avatar>
<ListItemText primary="Gateway IP" secondary={status.gateway_ip ? status.gateway_ip : "none"} />
<ListItemText primary="Gateway IP" secondary={data.gateway_ip ? data.gateway_ip : "none"} />
</ListItem>
);
listItems.push(<Divider key="gateway_ip_divider" inset component="li" />);
@ -135,7 +113,7 @@ class WiFiStatus extends Component {
<Avatar>
<DNSIcon />
</Avatar>
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(status)} />
<ListItemText primary="DNS Server IP" secondary={this.dnsServers(data)} />
</ListItem>
);
}
@ -145,7 +123,7 @@ class WiFiStatus extends Component {
<List>
{listItems}
</List>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadWiFiStatus}>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
@ -154,8 +132,7 @@ class WiFiStatus extends Component {
}
render() {
const { status, fetched, errorMessage } = this.state;
const { classes, fullDetails } = this.props;
const { data, fetched, errorMessage, classes, fullDetails } = this.props;
return (
<SectionContent title="WiFi Status">
@ -168,13 +145,13 @@ class WiFiStatus extends Component {
</Typography>
</div>
:
status ? this.renderWiFiStatus(status, fullDetails, classes)
data ? this.renderWiFiStatus(data, fullDetails, classes)
:
<div>
<Typography variant="display1" className={classes.fetching}>
{errorMessage}
</Typography>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.loadWiFiStatus}>
<Button variant="raised" color="secondary" className={classes.button} onClick={this.props.loadData}>
Refresh
</Button>
</div>
@ -184,4 +161,4 @@ class WiFiStatus extends Component {
}
}
export default withNotifier(withStyles(styles)(WiFiStatus));
export default restComponent(WIFI_STATUS_ENDPOINT, withStyles(styles)(WiFiStatus));
Loading…
Cancel
Save